diff --git a/src/dialogs/kdenlivesettingsdialog.cpp b/src/dialogs/kdenlivesettingsdialog.cpp index c1408cd6c..3b5db2675 100644 --- a/src/dialogs/kdenlivesettingsdialog.cpp +++ b/src/dialogs/kdenlivesettingsdialog.cpp @@ -1,1510 +1,1507 @@ /*************************************************************************** * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "kdenlivesettingsdialog.h" #include "clipcreationdialog.h" #include "core.h" #include "dialogs/profilesdialog.h" #include "encodingprofilesdialog.h" #include "kdenlivesettings.h" #include "profiles/profilemodel.hpp" #include "profiles/profilerepository.hpp" #include "profilesdialog.h" #include "project/dialogs/profilewidget.h" #ifdef USE_V4L #include "capture/v4lcapture.h" #endif #include "kdenlive_debug.h" #include "klocalizedstring.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_JOGSHUTTLE #include "jogshuttle/jogaction.h" #include "jogshuttle/jogshuttleconfig.h" #include #include #endif KdenliveSettingsDialog::KdenliveSettingsDialog(const QMap &mappable_actions, bool gpuAllowed, QWidget *parent) : KConfigDialog(parent, QStringLiteral("settings"), KdenliveSettings::self()) , m_modified(false) , m_shuttleModified(false) , m_mappable_actions(mappable_actions) { KdenliveSettings::setV4l_format(0); QWidget *p1 = new QWidget; QFontInfo ftInfo(font()); m_configMisc.setupUi(p1); m_page1 = addPage(p1, i18n("Misc")); m_page1->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); - // Hide avformat-novalidate trick, causes crash (bug #2205 and #2206) - m_configMisc.kcfg_projectloading_avformatnovalidate->setVisible(false); - m_configMisc.kcfg_use_exiftool->setEnabled(!QStandardPaths::findExecutable(QStringLiteral("exiftool")).isEmpty()); QWidget *p8 = new QWidget; m_configProject.setupUi(p8); m_page8 = addPage(p8, i18n("Project Defaults")); auto *vbox = new QVBoxLayout; m_pw = new ProfileWidget(this); vbox->addWidget(m_pw); m_configProject.profile_box->setLayout(vbox); m_configProject.profile_box->setTitle(i18n("Select the default profile (preset)")); // Select profile m_pw->loadProfile(KdenliveSettings::default_profile().isEmpty() ? pCore->getCurrentProfile()->path() : KdenliveSettings::default_profile()); connect(m_pw, &ProfileWidget::profileChanged, this, &KdenliveSettingsDialog::slotDialogModified); m_page8->setIcon(QIcon::fromTheme(QStringLiteral("project-defaults"))); connect(m_configProject.kcfg_generateproxy, &QAbstractButton::toggled, m_configProject.kcfg_proxyminsize, &QWidget::setEnabled); m_configProject.kcfg_proxyminsize->setEnabled(KdenliveSettings::generateproxy()); m_configProject.projecturl->setMode(KFile::Directory); m_configProject.projecturl->setUrl(QUrl::fromLocalFile(KdenliveSettings::defaultprojectfolder())); connect(m_configProject.kcfg_generateimageproxy, &QAbstractButton::toggled, m_configProject.kcfg_proxyimageminsize, &QWidget::setEnabled); m_configProject.kcfg_proxyimageminsize->setEnabled(KdenliveSettings::generateimageproxy()); QWidget *p3 = new QWidget; m_configTimeline.setupUi(p3); m_page3 = addPage(p3, i18n("Timeline")); m_page3->setIcon(QIcon::fromTheme(QStringLiteral("video-display"))); m_configTimeline.kcfg_trackheight->setMinimum(ftInfo.pixelSize() * 1.5); QWidget *p2 = new QWidget; m_configEnv.setupUi(p2); m_configEnv.mltpathurl->setMode(KFile::Directory); m_configEnv.mltpathurl->lineEdit()->setObjectName(QStringLiteral("kcfg_mltpath")); m_configEnv.rendererpathurl->lineEdit()->setObjectName(QStringLiteral("kcfg_rendererpath")); m_configEnv.ffmpegurl->lineEdit()->setObjectName(QStringLiteral("kcfg_ffmpegpath")); m_configEnv.ffplayurl->lineEdit()->setObjectName(QStringLiteral("kcfg_ffplaypath")); m_configEnv.ffprobeurl->lineEdit()->setObjectName(QStringLiteral("kcfg_ffprobepath")); int maxThreads = QThread::idealThreadCount(); m_configEnv.kcfg_mltthreads->setMaximum(maxThreads > 2 ? maxThreads : 8); m_configEnv.tmppathurl->setMode(KFile::Directory); m_configEnv.tmppathurl->lineEdit()->setObjectName(QStringLiteral("kcfg_currenttmpfolder")); m_configEnv.capturefolderurl->setMode(KFile::Directory); m_configEnv.capturefolderurl->lineEdit()->setObjectName(QStringLiteral("kcfg_capturefolder")); m_configEnv.capturefolderurl->setEnabled(!KdenliveSettings::capturetoprojectfolder()); connect(m_configEnv.kcfg_capturetoprojectfolder, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEnableCaptureFolder); // Library folder m_configEnv.libraryfolderurl->setMode(KFile::Directory); m_configEnv.libraryfolderurl->lineEdit()->setObjectName(QStringLiteral("kcfg_libraryfolder")); m_configEnv.libraryfolderurl->setEnabled(!KdenliveSettings::librarytodefaultfolder()); m_configEnv.kcfg_librarytodefaultfolder->setToolTip(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/library")); connect(m_configEnv.kcfg_librarytodefaultfolder, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEnableLibraryFolder); // Mime types QStringList mimes = ClipCreationDialog::getExtensions(); qSort(mimes); m_configEnv.supportedmimes->setPlainText(mimes.join(QLatin1Char(' '))); m_page2 = addPage(p2, i18n("Environment")); m_page2->setIcon(QIcon::fromTheme(QStringLiteral("application-x-executable-script"))); QWidget *p4 = new QWidget; m_configCapture.setupUi(p4); m_configCapture.tabWidget->removeTab(0); m_configCapture.tabWidget->removeTab(2); #ifdef USE_V4L // Video 4 Linux device detection for (int i = 0; i < 10; ++i) { QString path = QStringLiteral("/dev/video") + QString::number(i); if (QFile::exists(path)) { QStringList deviceInfo = V4lCaptureHandler::getDeviceName(path); if (!deviceInfo.isEmpty()) { m_configCapture.kcfg_detectedv4ldevices->addItem(deviceInfo.at(0), path); m_configCapture.kcfg_detectedv4ldevices->setItemData(m_configCapture.kcfg_detectedv4ldevices->count() - 1, deviceInfo.at(1), Qt::UserRole + 1); } } } connect(m_configCapture.kcfg_detectedv4ldevices, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdatev4lDevice); connect(m_configCapture.kcfg_v4l_format, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdatev4lCaptureProfile); connect(m_configCapture.config_v4l, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEditVideo4LinuxProfile); slotUpdatev4lDevice(); #endif m_page4 = addPage(p4, i18n("Capture")); m_page4->setIcon(QIcon::fromTheme(QStringLiteral("media-record"))); m_configCapture.tabWidget->setCurrentIndex(KdenliveSettings::defaultcapture()); #ifdef Q_WS_MAC m_configCapture.tabWidget->setEnabled(false); m_configCapture.kcfg_defaultcapture->setEnabled(false); m_configCapture.label->setText(i18n("Capture is not yet available on Mac OS X.")); #endif QWidget *p5 = new QWidget; m_configShuttle.setupUi(p5); #ifdef USE_JOGSHUTTLE m_configShuttle.toolBtnReload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(m_configShuttle.kcfg_enableshuttle, &QCheckBox::stateChanged, this, &KdenliveSettingsDialog::slotCheckShuttle); connect(m_configShuttle.shuttledevicelist, SIGNAL(activated(int)), this, SLOT(slotUpdateShuttleDevice(int))); connect(m_configShuttle.toolBtnReload, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotReloadShuttleDevices); slotCheckShuttle(static_cast(KdenliveSettings::enableshuttle())); m_configShuttle.shuttledisabled->hide(); // Store the button pointers into an array for easier handling them in the other functions. // TODO: impl enumerator or live with cut and paste :-))) setupJogshuttleBtns(KdenliveSettings::shuttledevice()); #if 0 m_shuttle_buttons.push_back(m_configShuttle.shuttle1); m_shuttle_buttons.push_back(m_configShuttle.shuttle2); m_shuttle_buttons.push_back(m_configShuttle.shuttle3); m_shuttle_buttons.push_back(m_configShuttle.shuttle4); m_shuttle_buttons.push_back(m_configShuttle.shuttle5); m_shuttle_buttons.push_back(m_configShuttle.shuttle6); m_shuttle_buttons.push_back(m_configShuttle.shuttle7); m_shuttle_buttons.push_back(m_configShuttle.shuttle8); m_shuttle_buttons.push_back(m_configShuttle.shuttle9); m_shuttle_buttons.push_back(m_configShuttle.shuttle10); m_shuttle_buttons.push_back(m_configShuttle.shuttle11); m_shuttle_buttons.push_back(m_configShuttle.shuttle12); m_shuttle_buttons.push_back(m_configShuttle.shuttle13); m_shuttle_buttons.push_back(m_configShuttle.shuttle14); m_shuttle_buttons.push_back(m_configShuttle.shuttle15); #endif #else /* ! USE_JOGSHUTTLE */ m_configShuttle.kcfg_enableshuttle->hide(); m_configShuttle.kcfg_enableshuttle->setDisabled(true); #endif /* USE_JOGSHUTTLE */ m_page5 = addPage(p5, i18n("JogShuttle")); m_page5->setIcon(QIcon::fromTheme(QStringLiteral("jog-dial"))); QWidget *p6 = new QWidget; m_configSdl.setupUi(p6); m_configSdl.reload_blackmagic->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(m_configSdl.reload_blackmagic, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotReloadBlackMagic); // m_configSdl.kcfg_openglmonitors->setHidden(true); m_page6 = addPage(p6, i18n("Playback")); m_page6->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); QWidget *p7 = new QWidget; m_configTranscode.setupUi(p7); m_page7 = addPage(p7, i18n("Transcode")); m_page7->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); connect(m_configTranscode.button_add, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotAddTranscode); connect(m_configTranscode.button_delete, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotDeleteTranscode); connect(m_configTranscode.profiles_list, &QListWidget::itemChanged, this, &KdenliveSettingsDialog::slotDialogModified); connect(m_configTranscode.profiles_list, &QListWidget::currentRowChanged, this, &KdenliveSettingsDialog::slotSetTranscodeProfile); connect(m_configTranscode.profile_name, &QLineEdit::textChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate); connect(m_configTranscode.profile_description, &QLineEdit::textChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate); connect(m_configTranscode.profile_extension, &QLineEdit::textChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate); connect(m_configTranscode.profile_parameters, &QPlainTextEdit::textChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate); connect(m_configTranscode.profile_audioonly, &QCheckBox::stateChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate); connect(m_configTranscode.button_update, &QAbstractButton::pressed, this, &KdenliveSettingsDialog::slotUpdateTranscodingProfile); m_configTranscode.profile_parameters->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5); connect(m_configEnv.kp_image, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEditImageApplication); connect(m_configEnv.kp_audio, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEditAudioApplication); loadEncodingProfiles(); connect(m_configSdl.kcfg_audio_driver, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotCheckAlsaDriver); connect(m_configSdl.kcfg_audio_backend, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotCheckAudioBackend); initDevices(); connect(m_configCapture.kcfg_grab_capture_type, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdateGrabRegionStatus); slotUpdateGrabRegionStatus(); loadTranscodeProfiles(); // decklink profile QAction *act = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure profiles"), this); act->setData(4); connect(act, &QAction::triggered, this, &KdenliveSettingsDialog::slotManageEncodingProfile); m_configCapture.decklink_manageprofile->setDefaultAction(act); m_configCapture.decklink_showprofileinfo->setIcon(QIcon::fromTheme(QStringLiteral("help-about"))); m_configCapture.decklink_parameters->setVisible(false); m_configCapture.decklink_parameters->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 4); m_configCapture.decklink_parameters->setPlainText(KdenliveSettings::decklink_parameters()); connect(m_configCapture.kcfg_decklink_profile, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdateDecklinkProfile); connect(m_configCapture.decklink_showprofileinfo, &QAbstractButton::clicked, m_configCapture.decklink_parameters, &QWidget::setVisible); // ffmpeg profile m_configCapture.v4l_showprofileinfo->setIcon(QIcon::fromTheme(QStringLiteral("help-about"))); m_configCapture.v4l_parameters->setVisible(false); m_configCapture.v4l_parameters->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 4); m_configCapture.v4l_parameters->setPlainText(KdenliveSettings::v4l_parameters()); act = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure profiles"), this); act->setData(2); connect(act, &QAction::triggered, this, &KdenliveSettingsDialog::slotManageEncodingProfile); m_configCapture.v4l_manageprofile->setDefaultAction(act); connect(m_configCapture.kcfg_v4l_profile, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdateV4lProfile); connect(m_configCapture.v4l_showprofileinfo, &QAbstractButton::clicked, m_configCapture.v4l_parameters, &QWidget::setVisible); // screen grab profile m_configCapture.grab_showprofileinfo->setIcon(QIcon::fromTheme(QStringLiteral("help-about"))); m_configCapture.grab_parameters->setVisible(false); m_configCapture.grab_parameters->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 4); m_configCapture.grab_parameters->setPlainText(KdenliveSettings::grab_parameters()); act = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure profiles"), this); act->setData(3); connect(act, &QAction::triggered, this, &KdenliveSettingsDialog::slotManageEncodingProfile); m_configCapture.grab_manageprofile->setDefaultAction(act); connect(m_configCapture.kcfg_grab_profile, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdateGrabProfile); connect(m_configCapture.grab_showprofileinfo, &QAbstractButton::clicked, m_configCapture.grab_parameters, &QWidget::setVisible); // Timeline preview act = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure profiles"), this); act->setData(1); connect(act, &QAction::triggered, this, &KdenliveSettingsDialog::slotManageEncodingProfile); m_configProject.preview_manageprofile->setDefaultAction(act); connect(m_configProject.kcfg_preview_profile, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdatePreviewProfile); connect(m_configProject.preview_showprofileinfo, &QAbstractButton::clicked, m_configProject.previewparams, &QWidget::setVisible); m_configProject.previewparams->setVisible(false); m_configProject.previewparams->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 3); m_configProject.previewparams->setPlainText(KdenliveSettings::previewparams()); m_configProject.preview_showprofileinfo->setIcon(QIcon::fromTheme(QStringLiteral("help-about"))); m_configProject.preview_showprofileinfo->setToolTip(i18n("Show default timeline preview parameters")); m_configProject.preview_manageprofile->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); m_configProject.preview_manageprofile->setToolTip(i18n("Manage timeline preview profiles")); m_configProject.kcfg_preview_profile->setToolTip(i18n("Select default timeline preview profile")); // proxy profile stuff m_configProject.proxy_showprofileinfo->setIcon(QIcon::fromTheme(QStringLiteral("help-about"))); m_configProject.proxy_showprofileinfo->setToolTip(i18n("Show default profile parameters")); m_configProject.proxy_manageprofile->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); m_configProject.proxy_manageprofile->setToolTip(i18n("Manage proxy profiles")); m_configProject.kcfg_proxy_profile->setToolTip(i18n("Select default proxy profile")); m_configProject.proxyparams->setVisible(false); m_configProject.proxyparams->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 3); m_configProject.proxyparams->setPlainText(KdenliveSettings::proxyparams()); act = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure profiles"), this); act->setData(0); connect(act, &QAction::triggered, this, &KdenliveSettingsDialog::slotManageEncodingProfile); m_configProject.proxy_manageprofile->setDefaultAction(act); connect(m_configProject.proxy_showprofileinfo, &QAbstractButton::clicked, m_configProject.proxyparams, &QWidget::setVisible); connect(m_configProject.kcfg_proxy_profile, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdateProxyProfile); slotUpdateProxyProfile(-1); slotUpdateV4lProfile(-1); slotUpdateGrabProfile(-1); slotUpdateDecklinkProfile(-1); // enable GPU accel only if Movit is found m_configSdl.kcfg_gpu_accel->setEnabled(gpuAllowed); m_configSdl.kcfg_gpu_accel->setToolTip(i18n("GPU processing needs MLT compiled with Movit and Rtaudio modules")); getBlackMagicDeviceList(m_configCapture.kcfg_decklink_capturedevice); if (!getBlackMagicOutputDeviceList(m_configSdl.kcfg_blackmagic_output_device)) { // No blackmagic card found m_configSdl.kcfg_external_display->setEnabled(false); } } //static bool KdenliveSettingsDialog::getBlackMagicDeviceList(KComboBox *devicelist, bool force) { if (!force && !KdenliveSettings::decklink_device_found()) { return false; } Mlt::Profile profile; Mlt::Producer bm(profile, "decklink"); int found_devices = 0; if (bm.is_valid()) { bm.set("list_devices", 1); found_devices = bm.get_int("devices"); } else { KdenliveSettings::setDecklink_device_found(false); } if (found_devices <= 0) { devicelist->setEnabled(false); return false; } KdenliveSettings::setDecklink_device_found(true); for (int i = 0; i < found_devices; ++i) { char *tmp = qstrdup(QStringLiteral("device.%1").arg(i).toUtf8().constData()); devicelist->addItem(bm.get(tmp)); delete[] tmp; } return true; } bool KdenliveSettingsDialog::getBlackMagicOutputDeviceList(KComboBox *devicelist, bool force) { if (!force && !KdenliveSettings::decklink_device_found()) { return false; } Mlt::Profile profile; Mlt::Consumer bm(profile, "decklink"); int found_devices = 0; if (bm.is_valid()) { bm.set("list_devices", 1); found_devices = bm.get_int("devices"); } else { KdenliveSettings::setDecklink_device_found(false); } if (found_devices <= 0) { devicelist->setEnabled(false); return false; } KdenliveSettings::setDecklink_device_found(true); for (int i = 0; i < found_devices; ++i) { char *tmp = qstrdup(QStringLiteral("device.%1").arg(i).toUtf8().constData()); devicelist->addItem(bm.get(tmp)); delete[] tmp; } devicelist->addItem(QStringLiteral("test")); return true; } void KdenliveSettingsDialog::setupJogshuttleBtns(const QString &device) { QList list; QList list1; list << m_configShuttle.shuttle1; list << m_configShuttle.shuttle2; list << m_configShuttle.shuttle3; list << m_configShuttle.shuttle4; list << m_configShuttle.shuttle5; list << m_configShuttle.shuttle6; list << m_configShuttle.shuttle7; list << m_configShuttle.shuttle8; list << m_configShuttle.shuttle9; list << m_configShuttle.shuttle10; list << m_configShuttle.shuttle11; list << m_configShuttle.shuttle12; list << m_configShuttle.shuttle13; list << m_configShuttle.shuttle14; list << m_configShuttle.shuttle15; list1 << m_configShuttle.label_2; // #1 list1 << m_configShuttle.label_4; // #2 list1 << m_configShuttle.label_3; // #3 list1 << m_configShuttle.label_7; // #4 list1 << m_configShuttle.label_5; // #5 list1 << m_configShuttle.label_6; // #6 list1 << m_configShuttle.label_8; // #7 list1 << m_configShuttle.label_9; // #8 list1 << m_configShuttle.label_10; // #9 list1 << m_configShuttle.label_11; // #10 list1 << m_configShuttle.label_12; // #11 list1 << m_configShuttle.label_13; // #12 list1 << m_configShuttle.label_14; // #13 list1 << m_configShuttle.label_15; // #14 list1 << m_configShuttle.label_16; // #15 for (int i = 0; i < list.count(); ++i) { list[i]->hide(); list1[i]->hide(); } #ifdef USE_JOGSHUTTLE if (!m_configShuttle.kcfg_enableshuttle->isChecked()) { return; } int keysCount = JogShuttle::keysCount(device); for (int i = 0; i < keysCount; ++i) { m_shuttle_buttons.push_back(list[i]); list[i]->show(); list1[i]->show(); } // populate the buttons with the current configuration. The items are sorted // according to the user-selected language, so they do not appear in random order. QMap mappable_actions(m_mappable_actions); QList action_names = mappable_actions.keys(); QList::Iterator iter = action_names.begin(); // qCDebug(KDENLIVE_LOG) << "::::::::::::::::"; while (iter != action_names.end()) { // qCDebug(KDENLIVE_LOG) << *iter; ++iter; } // qCDebug(KDENLIVE_LOG) << "::::::::::::::::"; qSort(action_names); iter = action_names.begin(); while (iter != action_names.end()) { // qCDebug(KDENLIVE_LOG) << *iter; ++iter; } // qCDebug(KDENLIVE_LOG) << "::::::::::::::::"; // Here we need to compute the action_id -> index-in-action_names. We iterate over the // action_names, as the sorting may depend on the user-language. QStringList actions_map = JogShuttleConfig::actionMap(KdenliveSettings::shuttlebuttons()); QMap action_pos; for (const QString &action_id : actions_map) { // This loop find out at what index is the string that would map to the action_id. for (int i = 0; i < action_names.size(); ++i) { if (mappable_actions[action_names.at(i)] == action_id) { action_pos[action_id] = i; break; } } } int i = 0; for (KComboBox *button : m_shuttle_buttons) { button->addItems(action_names); connect(button, SIGNAL(activated(int)), this, SLOT(slotShuttleModified())); ++i; if (i < actions_map.size()) { button->setCurrentIndex(action_pos[actions_map[i]]); } } #endif } KdenliveSettingsDialog::~KdenliveSettingsDialog() {} void KdenliveSettingsDialog::slotUpdateGrabRegionStatus() { m_configCapture.region_group->setHidden(m_configCapture.kcfg_grab_capture_type->currentIndex() != 1); } void KdenliveSettingsDialog::slotEnableCaptureFolder() { m_configEnv.capturefolderurl->setEnabled(!m_configEnv.kcfg_capturetoprojectfolder->isChecked()); } void KdenliveSettingsDialog::slotEnableLibraryFolder() { m_configEnv.libraryfolderurl->setEnabled(!m_configEnv.kcfg_librarytodefaultfolder->isChecked()); } void KdenliveSettingsDialog::initDevices() { // Fill audio drivers m_configSdl.kcfg_audio_driver->addItem(i18n("Automatic"), QString()); #ifndef Q_WS_MAC m_configSdl.kcfg_audio_driver->addItem(i18n("OSS"), "dsp"); m_configSdl.kcfg_audio_driver->addItem(i18n("ALSA"), "alsa"); m_configSdl.kcfg_audio_driver->addItem(i18n("PulseAudio"), "pulse"); m_configSdl.kcfg_audio_driver->addItem(i18n("OSS with DMA access"), "dma"); m_configSdl.kcfg_audio_driver->addItem(i18n("Esound daemon"), "esd"); m_configSdl.kcfg_audio_driver->addItem(i18n("ARTS daemon"), "artsc"); #endif if (!KdenliveSettings::audiodrivername().isEmpty()) for (int i = 1; i < m_configSdl.kcfg_audio_driver->count(); ++i) { if (m_configSdl.kcfg_audio_driver->itemData(i).toString() == KdenliveSettings::audiodrivername()) { m_configSdl.kcfg_audio_driver->setCurrentIndex(i); KdenliveSettings::setAudio_driver((uint)i); } } // Fill the list of audio playback / recording devices m_configSdl.kcfg_audio_device->addItem(i18n("Default"), QString()); m_configCapture.kcfg_v4l_alsadevice->addItem(i18n("Default"), "default"); if (!QStandardPaths::findExecutable(QStringLiteral("aplay")).isEmpty()) { m_readProcess.setOutputChannelMode(KProcess::OnlyStdoutChannel); m_readProcess.setProgram(QStringLiteral("aplay"), QStringList() << QStringLiteral("-l")); connect(&m_readProcess, &KProcess::readyReadStandardOutput, this, &KdenliveSettingsDialog::slotReadAudioDevices); m_readProcess.execute(5000); } else { // If aplay is not installed on the system, parse the /proc/asound/pcm file QFile file(QStringLiteral("/proc/asound/pcm")); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); QString line = stream.readLine(); QString deviceId; while (!line.isNull()) { if (line.contains(QStringLiteral("playback"))) { deviceId = line.section(QLatin1Char(':'), 0, 0); m_configSdl.kcfg_audio_device->addItem(line.section(QLatin1Char(':'), 1, 1), QStringLiteral("plughw:%1,%2") .arg(deviceId.section(QLatin1Char('-'), 0, 0).toInt()) .arg(deviceId.section(QLatin1Char('-'), 1, 1).toInt())); } if (line.contains(QStringLiteral("capture"))) { deviceId = line.section(QLatin1Char(':'), 0, 0); m_configCapture.kcfg_v4l_alsadevice->addItem( line.section(QLatin1Char(':'), 1, 1).simplified(), QStringLiteral("hw:%1,%2").arg(deviceId.section(QLatin1Char('-'), 0, 0).toInt()).arg(deviceId.section(QLatin1Char('-'), 1, 1).toInt())); } line = stream.readLine(); } file.close(); } else { qCDebug(KDENLIVE_LOG) << " / / / /CANNOT READ PCM"; } } // Add pulseaudio capture option m_configCapture.kcfg_v4l_alsadevice->addItem(i18n("PulseAudio"), "pulse"); if (!KdenliveSettings::audiodevicename().isEmpty()) { // Select correct alsa device int ix = m_configSdl.kcfg_audio_device->findData(KdenliveSettings::audiodevicename()); m_configSdl.kcfg_audio_device->setCurrentIndex(ix); KdenliveSettings::setAudio_device(ix); } if (!KdenliveSettings::v4l_alsadevicename().isEmpty()) { // Select correct alsa device int ix = m_configCapture.kcfg_v4l_alsadevice->findData(KdenliveSettings::v4l_alsadevicename()); m_configCapture.kcfg_v4l_alsadevice->setCurrentIndex(ix); KdenliveSettings::setV4l_alsadevice(ix); } m_configSdl.kcfg_audio_backend->addItem(i18n("SDL"), KdenliveSettings::sdlAudioBackend()); m_configSdl.kcfg_audio_backend->addItem(i18n("RtAudio"), "rtaudio"); if (!KdenliveSettings::audiobackend().isEmpty()) { int ix = m_configSdl.kcfg_audio_backend->findData(KdenliveSettings::audiobackend()); m_configSdl.kcfg_audio_backend->setCurrentIndex(ix); KdenliveSettings::setAudio_backend(ix); } m_configSdl.group_sdl->setEnabled(KdenliveSettings::audiobackend().startsWith(QLatin1String("sdl_audio"))); loadCurrentV4lProfileInfo(); } void KdenliveSettingsDialog::slotReadAudioDevices() { QString result = QString(m_readProcess.readAllStandardOutput()); // qCDebug(KDENLIVE_LOG) << "// / / / / / READING APLAY: "; // qCDebug(KDENLIVE_LOG) << result; const QStringList lines = result.split(QLatin1Char('\n')); for (const QString &devicestr : lines) { ////qCDebug(KDENLIVE_LOG) << "// READING LINE: " << data; if (!devicestr.startsWith(QLatin1Char(' ')) && devicestr.count(QLatin1Char(':')) > 1) { QString card = devicestr.section(QLatin1Char(':'), 0, 0).section(QLatin1Char(' '), -1); QString device = devicestr.section(QLatin1Char(':'), 1, 1).section(QLatin1Char(' '), -1); m_configSdl.kcfg_audio_device->addItem(devicestr.section(QLatin1Char(':'), -1).simplified(), QStringLiteral("plughw:%1,%2").arg(card).arg(device)); m_configCapture.kcfg_v4l_alsadevice->addItem(devicestr.section(QLatin1Char(':'), -1).simplified(), QStringLiteral("hw:%1,%2").arg(card).arg(device)); } } } void KdenliveSettingsDialog::showPage(int page, int option) { switch (page) { case 1: setCurrentPage(m_page1); break; case 2: setCurrentPage(m_page2); break; case 3: setCurrentPage(m_page3); break; case 4: setCurrentPage(m_page4); m_configCapture.tabWidget->setCurrentIndex(option); break; case 5: setCurrentPage(m_page5); break; case 6: setCurrentPage(m_page6); break; case 7: setCurrentPage(m_page7); break; default: setCurrentPage(m_page1); } } void KdenliveSettingsDialog::slotEditAudioApplication() { KService::Ptr service; QPointer dlg = new KOpenWithDialog(QList(), i18n("Select default audio editor"), m_configEnv.kcfg_defaultaudioapp->text(), this); if (dlg->exec() == QDialog::Accepted) { service = dlg->service(); m_configEnv.kcfg_defaultaudioapp->setText(KRun::binaryName(service->exec(), false)); } delete dlg; } void KdenliveSettingsDialog::slotEditImageApplication() { QPointer dlg = new KOpenWithDialog(QList(), i18n("Select default image editor"), m_configEnv.kcfg_defaultimageapp->text(), this); if (dlg->exec() == QDialog::Accepted) { KService::Ptr service = dlg->service(); m_configEnv.kcfg_defaultimageapp->setText(KRun::binaryName(service->exec(), false)); } delete dlg; } void KdenliveSettingsDialog::slotCheckShuttle(int state) { #ifdef USE_JOGSHUTTLE m_configShuttle.config_group->setEnabled(state != 0); m_configShuttle.shuttledevicelist->clear(); QStringList devNames = KdenliveSettings::shuttledevicenames(); QStringList devPaths = KdenliveSettings::shuttledevicepaths(); if (devNames.count() != devPaths.count()) { return; } for (int i = 0; i < devNames.count(); ++i) { m_configShuttle.shuttledevicelist->addItem(devNames.at(i), devPaths.at(i)); } if (state != 0) { setupJogshuttleBtns(m_configShuttle.shuttledevicelist->itemData(m_configShuttle.shuttledevicelist->currentIndex()).toString()); } #endif /* USE_JOGSHUTTLE */ } void KdenliveSettingsDialog::slotUpdateShuttleDevice(int ix) { #ifdef USE_JOGSHUTTLE QString device = m_configShuttle.shuttledevicelist->itemData(ix).toString(); // KdenliveSettings::setShuttledevice(device); setupJogshuttleBtns(device); m_configShuttle.kcfg_shuttledevice->setText(device); #endif /* USE_JOGSHUTTLE */ } void KdenliveSettingsDialog::updateWidgets() { // Revert widgets to last saved state (for example when user pressed "Cancel") // //qCDebug(KDENLIVE_LOG) << "// // // KCONFIG Revert called"; #ifdef USE_JOGSHUTTLE // revert jog shuttle device if (m_configShuttle.shuttledevicelist->count() > 0) { for (int i = 0; i < m_configShuttle.shuttledevicelist->count(); ++i) { if (m_configShuttle.shuttledevicelist->itemData(i) == KdenliveSettings::shuttledevice()) { m_configShuttle.shuttledevicelist->setCurrentIndex(i); break; } } } // Revert jog shuttle buttons QList action_names = m_mappable_actions.keys(); qSort(action_names); QStringList actions_map = JogShuttleConfig::actionMap(KdenliveSettings::shuttlebuttons()); QMap action_pos; for (const QString &action_id : actions_map) { // This loop find out at what index is the string that would map to the action_id. for (int i = 0; i < action_names.size(); ++i) { if (m_mappable_actions[action_names[i]] == action_id) { action_pos[action_id] = i; break; } } } int i = 0; for (KComboBox *button : m_shuttle_buttons) { ++i; if (i < actions_map.size()) { button->setCurrentIndex(action_pos[actions_map[i]]); } } #endif /* USE_JOGSHUTTLE */ } void KdenliveSettingsDialog::accept() { if (m_pw->selectedProfile().isEmpty()) { KMessageBox::error(this, i18n("Please select a video profile")); return; } KConfigDialog::accept(); } void KdenliveSettingsDialog::updateSettings() { // Save changes to settings (for example when user pressed "Apply" or "Ok") // //qCDebug(KDENLIVE_LOG) << "// // // KCONFIG UPDATE called"; if (m_pw->selectedProfile().isEmpty()) { KMessageBox::error(this, i18n("Please select a video profile")); return; } KdenliveSettings::setDefault_profile(m_pw->selectedProfile()); bool resetProfile = false; bool resetConsumer = false; bool fullReset = false; bool updateCapturePath = false; bool updateLibrary = false; /*if (m_configShuttle.shuttledevicelist->count() > 0) { QString device = m_configShuttle.shuttledevicelist->itemData(m_configShuttle.shuttledevicelist->currentIndex()).toString(); if (device != KdenliveSettings::shuttledevice()) KdenliveSettings::setShuttledevice(device); }*/ // Capture default folder if (m_configEnv.kcfg_capturetoprojectfolder->isChecked() != KdenliveSettings::capturetoprojectfolder()) { KdenliveSettings::setCapturetoprojectfolder(m_configEnv.kcfg_capturetoprojectfolder->isChecked()); updateCapturePath = true; } if (m_configProject.projecturl->url().toLocalFile() != KdenliveSettings::defaultprojectfolder()) { KdenliveSettings::setDefaultprojectfolder(m_configProject.projecturl->url().toLocalFile()); } if (m_configEnv.capturefolderurl->url().toLocalFile() != KdenliveSettings::capturefolder()) { KdenliveSettings::setCapturefolder(m_configEnv.capturefolderurl->url().toLocalFile()); updateCapturePath = true; } // Library default folder if (m_configEnv.kcfg_librarytodefaultfolder->isChecked() != KdenliveSettings::librarytodefaultfolder()) { KdenliveSettings::setLibrarytodefaultfolder(m_configEnv.kcfg_librarytodefaultfolder->isChecked()); updateLibrary = true; } if (m_configEnv.libraryfolderurl->url().toLocalFile() != KdenliveSettings::libraryfolder()) { KdenliveSettings::setLibraryfolder(m_configEnv.libraryfolderurl->url().toLocalFile()); if (!KdenliveSettings::librarytodefaultfolder()) { updateLibrary = true; } } if (m_configCapture.kcfg_v4l_format->currentIndex() != (int)KdenliveSettings::v4l_format()) { saveCurrentV4lProfile(); KdenliveSettings::setV4l_format(0); } // Check if screengrab is fullscreen if (m_configCapture.kcfg_grab_capture_type->currentIndex() != KdenliveSettings::grab_capture_type()) { KdenliveSettings::setGrab_capture_type(m_configCapture.kcfg_grab_capture_type->currentIndex()); emit updateFullScreenGrab(); } // Check encoding profiles // FFmpeg QString profilestr = m_configCapture.kcfg_v4l_profile->itemData(m_configCapture.kcfg_v4l_profile->currentIndex()).toString(); if (!profilestr.isEmpty() && (profilestr.section(QLatin1Char(';'), 0, 0) != KdenliveSettings::v4l_parameters() || profilestr.section(QLatin1Char(';'), 1, 1) != KdenliveSettings::v4l_extension())) { KdenliveSettings::setV4l_parameters(profilestr.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setV4l_extension(profilestr.section(QLatin1Char(';'), 1, 1)); } // screengrab profilestr = m_configCapture.kcfg_grab_profile->itemData(m_configCapture.kcfg_grab_profile->currentIndex()).toString(); if (!profilestr.isEmpty() && (profilestr.section(QLatin1Char(';'), 0, 0) != KdenliveSettings::grab_parameters() || profilestr.section(QLatin1Char(';'), 1, 1) != KdenliveSettings::grab_extension())) { KdenliveSettings::setGrab_parameters(profilestr.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setGrab_extension(profilestr.section(QLatin1Char(';'), 1, 1)); } // decklink profilestr = m_configCapture.kcfg_decklink_profile->itemData(m_configCapture.kcfg_decklink_profile->currentIndex()).toString(); if (!profilestr.isEmpty() && (profilestr.section(QLatin1Char(';'), 0, 0) != KdenliveSettings::decklink_parameters() || profilestr.section(QLatin1Char(';'), 1, 1) != KdenliveSettings::decklink_extension())) { KdenliveSettings::setDecklink_parameters(profilestr.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setDecklink_extension(profilestr.section(QLatin1Char(';'), 1, 1)); } // proxies profilestr = m_configProject.kcfg_proxy_profile->itemData(m_configProject.kcfg_proxy_profile->currentIndex()).toString(); if (!profilestr.isEmpty() && (profilestr.section(QLatin1Char(';'), 0, 0) != KdenliveSettings::proxyparams() || profilestr.section(QLatin1Char(';'), 1, 1) != KdenliveSettings::proxyextension())) { KdenliveSettings::setProxyparams(profilestr.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setProxyextension(profilestr.section(QLatin1Char(';'), 1, 1)); } // timeline preview profilestr = m_configProject.kcfg_preview_profile->itemData(m_configProject.kcfg_preview_profile->currentIndex()).toString(); if (!profilestr.isEmpty() && (profilestr.section(QLatin1Char(';'), 0, 0) != KdenliveSettings::previewparams() || profilestr.section(QLatin1Char(';'), 1, 1) != KdenliveSettings::previewextension())) { KdenliveSettings::setPreviewparams(profilestr.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setPreviewextension(profilestr.section(QLatin1Char(';'), 1, 1)); } if (updateCapturePath) { emit updateCaptureFolder(); } if (updateLibrary) { emit updateLibraryFolder(); } QString value = m_configCapture.kcfg_v4l_alsadevice->itemData(m_configCapture.kcfg_v4l_alsadevice->currentIndex()).toString(); if (value != KdenliveSettings::v4l_alsadevicename()) { KdenliveSettings::setV4l_alsadevicename(value); } if (m_configSdl.kcfg_external_display->isChecked() != KdenliveSettings::external_display()) { KdenliveSettings::setExternal_display(m_configSdl.kcfg_external_display->isChecked()); resetConsumer = true; fullReset = true; } else if (KdenliveSettings::external_display() && KdenliveSettings::blackmagic_output_device() != m_configSdl.kcfg_blackmagic_output_device->currentIndex()) { resetConsumer = true; fullReset = true; } value = m_configSdl.kcfg_audio_driver->itemData(m_configSdl.kcfg_audio_driver->currentIndex()).toString(); if (value != KdenliveSettings::audiodrivername()) { KdenliveSettings::setAudiodrivername(value); resetConsumer = true; } if (value == QLatin1String("alsa")) { // Audio device setting is only valid for alsa driver value = m_configSdl.kcfg_audio_device->itemData(m_configSdl.kcfg_audio_device->currentIndex()).toString(); if (value != KdenliveSettings::audiodevicename()) { KdenliveSettings::setAudiodevicename(value); resetConsumer = true; } } else if (!KdenliveSettings::audiodevicename().isEmpty()) { KdenliveSettings::setAudiodevicename(QString()); resetConsumer = true; } value = m_configSdl.kcfg_audio_backend->itemData(m_configSdl.kcfg_audio_backend->currentIndex()).toString(); if (value != KdenliveSettings::audiobackend()) { KdenliveSettings::setAudiobackend(value); resetConsumer = true; fullReset = true; } if (m_configSdl.kcfg_window_background->color() != KdenliveSettings::window_background()) { KdenliveSettings::setWindow_background(m_configSdl.kcfg_window_background->color()); resetProfile = true; } if (m_configSdl.kcfg_volume->value() != KdenliveSettings::volume()) { KdenliveSettings::setVolume(m_configSdl.kcfg_volume->value()); resetConsumer = true; } if (m_configMisc.kcfg_tabposition->currentIndex() != KdenliveSettings::tabposition()) { KdenliveSettings::setTabposition(m_configMisc.kcfg_tabposition->currentIndex()); } if (m_configTimeline.kcfg_displayallchannels->isChecked() != KdenliveSettings::displayallchannels()) { KdenliveSettings::setDisplayallchannels(m_configTimeline.kcfg_displayallchannels->isChecked()); emit audioThumbFormatChanged(); } if (m_modified) { // The transcoding profiles were modified, save. m_modified = false; saveTranscodeProfiles(); } #ifdef USE_JOGSHUTTLE m_shuttleModified = false; QStringList actions; actions << QStringLiteral("monitor_pause"); // the Job rest position action. for (KComboBox *button : m_shuttle_buttons) { actions << m_mappable_actions[button->currentText()]; } QString maps = JogShuttleConfig::actionMap(actions); // fprintf(stderr, "Shuttle config: %s\n", JogShuttleConfig::actionMap(actions).toLatin1().constData()); if (KdenliveSettings::shuttlebuttons() != maps) { KdenliveSettings::setShuttlebuttons(maps); } #endif bool restart = false; if (m_configSdl.kcfg_gpu_accel->isChecked() != KdenliveSettings::gpu_accel()) { // GPU setting was changed, we need to restart Kdenlive or everything will be corrupted if (KMessageBox::warningContinueCancel(this, i18n("Kdenlive must be restarted to change this setting")) == KMessageBox::Continue) { restart = true; } else { m_configSdl.kcfg_gpu_accel->setChecked(KdenliveSettings::gpu_accel()); } } if (m_configTimeline.kcfg_trackheight->value() != KdenliveSettings::trackheight()) { KdenliveSettings::setTrackheight(m_configTimeline.kcfg_trackheight->value()); emit resetView(); } // Mimes if (m_configEnv.kcfg_addedExtensions->text() != KdenliveSettings::addedExtensions()) { // Update list KdenliveSettings::setAddedExtensions(m_configEnv.kcfg_addedExtensions->text()); QStringList mimes = ClipCreationDialog::getExtensions(); qSort(mimes); m_configEnv.supportedmimes->setPlainText(mimes.join(QLatin1Char(' '))); } KConfigDialog::settingsChangedSlot(); // KConfigDialog::updateSettings(); if (resetConsumer) { emit doResetConsumer(fullReset); } if (resetProfile) { emit doResetProfile(); } if (restart) { emit restartKdenlive(); } emit checkTabPosition(); } void KdenliveSettingsDialog::slotCheckAlsaDriver() { QString value = m_configSdl.kcfg_audio_driver->itemData(m_configSdl.kcfg_audio_driver->currentIndex()).toString(); m_configSdl.kcfg_audio_device->setEnabled(value == QLatin1String("alsa")); } void KdenliveSettingsDialog::slotCheckAudioBackend() { QString value = m_configSdl.kcfg_audio_backend->itemData(m_configSdl.kcfg_audio_backend->currentIndex()).toString(); m_configSdl.group_sdl->setEnabled(value.startsWith(QLatin1String("sdl_audio"))); } void KdenliveSettingsDialog::loadTranscodeProfiles() { KSharedConfigPtr config = KSharedConfig::openConfig(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kdenlivetranscodingrc")), KConfig::CascadeConfig); KConfigGroup transConfig(config, "Transcoding"); // read the entries m_configTranscode.profiles_list->blockSignals(true); m_configTranscode.profiles_list->clear(); QMap profiles = transConfig.entryMap(); QMapIterator i(profiles); while (i.hasNext()) { i.next(); auto *item = new QListWidgetItem(i.key()); QString profilestr = i.value(); if (profilestr.contains(QLatin1Char(';'))) { item->setToolTip(profilestr.section(QLatin1Char(';'), 1, 1)); } item->setData(Qt::UserRole, profilestr); m_configTranscode.profiles_list->addItem(item); // item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable); } m_configTranscode.profiles_list->blockSignals(false); m_configTranscode.profiles_list->setCurrentRow(0); } void KdenliveSettingsDialog::saveTranscodeProfiles() { QString transcodeFile = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/kdenlivetranscodingrc"); KSharedConfigPtr config = KSharedConfig::openConfig(transcodeFile); KConfigGroup transConfig(config, "Transcoding"); // read the entries transConfig.deleteGroup(); int max = m_configTranscode.profiles_list->count(); for (int i = 0; i < max; ++i) { QListWidgetItem *item = m_configTranscode.profiles_list->item(i); transConfig.writeEntry(item->text(), item->data(Qt::UserRole).toString()); } config->sync(); } void KdenliveSettingsDialog::slotAddTranscode() { if (!m_configTranscode.profiles_list->findItems(m_configTranscode.profile_name->text(), Qt::MatchExactly).isEmpty()) { KMessageBox::sorry(this, i18n("A profile with that name already exists")); return; } QListWidgetItem *item = new QListWidgetItem(m_configTranscode.profile_name->text()); QString profilestr = m_configTranscode.profile_parameters->toPlainText(); profilestr.append(" %1." + m_configTranscode.profile_extension->text()); profilestr.append(';'); if (!m_configTranscode.profile_description->text().isEmpty()) { profilestr.append(m_configTranscode.profile_description->text()); } if (m_configTranscode.profile_audioonly->isChecked()) { profilestr.append(";audio"); } item->setData(Qt::UserRole, profilestr); m_configTranscode.profiles_list->addItem(item); m_configTranscode.profiles_list->setCurrentItem(item); slotDialogModified(); } void KdenliveSettingsDialog::slotUpdateTranscodingProfile() { QListWidgetItem *item = m_configTranscode.profiles_list->currentItem(); if (!item) { return; } m_configTranscode.button_update->setEnabled(false); item->setText(m_configTranscode.profile_name->text()); QString profilestr = m_configTranscode.profile_parameters->toPlainText(); profilestr.append(" %1." + m_configTranscode.profile_extension->text()); profilestr.append(';'); if (!m_configTranscode.profile_description->text().isEmpty()) { profilestr.append(m_configTranscode.profile_description->text()); } if (m_configTranscode.profile_audioonly->isChecked()) { profilestr.append(QStringLiteral(";audio")); } item->setData(Qt::UserRole, profilestr); slotDialogModified(); } void KdenliveSettingsDialog::slotDeleteTranscode() { QListWidgetItem *item = m_configTranscode.profiles_list->currentItem(); if (item == nullptr) { return; } delete item; slotDialogModified(); } void KdenliveSettingsDialog::slotEnableTranscodeUpdate() { if (!m_configTranscode.profile_box->isEnabled()) { return; } bool allow = true; if (m_configTranscode.profile_name->text().isEmpty() || m_configTranscode.profile_extension->text().isEmpty()) { allow = false; } m_configTranscode.button_update->setEnabled(allow); } void KdenliveSettingsDialog::slotSetTranscodeProfile() { m_configTranscode.profile_box->setEnabled(false); m_configTranscode.button_update->setEnabled(false); m_configTranscode.profile_name->clear(); m_configTranscode.profile_description->clear(); m_configTranscode.profile_extension->clear(); m_configTranscode.profile_parameters->clear(); m_configTranscode.profile_audioonly->setChecked(false); QListWidgetItem *item = m_configTranscode.profiles_list->currentItem(); if (!item) { return; } m_configTranscode.profile_name->setText(item->text()); QString profilestr = item->data(Qt::UserRole).toString(); if (profilestr.contains(QLatin1Char(';'))) { m_configTranscode.profile_description->setText(profilestr.section(QLatin1Char(';'), 1, 1)); if (profilestr.section(QLatin1Char(';'), 2, 2) == QLatin1String("audio")) { m_configTranscode.profile_audioonly->setChecked(true); } profilestr = profilestr.section(QLatin1Char(';'), 0, 0).simplified(); } m_configTranscode.profile_extension->setText(profilestr.section(QLatin1Char('.'), -1)); m_configTranscode.profile_parameters->setPlainText(profilestr.section(QLatin1Char(' '), 0, -2)); m_configTranscode.profile_box->setEnabled(true); } void KdenliveSettingsDialog::slotShuttleModified() { #ifdef USE_JOGSHUTTLE QStringList actions; actions << QStringLiteral("monitor_pause"); // the Job rest position action. for (KComboBox *button : m_shuttle_buttons) { actions << m_mappable_actions[button->currentText()]; } QString maps = JogShuttleConfig::actionMap(actions); m_shuttleModified = KdenliveSettings::shuttlebuttons() != maps; #endif KConfigDialog::updateButtons(); } void KdenliveSettingsDialog::slotDialogModified() { m_modified = true; KConfigDialog::updateButtons(); } // virtual bool KdenliveSettingsDialog::hasChanged() { if (m_modified || m_shuttleModified) { return true; } return KConfigDialog::hasChanged(); } void KdenliveSettingsDialog::slotUpdatev4lDevice() { QString device = m_configCapture.kcfg_detectedv4ldevices->itemData(m_configCapture.kcfg_detectedv4ldevices->currentIndex()).toString(); if (!device.isEmpty()) { m_configCapture.kcfg_video4vdevice->setText(device); } QString info = m_configCapture.kcfg_detectedv4ldevices->itemData(m_configCapture.kcfg_detectedv4ldevices->currentIndex(), Qt::UserRole + 1).toString(); m_configCapture.kcfg_v4l_format->blockSignals(true); m_configCapture.kcfg_v4l_format->clear(); QString vl4ProfilePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/video4linux"); if (QFile::exists(vl4ProfilePath)) { m_configCapture.kcfg_v4l_format->addItem(i18n("Current settings")); } QStringList pixelformats = info.split('>', QString::SkipEmptyParts); QString itemSize; QString pixelFormat; QStringList itemRates; for (int i = 0; i < pixelformats.count(); ++i) { QString format = pixelformats.at(i).section(QLatin1Char(':'), 0, 0); QStringList sizes = pixelformats.at(i).split(':', QString::SkipEmptyParts); pixelFormat = sizes.takeFirst(); for (int j = 0; j < sizes.count(); ++j) { itemSize = sizes.at(j).section(QLatin1Char('='), 0, 0); itemRates = sizes.at(j).section(QLatin1Char('='), 1, 1).split(QLatin1Char(','), QString::SkipEmptyParts); for (int k = 0; k < itemRates.count(); ++k) { m_configCapture.kcfg_v4l_format->addItem( QLatin1Char('[') + format + QStringLiteral("] ") + itemSize + QStringLiteral(" (") + itemRates.at(k) + QLatin1Char(')'), QStringList() << format << itemSize.section('x', 0, 0) << itemSize.section('x', 1, 1) << itemRates.at(k).section(QLatin1Char('/'), 0, 0) << itemRates.at(k).section(QLatin1Char('/'), 1, 1)); } } } m_configCapture.kcfg_v4l_format->blockSignals(false); slotUpdatev4lCaptureProfile(); } void KdenliveSettingsDialog::slotUpdatev4lCaptureProfile() { QStringList info = m_configCapture.kcfg_v4l_format->itemData(m_configCapture.kcfg_v4l_format->currentIndex(), Qt::UserRole).toStringList(); if (info.isEmpty()) { // No auto info, display the current ones loadCurrentV4lProfileInfo(); return; } m_configCapture.p_size->setText(info.at(1) + QLatin1Char('x') + info.at(2)); m_configCapture.p_fps->setText(info.at(3) + QLatin1Char('/') + info.at(4)); m_configCapture.p_aspect->setText(QStringLiteral("1/1")); m_configCapture.p_display->setText(info.at(1) + QLatin1Char('/') + info.at(2)); m_configCapture.p_colorspace->setText(ProfileRepository::getColorspaceDescription(601)); m_configCapture.p_progressive->setText(i18n("Progressive")); QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/")); if (!dir.exists() || !dir.exists(QStringLiteral("video4linux"))) { saveCurrentV4lProfile(); } } void KdenliveSettingsDialog::loadCurrentV4lProfileInfo() { QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/")); if (!dir.exists()) { dir.mkpath(QStringLiteral(".")); } if (!ProfileRepository::get()->profileExists(dir.absoluteFilePath(QStringLiteral("video4linux")))) { // No default formats found, build one std::unique_ptr prof(new ProfileParam(pCore->getCurrentProfile().get())); prof->m_width = 320; prof->m_height = 200; prof->m_frame_rate_num = 15; prof->m_frame_rate_den = 1; prof->m_display_aspect_num = 4; prof->m_display_aspect_den = 3; prof->m_sample_aspect_num = 1; prof->m_sample_aspect_den = 1; prof->m_progressive = 1; prof->m_colorspace = 601; ProfileRepository::get()->saveProfile(prof.get(), dir.absoluteFilePath(QStringLiteral("video4linux"))); } auto &prof = ProfileRepository::get()->getProfile(dir.absoluteFilePath(QStringLiteral("video4linux"))); m_configCapture.p_size->setText(QString::number(prof->width()) + QLatin1Char('x') + QString::number(prof->height())); m_configCapture.p_fps->setText(QString::number(prof->frame_rate_num()) + QLatin1Char('/') + QString::number(prof->frame_rate_den())); m_configCapture.p_aspect->setText(QString::number(prof->sample_aspect_num()) + QLatin1Char('/') + QString::number(prof->sample_aspect_den())); m_configCapture.p_display->setText(QString::number(prof->display_aspect_num()) + QLatin1Char('/') + QString::number(prof->display_aspect_den())); m_configCapture.p_colorspace->setText(ProfileRepository::getColorspaceDescription(prof->colorspace())); if (prof->progressive()) { m_configCapture.p_progressive->setText(i18n("Progressive")); } } void KdenliveSettingsDialog::saveCurrentV4lProfile() { std::unique_ptr profile(new ProfileParam(pCore->getCurrentProfile().get())); profile->m_description = QStringLiteral("Video4Linux capture"); profile->m_colorspace = ProfileRepository::getColorspaceFromDescription(m_configCapture.p_colorspace->text()); profile->m_width = m_configCapture.p_size->text().section('x', 0, 0).toInt(); profile->m_height = m_configCapture.p_size->text().section('x', 1, 1).toInt(); profile->m_sample_aspect_num = m_configCapture.p_aspect->text().section(QLatin1Char('/'), 0, 0).toInt(); profile->m_sample_aspect_den = m_configCapture.p_aspect->text().section(QLatin1Char('/'), 1, 1).toInt(); profile->m_display_aspect_num = m_configCapture.p_display->text().section(QLatin1Char('/'), 0, 0).toInt(); profile->m_display_aspect_den = m_configCapture.p_display->text().section(QLatin1Char('/'), 1, 1).toInt(); profile->m_frame_rate_num = m_configCapture.p_fps->text().section(QLatin1Char('/'), 0, 0).toInt(); profile->m_frame_rate_den = m_configCapture.p_fps->text().section(QLatin1Char('/'), 1, 1).toInt(); profile->m_progressive = m_configCapture.p_progressive->text() == i18n("Progressive"); QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/")); if (!dir.exists()) { dir.mkpath(QStringLiteral(".")); } ProfileRepository::get()->saveProfile(profile.get(), dir.absoluteFilePath(QStringLiteral("video4linux"))); } void KdenliveSettingsDialog::slotManageEncodingProfile() { QAction *act = qobject_cast(sender()); int type = 0; if (act) { type = act->data().toInt(); } QPointer dia = new EncodingProfilesDialog(type); dia->exec(); delete dia; loadEncodingProfiles(); } void KdenliveSettingsDialog::loadEncodingProfiles() { KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation); // Load v4l profiles m_configCapture.kcfg_v4l_profile->blockSignals(true); QString currentItem = m_configCapture.kcfg_v4l_profile->currentText(); m_configCapture.kcfg_v4l_profile->clear(); KConfigGroup group(&conf, "video4linux"); QMap values = group.entryMap(); QMapIterator i(values); while (i.hasNext()) { i.next(); if (!i.key().isEmpty()) { m_configCapture.kcfg_v4l_profile->addItem(i.key(), i.value()); } } m_configCapture.kcfg_v4l_profile->blockSignals(false); if (!currentItem.isEmpty()) { m_configCapture.kcfg_v4l_profile->setCurrentIndex(m_configCapture.kcfg_v4l_profile->findText(currentItem)); } // Load Screen Grab profiles m_configCapture.kcfg_grab_profile->blockSignals(true); currentItem = m_configCapture.kcfg_grab_profile->currentText(); m_configCapture.kcfg_grab_profile->clear(); KConfigGroup group2(&conf, "screengrab"); values = group2.entryMap(); QMapIterator j(values); while (j.hasNext()) { j.next(); if (!j.key().isEmpty()) { m_configCapture.kcfg_grab_profile->addItem(j.key(), j.value()); } } m_configCapture.kcfg_grab_profile->blockSignals(false); if (!currentItem.isEmpty()) { m_configCapture.kcfg_grab_profile->setCurrentIndex(m_configCapture.kcfg_grab_profile->findText(currentItem)); } // Load Decklink profiles m_configCapture.kcfg_decklink_profile->blockSignals(true); currentItem = m_configCapture.kcfg_decklink_profile->currentText(); m_configCapture.kcfg_decklink_profile->clear(); KConfigGroup group3(&conf, "decklink"); values = group3.entryMap(); QMapIterator k(values); while (k.hasNext()) { k.next(); if (!k.key().isEmpty()) { m_configCapture.kcfg_decklink_profile->addItem(k.key(), k.value()); } } m_configCapture.kcfg_decklink_profile->blockSignals(false); if (!currentItem.isEmpty()) { m_configCapture.kcfg_decklink_profile->setCurrentIndex(m_configCapture.kcfg_decklink_profile->findText(currentItem)); } // Load Timeline Preview profiles m_configProject.kcfg_preview_profile->blockSignals(true); currentItem = m_configProject.kcfg_preview_profile->currentText(); m_configProject.kcfg_preview_profile->clear(); KConfigGroup group5(&conf, "timelinepreview"); values = group5.entryMap(); m_configProject.kcfg_preview_profile->addItem(i18n("Automatic")); QMapIterator l(values); while (l.hasNext()) { l.next(); if (!l.key().isEmpty()) { m_configProject.kcfg_preview_profile->addItem(l.key(), l.value()); } } if (!currentItem.isEmpty()) { m_configProject.kcfg_preview_profile->setCurrentIndex(m_configProject.kcfg_preview_profile->findText(currentItem)); } m_configProject.kcfg_preview_profile->blockSignals(false); QString profilestr = m_configProject.kcfg_preview_profile->itemData(m_configProject.kcfg_preview_profile->currentIndex()).toString(); if (profilestr.isEmpty()) { m_configProject.previewparams->clear(); } else { m_configProject.previewparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); } // Load Proxy profiles m_configProject.kcfg_proxy_profile->blockSignals(true); currentItem = m_configProject.kcfg_proxy_profile->currentText(); m_configProject.kcfg_proxy_profile->clear(); KConfigGroup group4(&conf, "proxy"); values = group4.entryMap(); m_configProject.kcfg_proxy_profile->addItem(i18n("Automatic")); QMapIterator m(values); while (m.hasNext()) { m.next(); if (!m.key().isEmpty()) { m_configProject.kcfg_proxy_profile->addItem(m.key(), m.value()); } } if (!currentItem.isEmpty()) { m_configProject.kcfg_proxy_profile->setCurrentIndex(m_configProject.kcfg_proxy_profile->findText(currentItem)); } m_configProject.kcfg_proxy_profile->blockSignals(false); profilestr = m_configProject.kcfg_proxy_profile->itemData(m_configProject.kcfg_proxy_profile->currentIndex()).toString(); if (profilestr.isEmpty()) { m_configProject.proxyparams->clear(); } else { m_configProject.proxyparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); } } void KdenliveSettingsDialog::slotUpdateDecklinkProfile(int ix) { if (ix == -1) { ix = KdenliveSettings::decklink_profile(); } else { ix = m_configCapture.kcfg_decklink_profile->currentIndex(); } QString profilestr = m_configCapture.kcfg_decklink_profile->itemData(ix).toString(); if (profilestr.isEmpty()) { return; } m_configCapture.decklink_parameters->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); // } void KdenliveSettingsDialog::slotUpdateV4lProfile(int ix) { if (ix == -1) { ix = KdenliveSettings::v4l_profile(); } else { ix = m_configCapture.kcfg_v4l_profile->currentIndex(); } QString profilestr = m_configCapture.kcfg_v4l_profile->itemData(ix).toString(); if (profilestr.isEmpty()) { return; } m_configCapture.v4l_parameters->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); // } void KdenliveSettingsDialog::slotUpdateGrabProfile(int ix) { if (ix == -1) { ix = KdenliveSettings::grab_profile(); } else { ix = m_configCapture.kcfg_grab_profile->currentIndex(); } QString profilestr = m_configCapture.kcfg_grab_profile->itemData(ix).toString(); if (profilestr.isEmpty()) { return; } m_configCapture.grab_parameters->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); // } void KdenliveSettingsDialog::slotUpdateProxyProfile(int ix) { if (ix == -1) { ix = KdenliveSettings::proxy_profile(); } else { ix = m_configProject.kcfg_proxy_profile->currentIndex(); } QString profilestr = m_configProject.kcfg_proxy_profile->itemData(ix).toString(); if (profilestr.isEmpty()) { return; } m_configProject.proxyparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); } void KdenliveSettingsDialog::slotUpdatePreviewProfile(int ix) { if (ix == -1) { ix = KdenliveSettings::preview_profile(); } else { ix = m_configProject.kcfg_preview_profile->currentIndex(); } QString profilestr = m_configProject.kcfg_preview_profile->itemData(ix).toString(); if (profilestr.isEmpty()) { return; } m_configProject.previewparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); } void KdenliveSettingsDialog::slotEditVideo4LinuxProfile() { QString vl4ProfilePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/video4linux"); QPointer w = new ProfilesDialog(vl4ProfilePath, true); if (w->exec() == QDialog::Accepted) { // save and update profile loadCurrentV4lProfileInfo(); } delete w; } void KdenliveSettingsDialog::slotReloadBlackMagic() { getBlackMagicDeviceList(m_configCapture.kcfg_decklink_capturedevice, true); if (!getBlackMagicOutputDeviceList(m_configSdl.kcfg_blackmagic_output_device, true)) { // No blackmagic card found m_configSdl.kcfg_external_display->setEnabled(false); } m_configSdl.kcfg_external_display->setEnabled(KdenliveSettings::decklink_device_found()); } void KdenliveSettingsDialog::checkProfile() { m_pw->loadProfile(KdenliveSettings::default_profile().isEmpty() ? pCore->getCurrentProfile()->path() : KdenliveSettings::default_profile()); } void KdenliveSettingsDialog::slotReloadShuttleDevices() { #ifdef USE_JOGSHUTTLE QString devDirStr = QStringLiteral("/dev/input/by-id"); QDir devDir(devDirStr); if (!devDir.exists()) { devDirStr = QStringLiteral("/dev/input"); } QStringList devNamesList; QStringList devPathList; m_configShuttle.shuttledevicelist->clear(); DeviceMap devMap = JogShuttle::enumerateDevices(devDirStr); DeviceMapIter iter = devMap.begin(); while (iter != devMap.end()) { m_configShuttle.shuttledevicelist->addItem(iter.key(), iter.value()); devNamesList << iter.key(); devPathList << iter.value(); ++iter; } KdenliveSettings::setShuttledevicenames(devNamesList); KdenliveSettings::setShuttledevicepaths(devPathList); QTimer::singleShot(200, this, SLOT(slotUpdateShuttleDevice())); #endif // USE_JOGSHUTTLE } diff --git a/src/titler/titledocument.cpp b/src/titler/titledocument.cpp index 240b53a90..d7d943868 100644 --- a/src/titler/titledocument.cpp +++ b/src/titler/titledocument.cpp @@ -1,728 +1,743 @@ /*************************************************************************** titledocument.h - description ------------------- begin : Feb 28 2008 copyright : (C) 2008 by Marco Gittler email : g.marco@freenet.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "titledocument.h" #include "gradientwidget.h" #include "graphicsscenerectmove.h" #include "kdenlivesettings.h" #include "timecode.h" #include #include #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_MAC #include #endif #include #include #include #include QByteArray fileToByteArray(const QString &filename) { QByteArray ret; QFile file(filename); if (file.open(QIODevice::ReadOnly)) { while (!file.atEnd()) { ret.append(file.readLine()); } } return ret; } TitleDocument::TitleDocument() { m_scene = nullptr; m_width = 0; m_height = 0; + m_missingElements = 0; } void TitleDocument::setScene(QGraphicsScene *_scene, int width, int height) { m_scene = _scene; m_width = width; m_height = height; } int TitleDocument::base64ToUrl(QGraphicsItem *item, QDomElement &content, bool embed) { if (embed) { if (!item->data(Qt::UserRole + 1).toString().isEmpty()) { content.setAttribute(QStringLiteral("base64"), item->data(Qt::UserRole + 1).toString()); } else if (!item->data(Qt::UserRole).toString().isEmpty()) { content.setAttribute(QStringLiteral("base64"), fileToByteArray(item->data(Qt::UserRole).toString()).toBase64().data()); } content.removeAttribute(QStringLiteral("url")); } else { // save for project files to disk QString base64 = item->data(Qt::UserRole + 1).toString(); if (!base64.isEmpty()) { QString titlePath; if (!m_projectPath.isEmpty()) { titlePath = m_projectPath; } else { titlePath = QStringLiteral("/tmp/titles"); } QString filename = extractBase64Image(titlePath, base64); if (!filename.isEmpty()) { content.setAttribute(QStringLiteral("url"), filename); content.removeAttribute(QStringLiteral("base64")); } } else { return 1; } } return 0; } // static const QString TitleDocument::extractBase64Image(const QString &titlePath, const QString &data) { QString filename = titlePath + QString(QCryptographicHash::hash(data.toLatin1(), QCryptographicHash::Md5).toHex().append(".titlepart")); QDir dir; dir.mkpath(titlePath); QFile f(filename); if (f.open(QIODevice::WriteOnly)) { f.write(QByteArray::fromBase64(data.toLatin1())); f.close(); return filename; } return QString(); } QDomDocument TitleDocument::xml(QGraphicsRectItem *startv, QGraphicsRectItem *endv, bool embed) { QDomDocument doc; QDomElement main = doc.createElement(QStringLiteral("kdenlivetitle")); main.setAttribute(QStringLiteral("width"), m_width); main.setAttribute(QStringLiteral("height"), m_height); // Save locale #ifndef Q_OS_MAC const char *locale = setlocale(LC_NUMERIC, nullptr); #else const char *locale = setlocale(LC_NUMERIC_MASK, nullptr); #endif main.setAttribute(QStringLiteral("LC_NUMERIC"), locale); doc.appendChild(main); QTextCursor cur; QTextBlockFormat format; for (QGraphicsItem *item : m_scene->items()) { if (!(item->flags() & QGraphicsItem::ItemIsSelectable)) { continue; } QDomElement e = doc.createElement(QStringLiteral("item")); QDomElement content = doc.createElement(QStringLiteral("content")); QFont font; QString gradient; MyTextItem *t; double xPosition = item->pos().x(); switch (item->type()) { case 7: e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsPixmapItem")); content.setAttribute(QStringLiteral("url"), item->data(Qt::UserRole).toString()); base64ToUrl(item, content, embed); break; case 13: e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsSvgItem")); content.setAttribute(QStringLiteral("url"), item->data(Qt::UserRole).toString()); base64ToUrl(item, content, embed); break; case 3: e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsRectItem")); content.setAttribute(QStringLiteral("rect"), rectFToString(static_cast(item)->rect().normalized())); content.setAttribute(QStringLiteral("pencolor"), colorToString(static_cast(item)->pen().color())); if (static_cast(item)->pen() == Qt::NoPen) { content.setAttribute(QStringLiteral("penwidth"), 0); } else { content.setAttribute(QStringLiteral("penwidth"), static_cast(item)->pen().width()); } content.setAttribute(QStringLiteral("brushcolor"), colorToString(static_cast(item)->brush().color())); gradient = item->data(TitleDocument::Gradient).toString(); if (!gradient.isEmpty()) { content.setAttribute(QStringLiteral("gradient"), gradient); } break; case 8: e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsTextItem")); t = static_cast(item); // Don't save empty text nodes if (t->toPlainText().simplified().isEmpty()) { continue; } // content.appendChild(doc.createTextNode(((QGraphicsTextItem*)item)->toHtml())); content.appendChild(doc.createTextNode(t->toPlainText())); font = t->font(); content.setAttribute(QStringLiteral("font"), font.family()); content.setAttribute(QStringLiteral("font-weight"), font.weight()); content.setAttribute(QStringLiteral("font-pixel-size"), font.pixelSize()); content.setAttribute(QStringLiteral("font-italic"), static_cast(font.italic())); content.setAttribute(QStringLiteral("font-underline"), static_cast(font.underline())); content.setAttribute(QStringLiteral("letter-spacing"), QString::number(font.letterSpacing())); gradient = item->data(TitleDocument::Gradient).toString(); if (!gradient.isEmpty()) { content.setAttribute(QStringLiteral("gradient"), gradient); } cur = QTextCursor(t->document()); cur.select(QTextCursor::Document); format = cur.blockFormat(); if (t->toPlainText() == QLatin1String("%s")) { // template text box, adjust size for later remplacement text if (t->alignment() == Qt::AlignHCenter) { // grow dimensions on both sides double xcenter = item->pos().x() + (t->baseBoundingRect().width()) / 2; double offset = qMin(xcenter, m_width - xcenter); xPosition = xcenter - offset; content.setAttribute(QStringLiteral("box-width"), QString::number(2 * offset)); } else if (t->alignment() == Qt::AlignRight) { // grow to the left double offset = item->pos().x() + (t->baseBoundingRect().width()); xPosition = 0; content.setAttribute(QStringLiteral("box-width"), QString::number(offset)); } else { // left align, grow on right side double offset = m_width - item->pos().x(); content.setAttribute(QStringLiteral("box-width"), QString::number(offset)); } } else { content.setAttribute(QStringLiteral("box-width"), QString::number(t->baseBoundingRect().width())); } content.setAttribute(QStringLiteral("box-height"), QString::number(t->baseBoundingRect().height())); if (!t->data(TitleDocument::LineSpacing).isNull()) { content.setAttribute(QStringLiteral("line-spacing"), QString::number(t->data(TitleDocument::LineSpacing).toInt())); } { QTextCursor cursor(t->document()); cursor.select(QTextCursor::Document); QColor fontcolor = cursor.charFormat().foreground().color(); content.setAttribute(QStringLiteral("font-color"), colorToString(fontcolor)); if (!t->data(TitleDocument::OutlineWidth).isNull()) { content.setAttribute(QStringLiteral("font-outline"), QString::number(t->data(TitleDocument::OutlineWidth).toDouble())); } if (!t->data(TitleDocument::OutlineColor).isNull()) { QVariant variant = t->data(TitleDocument::OutlineColor); QColor outlineColor = variant.value(); content.setAttribute(QStringLiteral("font-outline-color"), colorToString(outlineColor)); } } if (!t->data(100).isNull()) { QStringList effectParams = t->data(100).toStringList(); QString effectName = effectParams.takeFirst(); content.setAttribute(QStringLiteral("textwidth"), QString::number(t->sceneBoundingRect().width())); content.setAttribute(effectName, effectParams.join(QLatin1Char(';'))); } // Only save when necessary. if (t->data(OriginXLeft).toInt() == AxisInverted) { content.setAttribute(QStringLiteral("kdenlive-axis-x-inverted"), t->data(OriginXLeft).toInt()); } if (t->data(OriginYTop).toInt() == AxisInverted) { content.setAttribute(QStringLiteral("kdenlive-axis-y-inverted"), t->data(OriginYTop).toInt()); } if (t->textWidth() > 0) { content.setAttribute(QStringLiteral("alignment"), (int)t->alignment()); } content.setAttribute(QStringLiteral("shadow"), t->shadowInfo().join(QLatin1Char(';'))); break; default: continue; } // position QDomElement pos = doc.createElement(QStringLiteral("position")); pos.setAttribute(QStringLiteral("x"), QString::number(xPosition)); pos.setAttribute(QStringLiteral("y"), QString::number(item->pos().y())); QTransform transform = item->transform(); QDomElement tr = doc.createElement(QStringLiteral("transform")); if (!item->data(TitleDocument::ZoomFactor).isNull()) { tr.setAttribute(QStringLiteral("zoom"), QString::number(item->data(TitleDocument::ZoomFactor).toInt())); } if (!item->data(TitleDocument::RotateFactor).isNull()) { QList rotlist = item->data(TitleDocument::RotateFactor).toList(); tr.setAttribute(QStringLiteral("rotation"), QStringLiteral("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble())); } tr.appendChild(doc.createTextNode(QStringLiteral("%1,%2,%3,%4,%5,%6,%7,%8,%9") .arg(transform.m11()) .arg(transform.m12()) .arg(transform.m13()) .arg(transform.m21()) .arg(transform.m22()) .arg(transform.m23()) .arg(transform.m31()) .arg(transform.m32()) .arg(transform.m33()))); e.setAttribute(QStringLiteral("z-index"), item->zValue()); pos.appendChild(tr); e.appendChild(pos); e.appendChild(content); if (item->zValue() > -1000) { main.appendChild(e); } } if ((startv != nullptr) && (endv != nullptr)) { QDomElement endport = doc.createElement(QStringLiteral("endviewport")); QDomElement startport = doc.createElement(QStringLiteral("startviewport")); QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height()); endport.setAttribute(QStringLiteral("rect"), rectFToString(r)); QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height()); startport.setAttribute(QStringLiteral("rect"), rectFToString(r2)); main.appendChild(startport); main.appendChild(endport); } QDomElement backgr = doc.createElement(QStringLiteral("background")); QColor color = getBackgroundColor(); backgr.setAttribute(QStringLiteral("color"), colorToString(color)); main.appendChild(backgr); return doc; } /** \brief Get the background color (incl. alpha) from the document, if possibly * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */ QColor TitleDocument::getBackgroundColor() const { QColor color(0, 0, 0, 0); if (m_scene) { QList items = m_scene->items(); for (int i = 0; i < items.size(); ++i) { if ((int)items.at(i)->zValue() == -1100) { color = static_cast(items.at(i))->brush().color(); return color; } } } return color; } bool TitleDocument::saveDocument(const QUrl &url, QGraphicsRectItem *startv, QGraphicsRectItem *endv, int duration, bool embed) { if (!m_scene) { return false; } QDomDocument doc = xml(startv, endv, embed); doc.documentElement().setAttribute(QStringLiteral("duration"), duration); // keep some time for backwards compatibility (opening projects with older versions) - 26/12/12 doc.documentElement().setAttribute(QStringLiteral("out"), duration); QTemporaryFile tmpfile; if (!tmpfile.open()) { qCWarning(KDENLIVE_LOG) << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName(); return false; } QFile xmlf(tmpfile.fileName()); if (!xmlf.open(QIODevice::WriteOnly)) { return false; } xmlf.write(doc.toString().toUtf8()); if (xmlf.error() != QFile::NoError) { xmlf.close(); return false; } xmlf.close(); KIO::FileCopyJob *copyjob = KIO::file_copy(QUrl::fromLocalFile(tmpfile.fileName()), url, -1, KIO::Overwrite); return copyjob->exec(); } int TitleDocument::loadFromXml(const QDomDocument &doc, QGraphicsRectItem *startv, QGraphicsRectItem *endv, int *duration, const QString &projectpath) { m_projectPath = projectpath; + m_missingElements = 0; QDomNodeList titles = doc.elementsByTagName(QStringLiteral("kdenlivetitle")); // TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale if (doc.documentElement().hasAttribute(QStringLiteral("width")) && doc.documentElement().hasAttribute(QStringLiteral("height"))) { int doc_width = doc.documentElement().attribute(QStringLiteral("width")).toInt(); int doc_height = doc.documentElement().attribute(QStringLiteral("height")).toInt(); if (doc_width != m_width || doc_height != m_height) { KMessageBox::information(QApplication::activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile")); // TODO: convert using QTransform m_width = doc_width; m_height = doc_height; } } else { // Document has no size info, it is likely an old version title, so ignore viewport data QDomNodeList viewportlist = doc.documentElement().elementsByTagName(QStringLiteral("startviewport")); if (!viewportlist.isEmpty()) { doc.documentElement().removeChild(viewportlist.at(0)); } viewportlist = doc.documentElement().elementsByTagName(QStringLiteral("endviewport")); if (!viewportlist.isEmpty()) { doc.documentElement().removeChild(viewportlist.at(0)); } } if (doc.documentElement().hasAttribute(QStringLiteral("duration"))) { *duration = doc.documentElement().attribute(QStringLiteral("duration")).toInt(); } else if (doc.documentElement().hasAttribute(QStringLiteral("out"))) { *duration = doc.documentElement().attribute(QStringLiteral("out")).toInt(); } else { *duration = Timecode().getFrameCount(KdenliveSettings::title_duration()); } int maxZValue = 0; if (!titles.isEmpty()) { QDomNodeList items = titles.item(0).childNodes(); for (int i = 0; i < items.count(); ++i) { QGraphicsItem *gitem = nullptr; QDomNode itemNode = items.item(i); // qCDebug(KDENLIVE_LOG) << items.item(i).attributes().namedItem("type").nodeValue(); int zValue = itemNode.attributes().namedItem(QStringLiteral("z-index")).nodeValue().toInt(); double xPosition = itemNode.namedItem(QStringLiteral("position")).attributes().namedItem(QStringLiteral("x")).nodeValue().toDouble(); if (zValue > -1000) { if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsTextItem")) { QDomNamedNodeMap txtProperties = itemNode.namedItem(QStringLiteral("content")).attributes(); QFont font(txtProperties.namedItem(QStringLiteral("font")).nodeValue()); QDomNode node = txtProperties.namedItem(QStringLiteral("font-bold")); if (!node.isNull()) { // Old: Bold/Not bold. font.setBold(node.nodeValue().toInt() != 0); } else { // New: Font weight (QFont::) font.setWeight(txtProperties.namedItem(QStringLiteral("font-weight")).nodeValue().toInt()); } // font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt()); font.setItalic(txtProperties.namedItem(QStringLiteral("font-italic")).nodeValue().toInt() != 0); font.setUnderline(txtProperties.namedItem(QStringLiteral("font-underline")).nodeValue().toInt() != 0); // Older Kdenlive version did not store pixel size but point size if (txtProperties.namedItem(QStringLiteral("font-pixel-size")).isNull()) { KMessageBox::information(QApplication::activeWindow(), i18n("Some of your text clips were saved with size in points, which means " "different sizes on different displays. They will be converted to pixel " "size, making them portable, but you could have to adjust their size."), i18n("Text Clips Updated")); QFont f2; f2.setPointSize(txtProperties.namedItem(QStringLiteral("font-size")).nodeValue().toInt()); font.setPixelSize(QFontInfo(f2).pixelSize()); } else { font.setPixelSize(txtProperties.namedItem(QStringLiteral("font-pixel-size")).nodeValue().toInt()); } font.setLetterSpacing(QFont::AbsoluteSpacing, txtProperties.namedItem(QStringLiteral("letter-spacing")).nodeValue().toInt()); QColor col(stringToColor(txtProperties.namedItem(QStringLiteral("font-color")).nodeValue())); MyTextItem *txt = new MyTextItem(itemNode.namedItem(QStringLiteral("content")).firstChild().nodeValue(), nullptr); m_scene->addItem(txt); txt->setFont(font); txt->setTextInteractionFlags(Qt::NoTextInteraction); QTextCursor cursor(txt->document()); cursor.select(QTextCursor::Document); QTextCharFormat cformat = cursor.charFormat(); if (txtProperties.namedItem(QStringLiteral("font-outline")).nodeValue().toDouble() > 0.0) { txt->setData(TitleDocument::OutlineWidth, txtProperties.namedItem(QStringLiteral("font-outline")).nodeValue().toDouble()); txt->setData(TitleDocument::OutlineColor, stringToColor(txtProperties.namedItem(QStringLiteral("font-outline-color")).nodeValue())); cformat.setTextOutline(QPen(QColor(stringToColor(txtProperties.namedItem(QStringLiteral("font-outline-color")).nodeValue())), txtProperties.namedItem(QStringLiteral("font-outline")).nodeValue().toDouble(), Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); } if (!txtProperties.namedItem(QStringLiteral("line-spacing")).isNull()) { int lineSpacing = txtProperties.namedItem(QStringLiteral("line-spacing")).nodeValue().toInt(); QTextBlockFormat format = cursor.blockFormat(); format.setLineHeight(lineSpacing, QTextBlockFormat::LineDistanceHeight); cursor.setBlockFormat(format); txt->setData(TitleDocument::LineSpacing, lineSpacing); } txt->setTextColor(col); cformat.setForeground(QBrush(col)); cursor.setCharFormat(cformat); if (!txtProperties.namedItem(QStringLiteral("gradient")).isNull()) { // Gradient color QString data = txtProperties.namedItem(QStringLiteral("gradient")).nodeValue(); txt->setData(TitleDocument::Gradient, data); QLinearGradient gr = GradientWidget::gradientFromString(data, txt->boundingRect().width(), txt->boundingRect().height()); cformat.setForeground(QBrush(gr)); cursor.setCharFormat(cformat); } if (!txtProperties.namedItem(QStringLiteral("alignment")).isNull()) { txt->setAlignment((Qt::Alignment)txtProperties.namedItem(QStringLiteral("alignment")).nodeValue().toInt()); } if (!txtProperties.namedItem(QStringLiteral("kdenlive-axis-x-inverted")).isNull()) { txt->setData(OriginXLeft, txtProperties.namedItem(QStringLiteral("kdenlive-axis-x-inverted")).nodeValue().toInt()); } if (!txtProperties.namedItem(QStringLiteral("kdenlive-axis-y-inverted")).isNull()) { txt->setData(OriginYTop, txtProperties.namedItem(QStringLiteral("kdenlive-axis-y-inverted")).nodeValue().toInt()); } if (!txtProperties.namedItem(QStringLiteral("shadow")).isNull()) { QString info = txtProperties.namedItem(QStringLiteral("shadow")).nodeValue(); txt->loadShadow(info.split(QLatin1Char(';'))); } // Effects if (!txtProperties.namedItem(QStringLiteral("typewriter")).isNull()) { QStringList effData = QStringList() << QStringLiteral("typewriter") << txtProperties.namedItem(QStringLiteral("typewriter")).nodeValue(); txt->setData(100, effData); } if (txt->toPlainText() == QLatin1String("%s")) { // template text box, adjust size for later remplacement text if (txt->alignment() == Qt::AlignHCenter) { // grow dimensions on both sides double width = txtProperties.namedItem(QStringLiteral("box-width")).nodeValue().toDouble(); double xcenter = (width - xPosition) / 2.0; xPosition = xcenter - txt->boundingRect().width() / 2; } else if (txt->alignment() == Qt::AlignRight) { // grow to the left xPosition = xPosition + txtProperties.namedItem(QStringLiteral("box-width")).nodeValue().toDouble() - txt->boundingRect().width(); } else { // left align, grow on right side, nothing to do } } gitem = txt; } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsRectItem")) { QDomNamedNodeMap rectProperties = itemNode.namedItem(QStringLiteral("content")).attributes(); QString rect = rectProperties.namedItem(QStringLiteral("rect")).nodeValue(); QString br_str = rectProperties.namedItem(QStringLiteral("brushcolor")).nodeValue(); QString pen_str = rectProperties.namedItem(QStringLiteral("pencolor")).nodeValue(); double penwidth = rectProperties.namedItem(QStringLiteral("penwidth")).nodeValue().toDouble(); auto *rec = new MyRectItem(); rec->setRect(stringToRect(rect)); if (penwidth > 0) { rec->setPen(QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); } else { rec->setPen(Qt::NoPen); } if (!rectProperties.namedItem(QStringLiteral("gradient")).isNull()) { // Gradient color QString data = rectProperties.namedItem(QStringLiteral("gradient")).nodeValue(); rec->setData(TitleDocument::Gradient, data); QLinearGradient gr = GradientWidget::gradientFromString(data, rec->rect().width(), rec->rect().height()); rec->setBrush(QBrush(gr)); } else { rec->setBrush(QBrush(stringToColor(br_str))); } m_scene->addItem(rec); gitem = rec; } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsPixmapItem")) { QString url = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("url")).nodeValue(); QString base64 = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("base64")).nodeValue(); QPixmap pix; + bool missing = false; if (base64.isEmpty()) { pix.load(url); if (pix.isNull()) { pix = createInvalidPixmap(url); + m_missingElements++; + missing = true; } } else { pix.loadFromData(QByteArray::fromBase64(base64.toLatin1())); } auto *rec = new MyPixmapItem(pix); + if (missing) { + rec->setData(Qt::UserRole + 2, 1); + } m_scene->addItem(rec); rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); rec->setData(Qt::UserRole, url); if (!base64.isEmpty()) { rec->setData(Qt::UserRole + 1, base64); } gitem = rec; } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsSvgItem")) { QString url = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("url")).nodeValue(); QString base64 = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("base64")).nodeValue(); QGraphicsSvgItem *rec = nullptr; if (base64.isEmpty()) { if (QFile::exists(url)) { rec = new MySvgItem(url); } } else { rec = new MySvgItem(); QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toLatin1()), rec); rec->setSharedRenderer(renderer); // QString elem=rec->elementId(); // QRectF bounds = renderer->boundsOnElement(elem); } if (rec) { m_scene->addItem(rec); rec->setData(Qt::UserRole, url); if (!base64.isEmpty()) { rec->setData(Qt::UserRole + 1, base64); } gitem = rec; } else { QPixmap pix = createInvalidPixmap(url); + m_missingElements++; auto *rec2 = new MyPixmapItem(pix); + rec2->setData(Qt::UserRole + 2, 1); m_scene->addItem(rec2); rec2->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); rec2->setData(Qt::UserRole, url); gitem = rec2; } } } // pos and transform if (gitem) { QPointF p(xPosition, itemNode.namedItem(QStringLiteral("position")).attributes().namedItem(QStringLiteral("y")).nodeValue().toDouble()); gitem->setPos(p); QDomElement trans = itemNode.namedItem(QStringLiteral("position")).firstChild().toElement(); gitem->setTransform(stringToTransform(trans.firstChild().nodeValue())); QString rotate = trans.attribute(QStringLiteral("rotation")); if (!rotate.isEmpty()) { gitem->setData(TitleDocument::RotateFactor, stringToList(rotate)); } QString zoom = trans.attribute(QStringLiteral("zoom")); if (!zoom.isEmpty()) { gitem->setData(TitleDocument::ZoomFactor, zoom.toInt()); } if (zValue >= maxZValue) { maxZValue = zValue + 1; } gitem->setZValue(zValue); gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges); // effects QDomNode eff = itemNode.namedItem(QStringLiteral("effect")); if (!eff.isNull()) { QDomElement e = eff.toElement(); if (e.attribute(QStringLiteral("type")) == QLatin1String("blur")) { auto *blur = new QGraphicsBlurEffect(); blur->setBlurRadius(e.attribute(QStringLiteral("blurradius")).toInt()); gitem->setGraphicsEffect(blur); } else if (e.attribute(QStringLiteral("type")) == QLatin1String("shadow")) { auto *shadow = new QGraphicsDropShadowEffect(); shadow->setBlurRadius(e.attribute(QStringLiteral("blurradius")).toInt()); shadow->setOffset(e.attribute(QStringLiteral("xoffset")).toInt(), e.attribute(QStringLiteral("yoffset")).toInt()); gitem->setGraphicsEffect(shadow); } } } if (itemNode.nodeName() == QLatin1String("background")) { // qCDebug(KDENLIVE_LOG) << items.item(i).attributes().namedItem("color").nodeValue(); QColor color = QColor(stringToColor(itemNode.attributes().namedItem(QStringLiteral("color")).nodeValue())); // color.setAlpha(itemNode.attributes().namedItem("alpha").nodeValue().toInt()); QList sceneItems = m_scene->items(); for (int j = 0; j < sceneItems.size(); ++j) { if ((int)sceneItems.at(j)->zValue() == -1100) { static_cast(sceneItems.at(j))->setBrush(QBrush(color)); break; } } } else if (itemNode.nodeName() == QLatin1String("startviewport") && (startv != nullptr)) { QString rect = itemNode.attributes().namedItem(QStringLiteral("rect")).nodeValue(); QRectF r = stringToRect(rect); startv->setRect(0, 0, r.width(), r.height()); startv->setPos(r.topLeft()); } else if (itemNode.nodeName() == QLatin1String("endviewport") && (endv != nullptr)) { QString rect = itemNode.attributes().namedItem(QStringLiteral("rect")).nodeValue(); QRectF r = stringToRect(rect); endv->setRect(0, 0, r.width(), r.height()); endv->setPos(r.topLeft()); } } } return maxZValue; } +int TitleDocument::invalidCount() const +{ + return m_missingElements; +} + QPixmap TitleDocument::createInvalidPixmap(const QString &url) { int missingHeight = m_height / 10; QPixmap pix(missingHeight, missingHeight); QIcon icon = QIcon::fromTheme(QStringLiteral("messagebox_warning")); pix.fill(QColor(255, 0, 0, 50)); QPainter ptr(&pix); icon.paint(&ptr, 0, 0, missingHeight/2, missingHeight/2); QPen pen(Qt::red); pen.setWidth(3); ptr.setPen(pen); ptr.drawText(QRectF(2, 2, missingHeight - 4, missingHeight - 4), Qt::AlignHCenter | Qt::AlignBottom, QFileInfo(url).fileName()); ptr.drawRect(2, 1, missingHeight - 4, missingHeight - 4); ptr.end(); return pix; } QString TitleDocument::colorToString(const QColor &c) { QString ret = QStringLiteral("%1,%2,%3,%4"); ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha()); return ret; } QString TitleDocument::rectFToString(const QRectF &c) { QString ret = QStringLiteral("%1,%2,%3,%4"); ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height()); return ret; } QRectF TitleDocument::stringToRect(const QString &s) { QStringList l = s.split(QLatin1Char(',')); if (l.size() < 4) { return QRectF(); } return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized(); } QColor TitleDocument::stringToColor(const QString &s) { QStringList l = s.split(QLatin1Char(',')); if (l.size() < 4) { return QColor(); } return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt()); } QTransform TitleDocument::stringToTransform(const QString &s) { QStringList l = s.split(QLatin1Char(',')); if (l.size() < 9) { return QTransform(); } return QTransform(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(), l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()); } QList TitleDocument::stringToList(const QString &s) { QStringList l = s.split(QLatin1Char(',')); if (l.size() < 3) { return QList(); } return QList() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble()); } int TitleDocument::frameWidth() const { return m_width; } int TitleDocument::frameHeight() const { return m_height; } diff --git a/src/titler/titledocument.h b/src/titler/titledocument.h index ca4dd4625..b6b48c321 100644 --- a/src/titler/titledocument.h +++ b/src/titler/titledocument.h @@ -1,67 +1,70 @@ /*************************************************************************** titledocument.h - description ------------------- begin : Feb 28 2008 copyright : (C) 2008 by Marco Gittler email : g.marco@freenet.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef TITLEDOCUMENT_H #define TITLEDOCUMENT_H #include #include #include #include #include #include class QGraphicsScene; class QGraphicsRectItem; class QGraphicsItem; class TitleDocument { public: TitleDocument(); enum TitleProperties { OutlineWidth = 101, OutlineColor, LineSpacing, Gradient, RotateFactor, ZoomFactor }; void setScene(QGraphicsScene *scene, int width, int height); bool saveDocument(const QUrl &url, QGraphicsRectItem *startv, QGraphicsRectItem *endv, int duration, bool embed_images = false); QDomDocument xml(QGraphicsRectItem *startv, QGraphicsRectItem *endv, bool embed_images = false); int loadFromXml(const QDomDocument &doc, QGraphicsRectItem *startv, QGraphicsRectItem *endv, int *duration, const QString &projectpath = QString()); /** \brief Get the background color (incl. alpha) from the document, if possibly * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */ QColor getBackgroundColor() const; int frameWidth() const; int frameHeight() const; /** \brief Extract embedded images in project titles folder. */ static const QString extractBase64Image(const QString &titlePath, const QString &data); + /** \brief The number of missing elements in this title. */ + int invalidCount() const; enum ItemOrigin { OriginXLeft = 0, OriginYTop = 1 }; enum AxisPosition { AxisDefault = 0, AxisInverted = 1 }; private: QGraphicsScene *m_scene; QString m_projectPath; + int m_missingElements; int m_width; int m_height; QString colorToString(const QColor &); QString rectFToString(const QRectF &); QRectF stringToRect(const QString &); QColor stringToColor(const QString &); QTransform stringToTransform(const QString &); QList stringToList(const QString &); int base64ToUrl(QGraphicsItem *item, QDomElement &content, bool embed); QPixmap createInvalidPixmap(const QString &url); }; #endif diff --git a/src/titler/titlewidget.cpp b/src/titler/titlewidget.cpp index 0f530d5ce..31ee758b2 100644 --- a/src/titler/titlewidget.cpp +++ b/src/titler/titlewidget.cpp @@ -1,3064 +1,3105 @@ /*************************************************************************** titlewidget.cpp - description ------------------- begin : Feb 28 2008 copyright : (C) 2008 by Marco Gittler email : g.marco@freenet.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "titlewidget.h" #include "KoSliderCombo.h" #include "doc/kthumb.h" #include "gradientwidget.h" #include "kdenlivesettings.h" #include "monitor/monitor.h" #include #include #include #include #include +#include #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static QList titletemplates; // What exactly is this variable good for? int settingUp = 0; const int IMAGEITEM = 7; const int RECTITEM = 3; const int TEXTITEM = 8; const int NOEFFECT = 0; const int BLUREFFECT = 1; const int SHADOWEFFECT = 2; const int TYPEWRITEREFFECT = 3; void TitleWidget::refreshTemplateBoxContents() { templateBox->clear(); templateBox->addItem(QString()); for (const TitleTemplate &t : titletemplates) { templateBox->addItem(t.icon, t.name, t.file); } } TitleWidget::TitleWidget(const QUrl &url, const Timecode &tc, const QString &projectTitlePath, Monitor *monitor, QWidget *parent) : QDialog(parent) , Ui::TitleWidget_UI() , m_startViewport(nullptr) , m_endViewport(nullptr) , m_count(0) + , m_missingMessage(nullptr) , m_unicodeDialog(new UnicodeDialog(UnicodeDialog::InputHex)) , m_projectTitlePath(projectTitlePath) , m_tc(tc) , m_fps(monitor->fps()) , m_guides(QList ()) { setupUi(this); setMinimumSize(200, 200); setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); frame_properties->setEnabled(false); frame_properties->setFixedHeight(frame_toolbar->height()); int size = style()->pixelMetric(QStyle::PM_SmallIconSize); QSize iconSize(size, size); rectBColor->setAlphaChannelEnabled(true); rectFColor->setAlphaChannelEnabled(true); fontColorButton->setAlphaChannelEnabled(true); textOutlineColor->setAlphaChannelEnabled(true); shadowColor->setAlphaChannelEnabled(true); auto *colorGroup = new QButtonGroup(this); colorGroup->addButton(gradient_color); colorGroup->addButton(plain_color); auto *alignGroup = new QButtonGroup(this); alignGroup->addButton(buttonAlignLeft); alignGroup->addButton(buttonAlignCenter); alignGroup->addButton(buttonAlignRight); textOutline->setMinimum(0); textOutline->setMaximum(200); // textOutline->setDecimals(0); textOutline->setValue(0); textOutline->setToolTip(i18n("Outline width")); backgroundAlpha->setMinimum(0); backgroundAlpha->setMaximum(255); bgAlphaSlider->setMinimum(0); bgAlphaSlider->setMaximum(255); backgroundAlpha->setValue(0); backgroundAlpha->setToolTip(i18n("Background color opacity")); itemrotatex->setMinimum(-360); itemrotatex->setMaximum(360); // itemrotatex->setDecimals(0); itemrotatex->setValue(0); itemrotatex->setToolTip(i18n("Rotation around the X axis")); itemrotatey->setMinimum(-360); itemrotatey->setMaximum(360); // itemrotatey->setDecimals(0); itemrotatey->setValue(0); itemrotatey->setToolTip(i18n("Rotation around the Y axis")); itemrotatez->setMinimum(-360); itemrotatez->setMaximum(360); // itemrotatez->setDecimals(0); itemrotatez->setValue(0); itemrotatez->setToolTip(i18n("Rotation around the Z axis")); rectLineWidth->setMinimum(0); rectLineWidth->setMaximum(500); // rectLineWidth->setDecimals(0); rectLineWidth->setValue(0); rectLineWidth->setToolTip(i18n("Border width")); itemzoom->setSuffix(i18n("%")); QSize profileSize = monitor->profileSize(); m_frameWidth = (int)(profileSize.height() * monitor->profile()->dar() + 0.5); m_frameHeight = profileSize.height(); showToolbars(TITLE_SELECT); splitter->setStretchFactor(0, 20); // If project is drop frame, set the input mask as such. title_duration->setInputMask(m_tc.mask()); title_duration->setText(m_tc.reformatSeparators(KdenliveSettings::title_duration())); connect(backgroundColor, &KColorButton::changed, this, &TitleWidget::slotChangeBackground); connect(backgroundAlpha, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotChangeBackground); connect(shadowBox, &QGroupBox::toggled, this, &TitleWidget::slotUpdateShadow); connect(shadowColor, &KColorButton::changed, this, &TitleWidget::slotUpdateShadow); connect(blur_radius, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateShadow); connect(shadowX, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateShadow); connect(shadowY, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateShadow); connect(fontColorButton, &KColorButton::changed, this, &TitleWidget::slotUpdateText); connect(plain_color, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText); connect(gradient_color, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText); connect(gradients_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateText())); connect(textOutlineColor, &KColorButton::changed, this, &TitleWidget::slotUpdateText); connect(font_family, &QFontComboBox::currentFontChanged, this, &TitleWidget::slotUpdateText); connect(font_size, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText); connect(letter_spacing, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText); connect(line_spacing, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText); connect(textOutline, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText); connect(font_weight_box, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateText())); connect(rectFColor, &KColorButton::changed, this, &TitleWidget::rectChanged); connect(rectBColor, &KColorButton::changed, this, &TitleWidget::rectChanged); connect(plain_rect, &QAbstractButton::clicked, this, &TitleWidget::rectChanged); connect(gradient_rect, &QAbstractButton::clicked, this, &TitleWidget::rectChanged); connect(gradients_rect_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(rectChanged())); connect(rectLineWidth, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::rectChanged); connect(zValue, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::zIndexChanged); connect(itemzoom, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::itemScaled); connect(itemrotatex, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::itemRotateX); connect(itemrotatey, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::itemRotateY); connect(itemrotatez, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::itemRotateZ); connect(itemhcenter, &QAbstractButton::clicked, this, &TitleWidget::itemHCenter); connect(itemvcenter, &QAbstractButton::clicked, this, &TitleWidget::itemVCenter); connect(itemtop, &QAbstractButton::clicked, this, &TitleWidget::itemTop); connect(itembottom, &QAbstractButton::clicked, this, &TitleWidget::itemBottom); connect(itemleft, &QAbstractButton::clicked, this, &TitleWidget::itemLeft); connect(itemright, &QAbstractButton::clicked, this, &TitleWidget::itemRight); connect(effect_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotAddEffect(int))); connect(typewriter_delay, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotEditTypewriter); connect(typewriter_start, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotEditTypewriter); connect(origin_x_left, &QAbstractButton::clicked, this, &TitleWidget::slotOriginXClicked); connect(origin_y_top, &QAbstractButton::clicked, this, &TitleWidget::slotOriginYClicked); connect(monitor, &Monitor::frameUpdated, this, &TitleWidget::slotGotBackground); // Position and size m_signalMapper = new QSignalMapper(this); m_signalMapper->setMapping(value_w, ValueWidth); m_signalMapper->setMapping(value_h, ValueHeight); m_signalMapper->setMapping(value_x, ValueX); m_signalMapper->setMapping(value_y, ValueY); connect(value_w, static_cast(&QSpinBox::valueChanged), m_signalMapper, static_cast(&QSignalMapper::map)); connect(value_h, static_cast(&QSpinBox::valueChanged), m_signalMapper, static_cast(&QSignalMapper::map)); connect(value_x, static_cast(&QSpinBox::valueChanged), m_signalMapper, static_cast(&QSignalMapper::map)); connect(value_y, static_cast(&QSpinBox::valueChanged), m_signalMapper, static_cast(&QSignalMapper::map)); connect(m_signalMapper, SIGNAL(mapped(int)), this, SLOT(slotValueChanged(int))); connect(buttonFitZoom, &QAbstractButton::clicked, this, &TitleWidget::slotAdjustZoom); connect(buttonRealSize, &QAbstractButton::clicked, this, &TitleWidget::slotZoomOneToOne); connect(buttonItalic, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText); connect(buttonUnder, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText); connect(buttonAlignLeft, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText); connect(buttonAlignRight, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText); connect(buttonAlignCenter, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText); connect(edit_gradient, &QAbstractButton::clicked, this, &TitleWidget::slotEditGradient); connect(edit_rect_gradient, &QAbstractButton::clicked, this, &TitleWidget::slotEditGradient); connect(displayBg, &QCheckBox::stateChanged, this, &TitleWidget::displayBackgroundFrame); connect(m_unicodeDialog, &UnicodeDialog::charSelected, this, &TitleWidget::slotInsertUnicodeString); // mbd connect(this, &QDialog::accepted, this, &TitleWidget::slotAccepted); font_weight_box->blockSignals(true); font_weight_box->addItem(i18nc("Font style", "Light"), QFont::Light); font_weight_box->addItem(i18nc("Font style", "Normal"), QFont::Normal); font_weight_box->addItem(i18nc("Font style", "Demi-Bold"), QFont::DemiBold); font_weight_box->addItem(i18nc("Font style", "Bold"), QFont::Bold); font_weight_box->addItem(i18nc("Font style", "Black"), QFont::Black); font_weight_box->setToolTip(i18n("Font weight")); font_weight_box->setCurrentIndex(1); font_weight_box->blockSignals(false); buttonFitZoom->setIconSize(iconSize); buttonRealSize->setIconSize(iconSize); buttonItalic->setIconSize(iconSize); buttonUnder->setIconSize(iconSize); buttonAlignCenter->setIconSize(iconSize); buttonAlignLeft->setIconSize(iconSize); buttonAlignRight->setIconSize(iconSize); buttonFitZoom->setIcon(QIcon::fromTheme(QStringLiteral("zoom-fit-best"))); buttonRealSize->setIcon(QIcon::fromTheme(QStringLiteral("zoom-original"))); buttonItalic->setIcon(QIcon::fromTheme(QStringLiteral("format-text-italic"))); buttonUnder->setIcon(QIcon::fromTheme(QStringLiteral("format-text-underline"))); buttonAlignCenter->setIcon(QIcon::fromTheme(QStringLiteral("format-justify-center"))); buttonAlignLeft->setIcon(QIcon::fromTheme(QStringLiteral("format-justify-left"))); buttonAlignRight->setIcon(QIcon::fromTheme(QStringLiteral("format-justify-right"))); edit_gradient->setIcon(QIcon::fromTheme(QStringLiteral("document-edit"))); edit_rect_gradient->setIcon(QIcon::fromTheme(QStringLiteral("document-edit"))); buttonAlignRight->setToolTip(i18n("Align right")); buttonAlignLeft->setToolTip(i18n("Align left")); buttonAlignCenter->setToolTip(i18n("Align center")); buttonAlignLeft->setChecked(true); m_unicodeAction = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-insert-unicode")), QString(), this); m_unicodeAction->setShortcut(Qt::SHIFT + Qt::CTRL + Qt::Key_U); m_unicodeAction->setToolTip(getTooltipWithShortcut(i18n("Insert Unicode character"), m_unicodeAction)); connect(m_unicodeAction, &QAction::triggered, this, &TitleWidget::slotInsertUnicode); buttonInsertUnicode->setDefaultAction(m_unicodeAction); m_zUp = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-zindex-up")), QString(), this); m_zUp->setShortcut(Qt::Key_PageUp); m_zUp->setToolTip(i18n("Raise object")); connect(m_zUp, &QAction::triggered, this, &TitleWidget::slotZIndexUp); zUp->setDefaultAction(m_zUp); m_zDown = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-zindex-down")), QString(), this); m_zDown->setShortcut(Qt::Key_PageDown); m_zDown->setToolTip(i18n("Lower object")); connect(m_zDown, &QAction::triggered, this, &TitleWidget::slotZIndexDown); zDown->setDefaultAction(m_zDown); m_zTop = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-zindex-top")), QString(), this); // TODO mbt 1414: Shortcut should change z index only if // cursor is NOT in a text field ... // m_zTop->setShortcut(Qt::Key_Home); m_zTop->setToolTip(i18n("Raise object to top")); connect(m_zTop, &QAction::triggered, this, &TitleWidget::slotZIndexTop); zTop->setDefaultAction(m_zTop); m_zBottom = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-zindex-bottom")), QString(), this); // TODO mbt 1414 // m_zBottom->setShortcut(Qt::Key_End); m_zBottom->setToolTip(i18n("Lower object to bottom")); connect(m_zBottom, &QAction::triggered, this, &TitleWidget::slotZIndexBottom); zBottom->setDefaultAction(m_zBottom); m_selectAll = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-select-all")), QString(), this); m_selectAll->setShortcut(Qt::CTRL + Qt::Key_A); connect(m_selectAll, &QAction::triggered, this, &TitleWidget::slotSelectAll); buttonSelectAll->setDefaultAction(m_selectAll); m_selectText = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-select-texts")), QString(), this); m_selectText->setShortcut(Qt::CTRL + Qt::Key_T); connect(m_selectText, &QAction::triggered, this, &TitleWidget::slotSelectText); buttonSelectText->setDefaultAction(m_selectText); buttonSelectText->setEnabled(false); m_selectRects = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-select-rects")), QString(), this); m_selectRects->setShortcut(Qt::CTRL + Qt::Key_R); connect(m_selectRects, &QAction::triggered, this, &TitleWidget::slotSelectRects); buttonSelectRects->setDefaultAction(m_selectRects); buttonSelectRects->setEnabled(false); m_selectImages = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-select-images")), QString(), this); m_selectImages->setShortcut(Qt::CTRL + Qt::Key_I); connect(m_selectImages, &QAction::triggered, this, &TitleWidget::slotSelectImages); buttonSelectImages->setDefaultAction(m_selectImages); buttonSelectImages->setEnabled(false); m_unselectAll = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-unselect-all")), QString(), this); m_unselectAll->setShortcut(Qt::SHIFT + Qt::CTRL + Qt::Key_A); connect(m_unselectAll, &QAction::triggered, this, &TitleWidget::slotSelectNone); buttonUnselectAll->setDefaultAction(m_unselectAll); buttonUnselectAll->setEnabled(false); zDown->setIconSize(iconSize); zTop->setIconSize(iconSize); zBottom->setIconSize(iconSize); zDown->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-zindex-down"))); zTop->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-zindex-top"))); zBottom->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-zindex-bottom"))); connect(zDown, &QAbstractButton::clicked, this, &TitleWidget::slotZIndexDown); connect(zTop, &QAbstractButton::clicked, this, &TitleWidget::slotZIndexTop); connect(zBottom, &QAbstractButton::clicked, this, &TitleWidget::slotZIndexBottom); origin_x_left->setToolTip(i18n("Invert x axis and change 0 point")); origin_y_top->setToolTip(i18n("Invert y axis and change 0 point")); rectBColor->setToolTip(i18n("Select fill color")); rectFColor->setToolTip(i18n("Select border color")); zoom_slider->setToolTip(i18n("Zoom")); buttonRealSize->setToolTip(i18n("Original size (1:1)")); buttonFitZoom->setToolTip(i18n("Fit zoom")); backgroundColor->setToolTip(i18n("Select background color")); backgroundAlpha->setToolTip(i18n("Background opacity")); buttonSelectAll->setToolTip(getTooltipWithShortcut(i18n("Select all"), m_selectAll)); buttonSelectText->setToolTip(getTooltipWithShortcut(i18n("Select text items in current selection"), m_selectText)); buttonSelectRects->setToolTip(getTooltipWithShortcut(i18n("Select rect items in current selection"), m_selectRects)); buttonSelectImages->setToolTip(getTooltipWithShortcut(i18n("Select image items in current selection"), m_selectImages)); buttonUnselectAll->setToolTip(getTooltipWithShortcut(i18n("Unselect all"), m_unselectAll)); itemhcenter->setIconSize(iconSize); itemvcenter->setIconSize(iconSize); itemtop->setIconSize(iconSize); itembottom->setIconSize(iconSize); itemright->setIconSize(iconSize); itemleft->setIconSize(iconSize); itemhcenter->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-align-hor"))); itemhcenter->setToolTip(i18n("Align item horizontally")); itemvcenter->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-align-vert"))); itemvcenter->setToolTip(i18n("Align item vertically")); itemtop->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-align-top"))); itemtop->setToolTip(i18n("Align item to top")); itembottom->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-align-bottom"))); itembottom->setToolTip(i18n("Align item to bottom")); itemright->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-align-right"))); itemright->setToolTip(i18n("Align item to right")); itemleft->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-align-left"))); itemleft->setToolTip(i18n("Align item to left")); auto *layout = new QHBoxLayout; frame_toolbar->setLayout(layout); layout->setContentsMargins(0, 0, 0, 0); QToolBar *m_toolbar = new QToolBar(QStringLiteral("titleToolBar"), this); m_toolbar->setIconSize(iconSize); m_buttonCursor = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("transform-move")), i18n("Selection Tool")); m_buttonCursor->setCheckable(true); m_buttonCursor->setShortcut(Qt::ALT + Qt::Key_S); m_buttonCursor->setToolTip(i18n("Selection Tool") + QLatin1Char(' ') + m_buttonCursor->shortcut().toString()); connect(m_buttonCursor, &QAction::triggered, this, &TitleWidget::slotSelectTool); m_buttonText = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("insert-text")), i18n("Add Text")); m_buttonText->setCheckable(true); m_buttonText->setShortcut(Qt::ALT + Qt::Key_T); m_buttonText->setToolTip(i18n("Add Text") + QLatin1Char(' ') + m_buttonText->shortcut().toString()); connect(m_buttonText, &QAction::triggered, this, &TitleWidget::slotTextTool); m_buttonRect = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("kdenlive-insert-rect")), i18n("Add Rectangle")); m_buttonRect->setCheckable(true); m_buttonRect->setShortcut(Qt::ALT + Qt::Key_R); m_buttonRect->setToolTip(i18n("Add Rectangle") + QLatin1Char(' ') + m_buttonRect->shortcut().toString()); connect(m_buttonRect, &QAction::triggered, this, &TitleWidget::slotRectTool); m_buttonImage = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("insert-image")), i18n("Add Image")); m_buttonImage->setCheckable(false); m_buttonImage->setShortcut(Qt::ALT + Qt::Key_I); m_buttonImage->setToolTip(i18n("Add Image") + QLatin1Char(' ') + m_buttonImage->shortcut().toString()); connect(m_buttonImage, &QAction::triggered, this, &TitleWidget::slotImageTool); m_toolbar->addSeparator(); m_buttonLoad = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open Document")); m_buttonLoad->setCheckable(false); m_buttonLoad->setShortcut(Qt::CTRL + Qt::Key_O); m_buttonLoad->setToolTip(i18n("Open Document") + QLatin1Char(' ') + m_buttonLoad->shortcut().toString()); connect(m_buttonLoad, SIGNAL(triggered()), this, SLOT(loadTitle())); m_buttonSave = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("Save As")); m_buttonSave->setCheckable(false); m_buttonSave->setShortcut(Qt::CTRL + Qt::Key_S); m_buttonSave->setToolTip(i18n("Save As") + QLatin1Char(' ') + m_buttonSave->shortcut().toString()); connect(m_buttonSave, SIGNAL(triggered()), this, SLOT(saveTitle())); m_buttonDownload = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("edit-download")), i18n("Download New Title Templates...")); m_buttonDownload->setCheckable(false); m_buttonDownload->setShortcut(Qt::ALT + Qt::Key_D); m_buttonDownload->setToolTip(i18n("Download New Title Templates...") + QLatin1Char(' ') + m_buttonDownload->shortcut().toString()); connect(m_buttonDownload, &QAction::triggered, this, &TitleWidget::downloadTitleTemplates); layout->addWidget(m_toolbar); // initialize graphic scene m_scene = new GraphicsSceneRectMove(this); graphicsView->setScene(m_scene); graphicsView->setMouseTracking(true); graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); graphicsView->setDragMode(QGraphicsView::RubberBandDrag); graphicsView->setRubberBandSelectionMode(Qt::ContainsItemBoundingRect); m_titledocument.setScene(m_scene, m_frameWidth, m_frameHeight); connect(m_scene, &QGraphicsScene::changed, this, &TitleWidget::slotChanged); connect(font_size, static_cast(&QSpinBox::valueChanged), m_scene, &GraphicsSceneRectMove::slotUpdateFontSize); connect(use_grid, &QAbstractButton::toggled, m_scene, &GraphicsSceneRectMove::slotUseGrid); // Video frame rect QPen framepen; framepen.setColor(Qt::red); m_frameBorder = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight)); m_frameBorder->setPen(framepen); m_frameBorder->setZValue(1000); m_frameBorder->setBrush(Qt::transparent); m_frameBorder->setFlags(nullptr); m_frameBorder->setData(-1, -1); graphicsView->scene()->addItem(m_frameBorder); // Guides connect(show_guides, &QCheckBox::stateChanged, this, &TitleWidget::showGuides); show_guides->setChecked(KdenliveSettings::titlerShowGuides()); hguides->setValue(KdenliveSettings::titlerHGuides()); vguides->setValue(KdenliveSettings::titlerVGuides()); guideColor->setColor(KdenliveSettings::titleGuideColor()); connect(guideColor, &KColorButton::changed, this, &TitleWidget::guideColorChanged); connect(hguides, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::updateGuides); connect(vguides, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::updateGuides); updateGuides(0); // semi transparent safe zones framepen.setColor(QColor(255, 0, 0, 100)); QGraphicsRectItem *safe1 = new QGraphicsRectItem(QRectF(m_frameWidth * 0.05, m_frameHeight * 0.05, m_frameWidth * 0.9, m_frameHeight * 0.9), m_frameBorder); safe1->setBrush(Qt::transparent); safe1->setPen(framepen); safe1->setFlags(nullptr); safe1->setData(-1, -1); QGraphicsRectItem *safe2 = new QGraphicsRectItem(QRectF(m_frameWidth * 0.1, m_frameHeight * 0.1, m_frameWidth * 0.8, m_frameHeight * 0.8), m_frameBorder); safe2->setBrush(Qt::transparent); safe2->setPen(framepen); safe2->setFlags(nullptr); safe2->setData(-1, -1); m_frameBackground = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight)); m_frameBackground->setZValue(-1100); m_frameBackground->setBrush(Qt::transparent); m_frameBackground->setFlags(nullptr); graphicsView->scene()->addItem(m_frameBackground); m_frameImage = new QGraphicsPixmapItem(); QTransform qtrans; qtrans.scale(2.0, 2.0); m_frameImage->setTransform(qtrans); m_frameImage->setZValue(-1200); m_frameImage->setFlags(nullptr); displayBackgroundFrame(); graphicsView->scene()->addItem(m_frameImage); connect(m_scene, &QGraphicsScene::selectionChanged, this, &TitleWidget::selectionChanged); connect(m_scene, &GraphicsSceneRectMove::itemMoved, this, &TitleWidget::selectionChanged); connect(m_scene, &GraphicsSceneRectMove::sceneZoom, this, &TitleWidget::slotZoom); connect(m_scene, &GraphicsSceneRectMove::actionFinished, this, &TitleWidget::slotSelectTool); connect(m_scene, &GraphicsSceneRectMove::newRect, this, &TitleWidget::slotNewRect); connect(m_scene, &GraphicsSceneRectMove::newText, this, &TitleWidget::slotNewText); connect(zoom_slider, &QAbstractSlider::valueChanged, this, &TitleWidget::slotUpdateZoom); connect(zoom_spin, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateZoom); // mbd: load saved settings loadGradients(); readChoices(); // Hide effects not implemented tabWidget->removeTab(3); graphicsView->show(); graphicsView->setInteractive(true); // qCDebug(KDENLIVE_LOG) << "// TITLE WIDGWT: " << graphicsView->viewport()->width() << 'x' << graphicsView->viewport()->height(); m_startViewport = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight)); // Setting data at -1 so that the item is recognized as undeletable by graphicsscenerectmove m_startViewport->setData(-1, -1); m_endViewport = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight)); m_endViewport->setData(-1, -1); m_startViewport->setData(0, m_frameWidth); m_startViewport->setData(1, m_frameHeight); m_endViewport->setData(0, m_frameWidth); m_endViewport->setData(1, m_frameHeight); // scale the view so that the title widget is not too big at startup graphicsView->scale(.5, .5); if (url.isValid()) { loadTitle(url); } else { prepareTools(nullptr); slotTextTool(); QTimer::singleShot(200, this, &TitleWidget::slotAdjustZoom); } initAnimation(); QColor color = backgroundColor->color(); m_scene->setBackgroundBrush(QBrush(color)); color.setAlpha(backgroundAlpha->value()); m_frameBackground->setBrush(color); connect(anim_start, &QAbstractButton::toggled, this, &TitleWidget::slotAnimStart); connect(anim_end, &QAbstractButton::toggled, this, &TitleWidget::slotAnimEnd); connect(templateBox, SIGNAL(currentIndexChanged(int)), this, SLOT(templateIndexChanged(int))); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(KdenliveSettings::hastitleproducer()); if (titletemplates.isEmpty()) { refreshTitleTemplates(m_projectTitlePath); } // templateBox->setIconSize(QSize(60,60)); refreshTemplateBoxContents(); lastDocumentHash = QCryptographicHash::hash(xml().toString().toLatin1(), QCryptographicHash::Md5).toHex(); } TitleWidget::~TitleWidget() { m_scene->blockSignals(true); delete m_buttonRect; delete m_buttonText; delete m_buttonImage; delete m_buttonCursor; delete m_buttonSave; delete m_buttonLoad; delete m_unicodeAction; delete m_zUp; delete m_zDown; delete m_zTop; delete m_zBottom; delete m_selectAll; delete m_selectText; delete m_selectRects; delete m_selectImages; delete m_unselectAll; delete m_unicodeDialog; delete m_frameBorder; delete m_frameImage; delete m_startViewport; delete m_endViewport; delete m_scene; delete m_signalMapper; } // static QStringList TitleWidget::extractImageList(const QString &xml) { QStringList result; if (xml.isEmpty()) { return result; } QDomDocument doc; doc.setContent(xml); QDomNodeList images = doc.elementsByTagName(QStringLiteral("content")); for (int i = 0; i < images.count(); ++i) { if (images.at(i).toElement().hasAttribute(QStringLiteral("url"))) { result.append(images.at(i).toElement().attribute(QStringLiteral("url"))); } } return result; } // static QStringList TitleWidget::extractFontList(const QString &xml) { QStringList result; if (xml.isEmpty()) { return result; } QDomDocument doc; doc.setContent(xml); QDomNodeList images = doc.elementsByTagName(QStringLiteral("content")); for (int i = 0; i < images.count(); ++i) { if (images.at(i).toElement().hasAttribute(QStringLiteral("font"))) { result.append(images.at(i).toElement().attribute(QStringLiteral("font"))); } } return result; } // static void TitleWidget::refreshTitleTemplates(const QString &projectPath) { QStringList filters = QStringList() << QStringLiteral("*.kdenlivetitle"); titletemplates.clear(); // project templates QDir dir(projectPath); QStringList templateFiles = dir.entryList(filters, QDir::Files); for (const QString &fname : templateFiles) { TitleTemplate t; t.name = fname; t.file = dir.absoluteFilePath(fname); t.icon = QIcon(KThumb::getImage(QUrl::fromLocalFile(t.file), 0, 60, -1)); titletemplates.append(t); } // system templates QStringList titleTemplates = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("titles/"), QStandardPaths::LocateDirectory); for (const QString &folderpath : titleTemplates) { QDir folder(folderpath); QStringList filesnames = folder.entryList(filters, QDir::Files); for (const QString &fname : filesnames) { TitleTemplate t; t.name = fname; t.file = folder.absoluteFilePath(fname); t.icon = QIcon(KThumb::getImage(QUrl::fromLocalFile(t.file), 0, 60, -1)); titletemplates.append(t); } } } void TitleWidget::templateIndexChanged(int index) { QString item = templateBox->itemData(index).toString(); if (!item.isEmpty()) { if (lastDocumentHash != QCryptographicHash::hash(xml().toString().toLatin1(), QCryptographicHash::Md5).toHex()) { if (KMessageBox::questionYesNo(this, i18n("Do you really want to load a new template? Changes in this title will be lost!")) == KMessageBox::No) { return; } } loadTitle(QUrl::fromLocalFile(item)); // mbt 1607: Add property to distinguish between unchanged template titles and user titles. // Text of unchanged template titles should be selected when clicked. QList list = graphicsView->scene()->items(); for (QGraphicsItem *qgItem : list) { if (qgItem->type() == TEXTITEM) { MyTextItem *i = static_cast(qgItem); i->setProperty("isTemplate", "true"); i->setProperty("templateText", i->toHtml()); } } lastDocumentHash = QCryptographicHash::hash(xml().toString().toLatin1(), QCryptographicHash::Md5).toHex(); } } // virtual void TitleWidget::resizeEvent(QResizeEvent * /*event*/) { // slotAdjustZoom(); } // virtual void TitleWidget::keyPressEvent(QKeyEvent *e) { if (e->key() != Qt::Key_Escape && e->key() != Qt::Key_Return && e->key() != Qt::Key_Enter) { QDialog::keyPressEvent(e); } } void TitleWidget::slotTextTool() { m_scene->setTool(TITLE_TEXT); showToolbars(TITLE_TEXT); checkButton(TITLE_TEXT); } void TitleWidget::slotRectTool() { m_scene->setTool(TITLE_RECTANGLE); showToolbars(TITLE_RECTANGLE); checkButton(TITLE_RECTANGLE); // Disable dragging mode, would make dragging a rect impossible otherwise ;) graphicsView->setDragMode(QGraphicsView::NoDrag); } void TitleWidget::slotSelectTool() { m_scene->setTool(TITLE_SELECT); // Enable rubberband selecting mode. graphicsView->setDragMode(QGraphicsView::RubberBandDrag); // Find out which toolbars need to be shown, depending on selected item TITLETOOL t = TITLE_SELECT; QList l = graphicsView->scene()->selectedItems(); if (!l.isEmpty()) { switch (l.at(0)->type()) { case TEXTITEM: t = TITLE_TEXT; break; case RECTITEM: t = TITLE_RECTANGLE; break; case IMAGEITEM: t = TITLE_IMAGE; break; } } enableToolbars(t); if (t == TITLE_RECTANGLE && (l.at(0) == m_endViewport || l.at(0) == m_startViewport)) { // graphicsView->centerOn(l.at(0)); t = TITLE_SELECT; } showToolbars(t); if (!l.isEmpty()) { updateCoordinates(l.at(0)); updateDimension(l.at(0)); updateRotZoom(l.at(0)); } checkButton(TITLE_SELECT); } void TitleWidget::slotImageTool() { QList supported = QImageReader::supportedImageFormats(); QStringList mimeTypeFilters; QString allExtensions = i18n("All Images") + QStringLiteral(" ("); for (const QByteArray &mimeType : supported) { mimeTypeFilters.append(i18n("%1 Image", QString(mimeType)) + QStringLiteral("( *.") + QString(mimeType) + QLatin1Char(')')); allExtensions.append(QStringLiteral("*.") + mimeType + QLatin1Char(' ')); } mimeTypeFilters.sort(); allExtensions.append(QLatin1Char(')')); mimeTypeFilters.prepend(allExtensions); QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveImageFolder")); if (clipFolder.isEmpty()) { clipFolder = QDir::homePath(); } QFileDialog dialog(this, i18n("Add Image"), clipFolder); dialog.setAcceptMode(QFileDialog::AcceptOpen); dialog.setNameFilters(mimeTypeFilters); if (dialog.exec() != QDialog::Accepted) { return; } QUrl url = QUrl::fromLocalFile(dialog.selectedFiles().at(0)); if (url.isValid()) { KRecentDirs::add(QStringLiteral(":KdenliveImageFolder"), url.adjusted(QUrl::RemoveFilename).toLocalFile()); if (url.toLocalFile().endsWith(QLatin1String(".svg"))) { MySvgItem *svg = new MySvgItem(url.toLocalFile()); svg->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges); svg->setZValue(m_count++); svg->setData(Qt::UserRole, url.toLocalFile()); m_scene->addNewItem(svg); prepareTools(svg); } else { QPixmap pix(url.toLocalFile()); auto *image = new MyPixmapItem(pix); image->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); image->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges); image->setData(Qt::UserRole, url.toLocalFile()); image->setZValue(m_count++); m_scene->addNewItem(image); prepareTools(image); } } m_scene->setTool(TITLE_SELECT); showToolbars(TITLE_SELECT); checkButton(TITLE_SELECT); } void TitleWidget::showToolbars(TITLETOOL toolType) { // toolbar_stack->setEnabled(toolType != TITLE_SELECT); switch (toolType) { case TITLE_IMAGE: toolbar_stack->setCurrentIndex(2); break; case TITLE_RECTANGLE: toolbar_stack->setCurrentIndex(1); break; case TITLE_TEXT: default: toolbar_stack->setCurrentIndex(0); break; } } void TitleWidget::enableToolbars(TITLETOOL toolType) { // TITLETOOL is defined in effectstack/graphicsscenerectmove.h bool enable = false; if (toolType == TITLE_RECTANGLE || toolType == TITLE_IMAGE) { enable = true; } value_w->setEnabled(enable); value_h->setEnabled(enable); } void TitleWidget::checkButton(TITLETOOL toolType) { bool bSelect = false; bool bText = false; bool bRect = false; bool bImage = false; switch (toolType) { case TITLE_SELECT: bSelect = true; break; case TITLE_TEXT: bText = true; break; case TITLE_RECTANGLE: bRect = true; break; case TITLE_IMAGE: bImage = true; break; default: break; } m_buttonCursor->setChecked(bSelect); m_buttonText->setChecked(bText); m_buttonRect->setChecked(bRect); m_buttonImage->setChecked(bImage); } void TitleWidget::displayBackgroundFrame() { QRectF r = m_frameBorder->sceneBoundingRect(); if (!displayBg->isChecked()) { QPixmap pattern(20, 20); pattern.fill(Qt::gray); QColor bgcolor(180, 180, 180); QPainter p(&pattern); p.fillRect(QRect(0, 0, 10, 10), bgcolor); p.fillRect(QRect(10, 10, 20, 20), bgcolor); p.end(); QBrush br(pattern); QPixmap bg((int)(r.width() / 2), (int)(r.height() / 2)); QPainter p2(&bg); p2.fillRect(bg.rect(), br); p2.end(); m_frameImage->setPixmap(bg); } else { emit requestBackgroundFrame(m_clipId, true); } } void TitleWidget::slotGotBackground(const QImage &img) { QRectF r = m_frameBorder->sceneBoundingRect(); m_frameImage->setPixmap(QPixmap::fromImage(img.scaled(r.width() / 2, r.height() / 2))); emit requestBackgroundFrame(m_clipId, false); } void TitleWidget::initAnimation() { align_box->setEnabled(false); QPen startpen(Qt::DotLine); QPen endpen(Qt::DashDotLine); startpen.setColor(QColor(100, 200, 100, 140)); endpen.setColor(QColor(200, 100, 100, 140)); m_startViewport->setPen(startpen); m_endViewport->setPen(endpen); m_startViewport->setZValue(-1000); m_endViewport->setZValue(-1000); m_startViewport->setFlags(nullptr); m_endViewport->setFlags(nullptr); graphicsView->scene()->addItem(m_startViewport); graphicsView->scene()->addItem(m_endViewport); connect(keep_aspect, &QAbstractButton::toggled, this, &TitleWidget::slotKeepAspect); connect(resize50, &QAbstractButton::clicked, this, &TitleWidget::slotResize50); connect(resize100, &QAbstractButton::clicked, this, &TitleWidget::slotResize100); connect(resize200, &QAbstractButton::clicked, this, &TitleWidget::slotResize200); } void TitleWidget::slotUpdateZoom(int pos) { zoom_spin->setValue(pos); zoom_slider->setValue(pos); m_scene->setZoom((double)pos / 100); } void TitleWidget::slotZoom(bool up) { int pos = zoom_slider->value(); if (up) { pos++; } else { pos--; } zoom_slider->setValue(pos); } void TitleWidget::slotAdjustZoom() { /*double scalex = graphicsView->width() / (double)(m_frameWidth * 1.2); double scaley = graphicsView->height() / (double)(m_frameHeight * 1.2); if (scalex > scaley) scalex = scaley; int zoompos = (int)(scalex * 7 + 0.5);*/ graphicsView->fitInView(m_frameBorder, Qt::KeepAspectRatio); int zoompos = graphicsView->matrix().m11() * 100; zoom_slider->setValue(zoompos); graphicsView->centerOn(m_frameBorder); } void TitleWidget::slotZoomOneToOne() { zoom_slider->setValue(100); graphicsView->centerOn(m_frameBorder); } void TitleWidget::slotNewRect(QGraphicsRectItem *rect) { updateAxisButtons(rect); // back to default if (rectLineWidth->value() == 0) { rect->setPen(Qt::NoPen); } else { QPen penf(rectFColor->color()); penf.setWidth(rectLineWidth->value()); penf.setJoinStyle(Qt::RoundJoin); rect->setPen(penf); } if (plain_rect->isChecked()) { rect->setBrush(QBrush(rectBColor->color())); rect->setData(TitleDocument::Gradient, QVariant()); } else { // gradient QString gradientData = gradients_rect_combo->currentData().toString(); rect->setData(TitleDocument::Gradient, gradientData); QLinearGradient gr = GradientWidget::gradientFromString(gradientData, rect->boundingRect().width(), rect->boundingRect().height()); rect->setBrush(QBrush(gr)); } rect->setZValue(m_count++); rect->setData(TitleDocument::ZoomFactor, 100); prepareTools(rect); // setCurrentItem(rect); // graphicsView->setFocus(); } void TitleWidget::slotNewText(MyTextItem *tt) { updateAxisButtons(tt); // back to default letter_spacing->blockSignals(true); line_spacing->blockSignals(true); letter_spacing->setValue(0); line_spacing->setValue(0); letter_spacing->blockSignals(false); line_spacing->blockSignals(false); letter_spacing->setEnabled(true); line_spacing->setEnabled(true); QFont font = font_family->currentFont(); font.setPixelSize(font_size->value()); // mbd: issue 551: font.setWeight(font_weight_box->itemData(font_weight_box->currentIndex()).toInt()); font.setItalic(buttonItalic->isChecked()); font.setUnderline(buttonUnder->isChecked()); tt->setFont(font); QColor color = fontColorButton->color(); QColor outlineColor = textOutlineColor->color(); tt->setTextColor(color); tt->document()->setDocumentMargin(0); QTextCursor cur(tt->document()); cur.select(QTextCursor::Document); QTextBlockFormat format = cur.blockFormat(); QTextCharFormat cformat = cur.charFormat(); double outlineWidth = textOutline->value() / 10.0; tt->setData(TitleDocument::OutlineWidth, outlineWidth); tt->setData(TitleDocument::OutlineColor, outlineColor); if (outlineWidth > 0.0) { cformat.setTextOutline(QPen(outlineColor, outlineWidth)); } tt->updateShadow(shadowBox->isChecked(), blur_radius->value(), shadowX->value(), shadowY->value(), shadowColor->color()); if (gradient_color->isChecked()) { QString gradientData = gradients_combo->currentData().toString(); tt->setData(TitleDocument::Gradient, gradientData); QLinearGradient gr = GradientWidget::gradientFromString(gradientData, tt->boundingRect().width(), tt->boundingRect().height()); cformat.setForeground(QBrush(gr)); } else { cformat.setForeground(QBrush(color)); } cur.setCharFormat(cformat); cur.setBlockFormat(format); tt->setTextCursor(cur); tt->setZValue(m_count++); setCurrentItem(tt); prepareTools(tt); } void TitleWidget::setFontBoxWeight(int weight) { int index = font_weight_box->findData(weight); if (index < 0) { index = font_weight_box->findData(QFont::Normal); } font_weight_box->setCurrentIndex(index); } void TitleWidget::setCurrentItem(QGraphicsItem *item) { m_scene->setSelectedItem(item); } void TitleWidget::zIndexChanged(int v) { QList l = graphicsView->scene()->selectedItems(); for (int i = 0; i < l.size(); ++i) { l[i]->setZValue(v); } } void TitleWidget::selectionChanged() { if (m_scene->tool() != TITLE_SELECT) { return; } // qCDebug(KDENLIVE_LOG) << "Number of selected items: " << graphicsView->scene()->selectedItems().length() << '\n'; QList l; // mbt 1607: One text item might have grabbed the keyboard. // Ungrab it for all items that are not selected, otherwise // text input would only work for the text item that grabbed // the keyboard last. l = graphicsView->scene()->items(); for (QGraphicsItem *item : l) { if (item->type() == TEXTITEM && !item->isSelected()) { MyTextItem *i = static_cast(item); i->clearFocus(); } } l = graphicsView->scene()->selectedItems(); if (!l.isEmpty()) { buttonUnselectAll->setEnabled(true); // Enable all z index buttons if items selected. // We can selectively disable them later. zUp->setEnabled(true); zDown->setEnabled(true); zTop->setEnabled(true); zBottom->setEnabled(true); } else { buttonUnselectAll->setEnabled(false); } if (l.size() >= 2) { buttonSelectText->setEnabled(true); buttonSelectRects->setEnabled(true); buttonSelectImages->setEnabled(true); } else { buttonSelectText->setEnabled(false); buttonSelectRects->setEnabled(false); buttonSelectImages->setEnabled(false); } if (l.size() == 0) { prepareTools(nullptr); } else if (l.size() == 1) { prepareTools(l.at(0)); } else { /* For multiple selected objects we need to decide which tools to show. */ int firstType = l.at(0)->type(); bool allEqual = true; for (int i = 0; i < l.size(); ++i) { if (l.at(i)->type() != firstType) { allEqual = false; break; } } // qCDebug(KDENLIVE_LOG) << "All equal? " << allEqual << ".\n"; if (allEqual) { prepareTools(l.at(0)); } else { // Get the default toolset, but enable the property frame (x,y,w,h) prepareTools(nullptr); frame_properties->setEnabled(true); // Enable x/y/w/h if it makes sense. value_x->setEnabled(true); value_y->setEnabled(true); bool containsTextitem = false; for (int i = 0; i < l.size(); ++i) { if (l.at(i)->type() == TEXTITEM) { containsTextitem = true; break; } } if (!containsTextitem) { value_w->setEnabled(true); value_h->setEnabled(true); } } // Disable z index buttons if they don't make sense for the current selection int firstZindex = l.at(0)->zValue(); allEqual = true; for (int i = 0; i < l.size(); ++i) { if ((int)l[i]->zValue() != firstZindex) { allEqual = false; break; } } if (!allEqual) { zUp->setEnabled(false); zDown->setEnabled(false); } } } void TitleWidget::slotValueChanged(int type) { /* type tells us which QSpinBox value has changed. */ QList l = graphicsView->scene()->selectedItems(); // qCDebug(KDENLIVE_LOG) << l.size() << " items to be resized\n"; // Get the updated value here already to do less coding afterwards int val = 0; switch (type) { case ValueWidth: val = value_w->value(); break; case ValueHeight: val = value_h->value(); break; case ValueX: val = value_x->value(); break; case ValueY: val = value_y->value(); break; } for (int k = 0; k < l.size(); ++k) { // qCDebug(KDENLIVE_LOG) << "Type of item " << k << ": " << l.at(k)->type() << '\n'; if (l.at(k)->type() == TEXTITEM) { // Just update the position. We don't allow setting width/height for text items yet. switch (type) { case ValueX: updatePosition(l.at(k), val, l.at(k)->pos().y()); break; case ValueY: updatePosition(l.at(k), l.at(k)->pos().x(), val); break; } } else if (l.at(k)->type() == RECTITEM) { QGraphicsRectItem *rec = static_cast(l.at(k)); switch (type) { case ValueX: updatePosition(l.at(k), val, l.at(k)->pos().y()); break; case ValueY: updatePosition(l.at(k), l.at(k)->pos().x(), val); break; case ValueWidth: rec->setRect(QRect(0, 0, val, rec->rect().height())); break; case ValueHeight: rec->setRect(QRect(0, 0, rec->rect().width(), val)); break; } } else if (l.at(k)->type() == IMAGEITEM) { if (type == ValueX) { updatePosition(l.at(k), val, l.at(k)->pos().y()); } else if (type == ValueY) { updatePosition(l.at(k), l.at(k)->pos().x(), val); } else { // Width/height has changed. This is more complex. QGraphicsItem *i = l.at(k); Transform t = m_transformations.value(i); // Ratio width:height double phi = (double)i->boundingRect().width() / i->boundingRect().height(); // TODO: proper calculation for rotation around 3 axes double alpha = (double)t.rotatez / 180.0 * M_PI; // New length double length; // Scaling factor double scale = 1; // We want to keep the aspect ratio of the image as the user does not yet have the possibility // to restore the original ratio. You rarely want to change it anyway. switch (type) { case ValueWidth: // Add 0.5 because otherwise incrementing by 1 might have no effect length = val / (cos(alpha) + 1 / phi * sin(alpha)) + 0.5; scale = length / i->boundingRect().width(); break; case ValueHeight: length = val / (phi * sin(alpha) + cos(alpha)) + 0.5; scale = length / i->boundingRect().height(); break; } t.scalex = scale; t.scaley = scale; QTransform qtrans; qtrans.scale(scale, scale); qtrans.rotate(t.rotatex, Qt::XAxis); qtrans.rotate(t.rotatey, Qt::YAxis); qtrans.rotate(t.rotatez, Qt::ZAxis); i->setTransform(qtrans); // qCDebug(KDENLIVE_LOG) << "scale is: " << scale << '\n'; // qCDebug(KDENLIVE_LOG) << i->boundingRect().width() << ": new width\n"; m_transformations[i] = t; if (l.size() == 1) { // Only update the w/h values if the selection contains just one item. // Otherwise, what should we do? ;) // (Use the values of the first item? Of the second? Of the x-th?) updateDimension(i); // Update rotation/zoom values. // These values are not yet able to handle multiple items! updateRotZoom(i); } } } } } void TitleWidget::updateDimension(QGraphicsItem *i) { bool wBlocked = value_w->signalsBlocked(); bool hBlocked = value_h->signalsBlocked(); bool zBlocked = zValue->signalsBlocked(); value_w->blockSignals(true); value_h->blockSignals(true); zValue->blockSignals(true); zValue->setValue((int)i->zValue()); if (i->type() == IMAGEITEM) { // Get multipliers for rotation/scaling /*Transform t = m_transformations.value(i); QRectF r = i->boundingRect(); int width = (int) ( abs(r.width()*t.scalex * cos(t.rotate/180.0*M_PI)) + abs(r.height()*t.scaley * sin(t.rotate/180.0*M_PI)) ); int height = (int) ( abs(r.height()*t.scaley * cos(t.rotate/180*M_PI)) + abs(r.width()*t.scalex * sin(t.rotate/180*M_PI)) );*/ value_w->setValue(i->sceneBoundingRect().width()); value_h->setValue(i->sceneBoundingRect().height()); } else if (i->type() == RECTITEM) { QGraphicsRectItem *r = static_cast(i); // qCDebug(KDENLIVE_LOG) << "Rect width is: " << r->rect().width() << ", was: " << value_w->value() << '\n'; value_w->setValue((int)r->rect().width()); value_h->setValue((int)r->rect().height()); } else if (i->type() == TEXTITEM) { MyTextItem *t = static_cast(i); value_w->setValue((int)t->boundingRect().width()); value_h->setValue((int)t->boundingRect().height()); } zValue->blockSignals(zBlocked); value_w->blockSignals(wBlocked); value_h->blockSignals(hBlocked); } void TitleWidget::updateCoordinates(QGraphicsItem *i) { // Block signals emitted by this method value_x->blockSignals(true); value_y->blockSignals(true); if (i->type() == TEXTITEM) { MyTextItem *rec = static_cast(i); // Set the correct x coordinate value if (origin_x_left->isChecked()) { // Origin (0 point) is at m_frameWidth, coordinate axis is inverted value_x->setValue((int)(m_frameWidth - rec->pos().x() - rec->boundingRect().width())); } else { // Origin is at 0 (default) value_x->setValue((int)rec->pos().x()); } // Same for y if (origin_y_top->isChecked()) { value_y->setValue((int)(m_frameHeight - rec->pos().y() - rec->boundingRect().height())); } else { value_y->setValue((int)rec->pos().y()); } } else if (i->type() == RECTITEM) { QGraphicsRectItem *rec = static_cast(i); if (origin_x_left->isChecked()) { // Origin (0 point) is at m_frameWidth value_x->setValue((int)(m_frameWidth - rec->pos().x() - rec->rect().width())); } else { // Origin is at 0 (default) value_x->setValue((int)rec->pos().x()); } if (origin_y_top->isChecked()) { value_y->setValue((int)(m_frameHeight - rec->pos().y() - rec->rect().height())); } else { value_y->setValue((int)rec->pos().y()); } } else if (i->type() == IMAGEITEM) { if (origin_x_left->isChecked()) { value_x->setValue((int)(m_frameWidth - i->pos().x() - i->sceneBoundingRect().width())); } else { value_x->setValue((int)i->pos().x()); } if (origin_y_top->isChecked()) { value_y->setValue((int)(m_frameHeight - i->pos().y() - i->sceneBoundingRect().height())); } else { value_y->setValue((int)i->pos().y()); } } // Stop blocking signals now value_x->blockSignals(false); value_y->blockSignals(false); } void TitleWidget::updateRotZoom(QGraphicsItem *i) { itemzoom->blockSignals(true); itemrotatex->blockSignals(true); itemrotatey->blockSignals(true); itemrotatez->blockSignals(true); Transform t = m_transformations.value(i); if (!i->data(TitleDocument::ZoomFactor).isNull()) { itemzoom->setValue(i->data(TitleDocument::ZoomFactor).toInt()); } else { itemzoom->setValue((int)(t.scalex * 100.0 + 0.5)); } itemrotatex->setValue((int)(t.rotatex)); itemrotatey->setValue((int)(t.rotatey)); itemrotatez->setValue((int)(t.rotatez)); itemzoom->blockSignals(false); itemrotatex->blockSignals(false); itemrotatey->blockSignals(false); itemrotatez->blockSignals(false); } void TitleWidget::updatePosition(QGraphicsItem *i) { updatePosition(i, value_x->value(), value_y->value()); } void TitleWidget::updatePosition(QGraphicsItem *i, int x, int y) { if (i->type() == TEXTITEM) { MyTextItem *rec = static_cast(i); int posX; if (origin_x_left->isChecked()) { /* * Origin of the X axis is at m_frameWidth, and distance from right * border of the item to the right border of the frame is taken. See * comment to slotOriginXClicked(). */ posX = m_frameWidth - x - rec->boundingRect().width(); } else { posX = x; } int posY; if (origin_y_top->isChecked()) { /* Same for y axis */ posY = m_frameHeight - y - rec->boundingRect().height(); } else { posY = y; } rec->setPos(posX, posY); } else if (i->type() == RECTITEM) { QGraphicsRectItem *rec = static_cast(i); int posX; if (origin_x_left->isChecked()) { posX = m_frameWidth - x - rec->rect().width(); } else { posX = x; } int posY; if (origin_y_top->isChecked()) { posY = m_frameHeight - y - rec->rect().height(); } else { posY = y; } rec->setPos(posX, posY); } else if (i->type() == IMAGEITEM) { int posX; if (origin_x_left->isChecked()) { // Use the sceneBoundingRect because this also regards transformations like zoom posX = m_frameWidth - x - i->sceneBoundingRect().width(); } else { posX = x; } int posY; if (origin_y_top->isChecked()) { posY = m_frameHeight - y - i->sceneBoundingRect().height(); } else { posY = y; } i->setPos(posX, posY); } } void TitleWidget::updateTextOriginX() { if (origin_x_left->isChecked()) { origin_x_left->setText(i18n("\u2212X")); } else { origin_x_left->setText(i18n("+X")); } } void TitleWidget::slotOriginXClicked() { // Update the text displayed on the button. updateTextOriginX(); QList l = graphicsView->scene()->selectedItems(); if (l.size() >= 1) { updateCoordinates(l.at(0)); // Remember x axis setting l.at(0)->setData(TitleDocument::OriginXLeft, origin_x_left->isChecked() ? TitleDocument::AxisInverted : TitleDocument::AxisDefault); } graphicsView->setFocus(); } void TitleWidget::updateTextOriginY() { if (origin_y_top->isChecked()) { origin_y_top->setText(i18n("\u2212Y")); } else { origin_y_top->setText(i18n("+Y")); } } void TitleWidget::slotOriginYClicked() { // Update the text displayed on the button. updateTextOriginY(); QList l = graphicsView->scene()->selectedItems(); if (l.size() >= 1) { updateCoordinates(l.at(0)); l.at(0)->setData(TitleDocument::OriginYTop, origin_y_top->isChecked() ? TitleDocument::AxisInverted : TitleDocument::AxisDefault); } graphicsView->setFocus(); } void TitleWidget::updateAxisButtons(QGraphicsItem *i) { int xAxis = i->data(TitleDocument::OriginXLeft).toInt(); int yAxis = i->data(TitleDocument::OriginYTop).toInt(); origin_x_left->blockSignals(true); origin_y_top->blockSignals(true); if (xAxis == TitleDocument::AxisInverted) { origin_x_left->setChecked(true); } else { origin_x_left->setChecked(false); } updateTextOriginX(); if (yAxis == TitleDocument::AxisInverted) { origin_y_top->setChecked(true); } else { origin_y_top->setChecked(false); } updateTextOriginY(); origin_x_left->blockSignals(false); origin_y_top->blockSignals(false); } void TitleWidget::slotChangeBackground() { QColor color = backgroundColor->color(); m_scene->setBackgroundBrush(QBrush(color)); color.setAlpha(backgroundAlpha->value()); m_frameBackground->setBrush(QBrush(color)); } void TitleWidget::slotChanged() { QList l = graphicsView->scene()->selectedItems(); if (l.size() >= 1 && l.at(0)->type() == TEXTITEM) { textChanged(static_cast(l.at(0))); } } void TitleWidget::textChanged(MyTextItem *i) { /* * If the user has set origin_x_left (the same for y), we need to look * whether a text element has been selected. If yes, we need to ensure that * the right border of the text field remains fixed also when some text has * been entered. * * This is also known as right-justified, with the difference that it is not * valid for text but for its boundingRect. Text may still be * left-justified. */ updateDimension(i); if (origin_x_left->isChecked() || origin_y_top->isChecked()) { if (!i->document()->isEmpty()) { updatePosition(i); } else { /* * Don't do anything if the string is empty. If the position were * updated here, a newly created text field would be set to the * position of the last selected text field. */ } } // mbt 1607: Template text has changed; don't auto-select content anymore. if (i->property("isTemplate").isValid()) { if (i->property("templateText").isValid()) { if (i->property("templateText") == i->toHtml()) { // Unchanged, do nothing. } else { i->setProperty("isTemplate", QVariant::Invalid); i->setProperty("templateText", QVariant::Invalid); } } } } void TitleWidget::slotInsertUnicode() { m_unicodeDialog->exec(); } void TitleWidget::slotInsertUnicodeString(const QString &string) { const QList l = graphicsView->scene()->selectedItems(); if (!l.isEmpty()) { if (l.at(0)->type() == TEXTITEM) { MyTextItem *t = static_cast(l.at(0)); t->textCursor().insertText(string); } } } void TitleWidget::slotUpdateText() { QFont font = font_family->currentFont(); font.setPixelSize(font_size->value()); font.setItalic(buttonItalic->isChecked()); font.setUnderline(buttonUnder->isChecked()); font.setWeight(font_weight_box->itemData(font_weight_box->currentIndex()).toInt()); font.setLetterSpacing(QFont::AbsoluteSpacing, letter_spacing->value()); QColor color = fontColorButton->color(); QColor outlineColor = textOutlineColor->color(); QString gradientData; if (gradient_color->isChecked()) { // user wants a gradient gradientData = gradients_combo->currentData().toString(); } double outlineWidth = textOutline->value() / 10.0; int i; QList l = graphicsView->scene()->selectedItems(); for (i = 0; i < l.length(); ++i) { MyTextItem *item = nullptr; if (l.at(i)->type() == TEXTITEM) { item = static_cast(l.at(i)); } if (!item) { // No text item, try next one. continue; } // Set alignment of all text in the text item QTextCursor cur(item->document()); cur.select(QTextCursor::Document); QTextBlockFormat format = cur.blockFormat(); item->setData(TitleDocument::LineSpacing, line_spacing->value()); format.setLineHeight(line_spacing->value(), QTextBlockFormat::LineDistanceHeight); if (buttonAlignLeft->isChecked() || buttonAlignCenter->isChecked() || buttonAlignRight->isChecked()) { if (buttonAlignCenter->isChecked()) { item->setAlignment(Qt::AlignHCenter); } else if (buttonAlignRight->isChecked()) { item->setAlignment(Qt::AlignRight); } else if (buttonAlignLeft->isChecked()) { item->setAlignment(Qt::AlignLeft); } } else { item->setAlignment(Qt::AlignLeft); } // Set font properties item->setFont(font); QTextCharFormat cformat = cur.charFormat(); item->setData(TitleDocument::OutlineWidth, outlineWidth); item->setData(TitleDocument::OutlineColor, outlineColor); if (outlineWidth > 0.0) { cformat.setTextOutline(QPen(outlineColor, outlineWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); } if (gradientData.isEmpty()) { cformat.setForeground(QBrush(color)); } else { QLinearGradient gr = GradientWidget::gradientFromString(gradientData, item->boundingRect().width(), item->boundingRect().height()); cformat.setForeground(QBrush(gr)); } // Store gradient in item properties item->setData(TitleDocument::Gradient, gradientData); cur.setCharFormat(cformat); cur.setBlockFormat(format); // item->setTextCursor(cur); cur.clearSelection(); item->setTextCursor(cur); item->setTextColor(color); } } void TitleWidget::rectChanged() { QList l = graphicsView->scene()->selectedItems(); for (int i = 0; i < l.length(); ++i) { if (l.at(i)->type() == RECTITEM && (settingUp == 0)) { QGraphicsRectItem *rec = static_cast(l.at(i)); QColor f = rectFColor->color(); if (rectLineWidth->value() == 0) { rec->setPen(Qt::NoPen); } else { QPen penf(f); penf.setWidth(rectLineWidth->value()); penf.setJoinStyle(Qt::RoundJoin); rec->setPen(penf); } if (plain_rect->isChecked()) { rec->setBrush(QBrush(rectBColor->color())); rec->setData(TitleDocument::Gradient, QVariant()); } else { // gradient QString gradientData = gradients_rect_combo->currentData().toString(); rec->setData(TitleDocument::Gradient, gradientData); QLinearGradient gr = GradientWidget::gradientFromString(gradientData, rec->boundingRect().width(), rec->boundingRect().height()); rec->setBrush(QBrush(gr)); } } } } void TitleWidget::itemScaled(int val) { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { Transform x = m_transformations.value(l.at(0)); x.scalex = (double)val / 100.0; x.scaley = (double)val / 100.0; QTransform qtrans; qtrans.scale(x.scalex, x.scaley); qtrans.rotate(x.rotatex, Qt::XAxis); qtrans.rotate(x.rotatey, Qt::YAxis); qtrans.rotate(x.rotatez, Qt::ZAxis); l[0]->setTransform(qtrans); l[0]->setData(TitleDocument::ZoomFactor, val); m_transformations[l.at(0)] = x; updateDimension(l.at(0)); } } void TitleWidget::itemRotateX(int val) { itemRotate(val, 0); } void TitleWidget::itemRotateY(int val) { itemRotate(val, 1); } void TitleWidget::itemRotateZ(int val) { itemRotate(val, 2); } void TitleWidget::itemRotate(int val, int axis) { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { Transform x = m_transformations[l.at(0)]; switch (axis) { case 0: x.rotatex = val; break; case 1: x.rotatey = val; break; case 2: x.rotatez = val; break; } l[0]->setData(TitleDocument::RotateFactor, QList() << QVariant(x.rotatex) << QVariant(x.rotatey) << QVariant(x.rotatez)); QTransform qtrans; qtrans.scale(x.scalex, x.scaley); qtrans.rotate(x.rotatex, Qt::XAxis); qtrans.rotate(x.rotatey, Qt::YAxis); qtrans.rotate(x.rotatez, Qt::ZAxis); l[0]->setTransform(qtrans); m_transformations[l.at(0)] = x; if (l[0]->data(TitleDocument::ZoomFactor).isNull()) { l[0]->setData(TitleDocument::ZoomFactor, 100); } updateDimension(l.at(0)); } } void TitleWidget::itemHCenter() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); int width = (int)br.width(); int newPos = (int)((m_frameWidth - width) / 2); newPos += item->pos().x() - br.left(); // Check item transformation item->setPos(newPos, item->pos().y()); updateCoordinates(item); } } void TitleWidget::itemVCenter() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); int height = (int)br.height(); int newPos = (int)((m_frameHeight - height) / 2); newPos += item->pos().y() - br.top(); // Check item transformation item->setPos(item->pos().x(), newPos); updateCoordinates(item); } } void TitleWidget::itemTop() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); double diff; if (br.top() > 0) { diff = -br.top(); } else { diff = -br.bottom(); } item->moveBy(0, diff); updateCoordinates(item); } } void TitleWidget::itemBottom() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); double diff; if (br.bottom() > m_frameHeight) { diff = m_frameHeight - br.top(); } else { diff = m_frameHeight - br.bottom(); } item->moveBy(0, diff); updateCoordinates(item); } } void TitleWidget::itemLeft() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); double diff; if (br.left() > 0) { diff = -br.left(); } else { diff = -br.right(); } item->moveBy(diff, 0); updateCoordinates(item); } } void TitleWidget::itemRight() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); double diff; if (br.right() < m_frameWidth) { diff = m_frameWidth - br.right(); } else { diff = m_frameWidth - br.left(); } item->moveBy(diff, 0); updateCoordinates(item); } } void TitleWidget::loadTitle(QUrl url) { if (!url.isValid()) { QString startFolder = KRecentDirs::dir(QStringLiteral(":KdenliveProjectsTitles")); url = QFileDialog::getOpenFileUrl(this, i18n("Load Title"), QUrl::fromLocalFile(startFolder.isEmpty() ? m_projectTitlePath : startFolder), i18n("Kdenlive title (*.kdenlivetitle)")); } if (url.isValid()) { // make sure we don't delete the guides qDeleteAll(m_guides); m_guides.clear(); QList items = m_scene->items(); items.removeAll(m_frameBorder); items.removeAll(m_frameBackground); items.removeAll(m_frameImage); for (int i = 0; i < items.size(); ++i) { if (items.at(i)->zValue() > -1000) { delete items.at(i); } } m_scene->clearTextSelection(); QDomDocument doc; QFile file(url.toLocalFile()); doc.setContent(&file, false); file.close(); setXml(doc); updateGuides(0); m_projectTitlePath = QFileInfo(file).dir().absolutePath(); KRecentDirs::add(QStringLiteral(":KdenliveProjectsTitles"), m_projectTitlePath); } } void TitleWidget::saveTitle(QUrl url) { if (anim_start->isChecked()) { slotAnimStart(false); } if (anim_end->isChecked()) { slotAnimEnd(false); } bool embed_image = false; // If we have images in the title, ask for embed QList list = graphicsView->scene()->items(); QGraphicsPixmapItem pix; int pixmapType = pix.type(); for (const QGraphicsItem *item : list) { if (item->type() == pixmapType && item != m_frameImage) { embed_image = true; break; } } if (embed_image && KMessageBox::questionYesNo( this, i18n("Do you want to embed Images into this TitleDocument?\nThis is most needed for sharing Titles.")) != KMessageBox::Yes) { embed_image = false; } if (!url.isValid()) { QPointer fs = new QFileDialog(this, i18n("Save Title"), m_projectTitlePath); fs->setMimeTypeFilters(QStringList() << QStringLiteral("application/x-kdenlivetitle")); fs->setFileMode(QFileDialog::AnyFile); fs->setAcceptMode(QFileDialog::AcceptSave); fs->setDefaultSuffix(QStringLiteral("kdenlivetitle")); // TODO: KF5 porting? // fs->setConfirmOverwrite(true); // fs->setKeepLocation(true); if ((fs->exec() != 0) && !fs->selectedUrls().isEmpty()) { url = fs->selectedUrls().constFirst(); } delete fs; } if (url.isValid()) { if (!m_titledocument.saveDocument(url, m_startViewport, m_endViewport, m_tc.getFrameCount(title_duration->text()), embed_image)) { KMessageBox::error(this, i18n("Cannot write to file %1", url.toLocalFile())); } } } void TitleWidget::downloadTitleTemplates() { if (getNewStuff(QStringLiteral(":data/kdenlive_titles.knsrc")) > 0) { refreshTitleTemplates(m_projectTitlePath); refreshTemplateBoxContents(); } } int TitleWidget::getNewStuff(const QString &configFile) { KNS3::Entry::List entries; QPointer dialog = new KNS3::DownloadDialog(configFile); if (dialog->exec() != 0) { entries = dialog->changedEntries(); } for (const KNS3::Entry &entry : entries) { if (entry.status() == KNS3::Entry::Installed) { qCDebug(KDENLIVE_LOG) << "// Installed files: " << entry.installedFiles(); } } delete dialog; return entries.size(); } QDomDocument TitleWidget::xml() { QDomDocument doc = m_titledocument.xml(m_startViewport, m_endViewport); doc.documentElement().setAttribute(QStringLiteral("duration"), m_tc.getFrameCount(title_duration->text())); doc.documentElement().setAttribute(QStringLiteral("out"), m_tc.getFrameCount(title_duration->text())); return doc; } int TitleWidget::duration() const { return m_tc.getFrameCount(title_duration->text()); } void TitleWidget::setXml(const QDomDocument &doc, const QString &id) { m_clipId = id; int duration; + if (m_missingMessage) { + delete m_missingMessage; + m_missingMessage = nullptr; + } m_count = m_titledocument.loadFromXml(doc, m_startViewport, m_endViewport, &duration, m_projectTitlePath); adjustFrameSize(); + if (m_titledocument.invalidCount() > 0) { + m_missingMessage = new KMessageWidget(this); + m_missingMessage->setCloseButtonVisible(true); + m_missingMessage->setWordWrap(true); + m_missingMessage->setMessageType(KMessageWidget::Warning); + m_missingMessage->setText(i18n("This title has %1 missing elements", m_titledocument.invalidCount())); + QAction *action = new QAction(i18n("Delete missing elements")); + m_missingMessage->addAction(action); + connect(action, &QAction::triggered, this, &TitleWidget::deleteMissingItems); + messageLayout->addWidget(m_missingMessage); + m_missingMessage->animatedShow(); + } title_duration->setText(m_tc.getTimecode(GenTime(duration, m_fps))); /*if (doc.documentElement().hasAttribute("out")) { GenTime duration = GenTime(doc.documentElement().attribute("out").toDouble() / 1000.0); title_duration->setText(m_tc.getTimecode(duration)); } else title_duration->setText(m_tc.getTimecode(GenTime(5000)));*/ QDomElement e = doc.documentElement(); m_transformations.clear(); QList items = graphicsView->scene()->items(); const double PI = 4.0 * atan(1.0); for (int i = 0; i < items.count(); ++i) { QTransform t = items.at(i)->transform(); Transform x; x.scalex = t.m11(); x.scaley = t.m22(); if (!items.at(i)->data(TitleDocument::RotateFactor).isNull()) { QList rotlist = items.at(i)->data(TitleDocument::RotateFactor).toList(); if (rotlist.count() >= 3) { x.rotatex = rotlist[0].toDouble(); x.rotatey = rotlist[1].toDouble(); x.rotatez = rotlist[2].toDouble(); // Try to adjust zoom t.rotate(x.rotatex * (-1), Qt::XAxis); t.rotate(x.rotatey * (-1), Qt::YAxis); t.rotate(x.rotatez * (-1), Qt::ZAxis); x.scalex = t.m11(); x.scaley = t.m22(); } else { x.rotatex = 0; x.rotatey = 0; x.rotatez = 0; } } else { x.rotatex = 0; x.rotatey = 0; x.rotatez = 180. / PI * atan2(-t.m21(), t.m11()); } m_transformations[items.at(i)] = x; } // mbd: Update the GUI color selectors to match the stuff from the loaded document QColor background_color = m_titledocument.getBackgroundColor(); backgroundAlpha->blockSignals(true); backgroundColor->blockSignals(true); backgroundAlpha->setValue(background_color.alpha()); background_color.setAlpha(255); backgroundColor->setColor(background_color); backgroundAlpha->blockSignals(false); backgroundColor->blockSignals(false); /*startViewportX->setValue(m_startViewport->data(0).toInt()); startViewportY->setValue(m_startViewport->data(1).toInt()); startViewportSize->setValue(m_startViewport->data(2).toInt()); endViewportX->setValue(m_endViewport->data(0).toInt()); endViewportY->setValue(m_endViewport->data(1).toInt()); endViewportSize->setValue(m_endViewport->data(2).toInt());*/ QTimer::singleShot(200, this, &TitleWidget::slotAdjustZoom); slotSelectTool(); selectionChanged(); } void TitleWidget::slotAccepted() { if (anim_start->isChecked()) { slotAnimStart(false); } if (anim_end->isChecked()) { slotAnimEnd(false); } writeChoices(); } +void TitleWidget::deleteMissingItems() +{ + m_missingMessage->animatedHide(); + QList items = graphicsView->scene()->items(); + QList toDelete; + for (int i = 0; i < items.count(); ++i) { + if (items.at(i)->data(Qt::UserRole + 2).toInt() == 1) { + // We found a missing item + toDelete << items.at(i); + } + } + if (toDelete.size() != m_titledocument.invalidCount()) { + qDebug()<<"/// WARNING, INCOHERENT MISSING ELEMENTS in title: "<currentFont()); // titleConfig.writeEntry("font_size", font_size->value()); titleConfig.writeEntry("font_pixel_size", font_size->value()); titleConfig.writeEntry("font_color", fontColorButton->color()); titleConfig.writeEntry("font_outline_color", textOutlineColor->color()); titleConfig.writeEntry("font_outline", textOutline->value()); titleConfig.writeEntry("font_weight", font_weight_box->itemData(font_weight_box->currentIndex()).toInt()); titleConfig.writeEntry("font_italic", buttonItalic->isChecked()); titleConfig.writeEntry("font_underlined", buttonUnder->isChecked()); titleConfig.writeEntry("rect_background_color", rectBColor->color()); titleConfig.writeEntry("rect_foreground_color", rectFColor->color()); titleConfig.writeEntry("rect_background_alpha", rectBColor->color().alpha()); titleConfig.writeEntry("rect_foreground_alpha", rectFColor->color().alpha()); titleConfig.writeEntry("rect_line_width", rectLineWidth->value()); titleConfig.writeEntry("background_color", backgroundColor->color()); titleConfig.writeEntry("background_alpha", backgroundAlpha->value()); titleConfig.writeEntry("use_grid", use_grid->isChecked()); //! \todo Not sure if I should sync - it is probably safe to do it config->sync(); } void TitleWidget::readChoices() { // Get a pointer to a shared configuration instance, then get the TitleWidget group. KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup titleConfig(config, "TitleWidget"); // read the entries const QByteArray geometry = titleConfig.readEntry("dialog_geometry", QByteArray()); restoreGeometry(QByteArray::fromBase64(geometry)); font_family->setCurrentFont(titleConfig.readEntry("font_family", font_family->currentFont())); font_size->setValue(titleConfig.readEntry("font_pixel_size", font_size->value())); m_scene->slotUpdateFontSize(font_size->value()); QColor fontColor = QColor(titleConfig.readEntry("font_color", fontColorButton->color())); QColor outlineColor = QColor(titleConfig.readEntry("font_outline_color", textOutlineColor->color())); fontColor.setAlpha(titleConfig.readEntry("font_alpha", fontColor.alpha())); outlineColor.setAlpha(titleConfig.readEntry("font_outline_alpha", outlineColor.alpha())); fontColorButton->setColor(fontColor); textOutlineColor->setColor(outlineColor); textOutline->setValue(titleConfig.readEntry("font_outline", textOutline->value())); int weight; if (titleConfig.readEntry("font_bold", false)) { weight = QFont::Bold; } else { weight = titleConfig.readEntry("font_weight", font_weight_box->itemData(font_weight_box->currentIndex()).toInt()); } setFontBoxWeight(weight); buttonItalic->setChecked(titleConfig.readEntry("font_italic", buttonItalic->isChecked())); buttonUnder->setChecked(titleConfig.readEntry("font_underlined", buttonUnder->isChecked())); QColor fgColor = QColor(titleConfig.readEntry("rect_foreground_color", rectFColor->color())); QColor bgColor = QColor(titleConfig.readEntry("rect_background_color", rectBColor->color())); fgColor.setAlpha(titleConfig.readEntry("rect_foreground_alpha", fgColor.alpha())); bgColor.setAlpha(titleConfig.readEntry("rect_background_alpha", bgColor.alpha())); rectFColor->setColor(fgColor); rectBColor->setColor(bgColor); rectLineWidth->setValue(titleConfig.readEntry("rect_line_width", rectLineWidth->value())); backgroundColor->setColor(titleConfig.readEntry("background_color", backgroundColor->color())); backgroundAlpha->setValue(titleConfig.readEntry("background_alpha", backgroundAlpha->value())); use_grid->setChecked(titleConfig.readEntry("use_grid", false)); m_scene->slotUseGrid(use_grid->isChecked()); } void TitleWidget::adjustFrameSize() { m_frameWidth = m_titledocument.frameWidth(); m_frameHeight = m_titledocument.frameHeight(); m_frameBorder->setRect(0, 0, m_frameWidth, m_frameHeight); displayBackgroundFrame(); } void TitleWidget::slotAnimStart(bool anim) { if (anim && anim_end->isChecked()) { anim_end->setChecked(false); m_endViewport->setZValue(-1000); m_endViewport->setBrush(QBrush()); } slotSelectTool(); QList list = m_scene->items(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->zValue() > -1000) { if (!list.at(i)->data(-1).isNull()) { continue; } list.at(i)->setFlag(QGraphicsItem::ItemIsMovable, !anim); list.at(i)->setFlag(QGraphicsItem::ItemIsSelectable, !anim); } } align_box->setEnabled(anim); itemzoom->setEnabled(!anim); itemrotatex->setEnabled(!anim); itemrotatey->setEnabled(!anim); itemrotatez->setEnabled(!anim); frame_toolbar->setEnabled(!anim); toolbar_stack->setEnabled(!anim); if (anim) { keep_aspect->setChecked(!m_startViewport->data(0).isNull()); m_startViewport->setZValue(1100); QColor col = m_startViewport->pen().color(); col.setAlpha(100); m_startViewport->setBrush(col); m_startViewport->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); m_startViewport->setSelected(true); selectionChanged(); slotSelectTool(); if (m_startViewport->childItems().isEmpty()) { addAnimInfoText(); } } else { m_startViewport->setZValue(-1000); m_startViewport->setBrush(QBrush()); m_startViewport->setFlags(nullptr); if (!anim_end->isChecked()) { deleteAnimInfoText(); } } } void TitleWidget::slotAnimEnd(bool anim) { if (anim && anim_start->isChecked()) { anim_start->setChecked(false); m_startViewport->setZValue(-1000); m_startViewport->setBrush(QBrush()); } slotSelectTool(); QList list = m_scene->items(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->zValue() > -1000) { if (!list.at(i)->data(-1).isNull()) { continue; } list.at(i)->setFlag(QGraphicsItem::ItemIsMovable, !anim); list.at(i)->setFlag(QGraphicsItem::ItemIsSelectable, !anim); } } align_box->setEnabled(anim); itemzoom->setEnabled(!anim); itemrotatex->setEnabled(!anim); itemrotatey->setEnabled(!anim); itemrotatez->setEnabled(!anim); frame_toolbar->setEnabled(!anim); toolbar_stack->setEnabled(!anim); if (anim) { keep_aspect->setChecked(!m_endViewport->data(0).isNull()); m_endViewport->setZValue(1100); QColor col = m_endViewport->pen().color(); col.setAlpha(100); m_endViewport->setBrush(col); m_endViewport->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); m_endViewport->setSelected(true); selectionChanged(); slotSelectTool(); if (m_endViewport->childItems().isEmpty()) { addAnimInfoText(); } } else { m_endViewport->setZValue(-1000); m_endViewport->setBrush(QBrush()); m_endViewport->setFlags(nullptr); if (!anim_start->isChecked()) { deleteAnimInfoText(); } } } void TitleWidget::addAnimInfoText() { // add text to anim viewport QGraphicsTextItem *t = new QGraphicsTextItem(i18nc("Indicates the start of an animation", "Start"), m_startViewport); QGraphicsTextItem *t2 = new QGraphicsTextItem(i18nc("Indicates the end of an animation", "End"), m_endViewport); QFont font = t->font(); font.setPixelSize(m_startViewport->rect().width() / 10); QColor col = m_startViewport->pen().color(); col.setAlpha(255); t->setDefaultTextColor(col); t->setFont(font); font.setPixelSize(m_endViewport->rect().width() / 10); col = m_endViewport->pen().color(); col.setAlpha(255); t2->setDefaultTextColor(col); t2->setFont(font); } void TitleWidget::updateInfoText() { // update info text font if (!m_startViewport->childItems().isEmpty()) { MyTextItem *item = static_cast(m_startViewport->childItems().at(0)); if (item) { QFont font = item->font(); font.setPixelSize(m_startViewport->rect().width() / 10); item->setFont(font); } } if (!m_endViewport->childItems().isEmpty()) { MyTextItem *item = static_cast(m_endViewport->childItems().at(0)); if (item) { QFont font = item->font(); font.setPixelSize(m_endViewport->rect().width() / 10); item->setFont(font); } } } void TitleWidget::deleteAnimInfoText() { // end animation editing, remove info text while (!m_startViewport->childItems().isEmpty()) { QGraphicsItem *item = m_startViewport->childItems().at(0); if (m_scene) { m_scene->removeItem(item); } } while (!m_endViewport->childItems().isEmpty()) { QGraphicsItem *item = m_endViewport->childItems().at(0); if (m_scene) { m_scene->removeItem(item); } } } void TitleWidget::slotKeepAspect(bool keep) { if ((int)m_endViewport->zValue() == 1100) { m_endViewport->setData(0, keep ? m_frameWidth : QVariant()); m_endViewport->setData(1, keep ? m_frameHeight : QVariant()); } else { m_startViewport->setData(0, keep ? m_frameWidth : QVariant()); m_startViewport->setData(1, keep ? m_frameHeight : QVariant()); } } void TitleWidget::slotResize50() { if ((int)m_endViewport->zValue() == 1100) { m_endViewport->setRect(0, 0, m_frameWidth / 2, m_frameHeight / 2); } else { m_startViewport->setRect(0, 0, m_frameWidth / 2, m_frameHeight / 2); } } void TitleWidget::slotResize100() { if ((int)m_endViewport->zValue() == 1100) { m_endViewport->setRect(0, 0, m_frameWidth, m_frameHeight); } else { m_startViewport->setRect(0, 0, m_frameWidth, m_frameHeight); } } void TitleWidget::slotResize200() { if ((int)m_endViewport->zValue() == 1100) { m_endViewport->setRect(0, 0, m_frameWidth * 2, m_frameHeight * 2); } else { m_startViewport->setRect(0, 0, m_frameWidth * 2, m_frameHeight * 2); } } void TitleWidget::slotAddEffect(int /*ix*/) { QList list = graphicsView->scene()->selectedItems(); /* int effect = effect_list->itemData(ix).toInt(); if (list.size() == 1) { if (effect == NOEFFECT) effect_stack->setHidden(true); else { effect_stack->setCurrentIndex(effect - 1); effect_stack->setHidden(false); } } else // Hide the effects stack when more than one element is selected. effect_stack->setHidden(true); for (QGraphicsItem * item : list) { switch (effect) { case NOEFFECT: item->setData(100, QVariant()); item->setGraphicsEffect(0); break; case TYPEWRITEREFFECT: if (item->type() == TEXTITEM) { QStringList effdata = QStringList() << QStringLiteral("typewriter") << QString::number(typewriter_delay->value()) + QLatin1Char(';') + QString::number(typewriter_start->value()); item->setData(100, effdata); } break; // Do not remove the non-QGraphicsEffects. case BLUREFFECT: item->setGraphicsEffect(new QGraphicsBlurEffect()); break; case SHADOWEFFECT: item->setGraphicsEffect(new QGraphicsDropShadowEffect()); break; } }*/ } void TitleWidget::slotEditTypewriter(int /*ix*/) { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QStringList effdata = QStringList() << QStringLiteral("typewriter") << QString::number(typewriter_delay->value()) + QLatin1Char(';') + QString::number(typewriter_start->value()); l[0]->setData(100, effdata); } } qreal TitleWidget::zIndexBounds(bool maxBound, bool intersectingOnly) { qreal bound = maxBound ? -99 : 99; const QList l = graphicsView->scene()->selectedItems(); if (!l.isEmpty()) { QList lItems; // Get items (all or intersecting only) if (intersectingOnly) { lItems = graphicsView->scene()->items(l[0]->sceneBoundingRect(), Qt::IntersectsItemShape); } else { lItems = graphicsView->scene()->items(); } if (!lItems.isEmpty()) { int n = lItems.size(); qreal z; if (maxBound) { for (int i = 0; i < n; ++i) { z = lItems[i]->zValue(); if (z > bound && !lItems[i]->isSelected()) { bound = z; } else if (z - 1 > bound) { // To get the maximum index even if it is of an item of the current selection. // Used when updating multiple items, to get all to the same level. // Otherwise, the maximum would stay at -99 if the highest item is in the selection. bound = z - 1; } } } else { // Get minimum z index. for (int i = 0; i < n; ++i) { z = lItems[i]->zValue(); if (z < bound && !lItems[i]->isSelected() && z > -999) { // There are items at the very bottom (background e.g.) with z-index < -1000. bound = z; } else if (z + 1 < bound && z > -999) { bound = z + 1; } } } } } return bound; } void TitleWidget::slotZIndexUp() { QList l = graphicsView->scene()->selectedItems(); if (l.size() >= 1) { qreal currentZ = l[0]->zValue(); qreal max = zIndexBounds(true, true); if (currentZ <= max) { l[0]->setZValue(currentZ + 1); updateDimension(l[0]); } } } void TitleWidget::slotZIndexTop() { QList l = graphicsView->scene()->selectedItems(); qreal max = zIndexBounds(true, false); // qCDebug(KDENLIVE_LOG) << "Max z-index is " << max << ".\n"; for (int i = 0; i < l.size(); ++i) { qreal currentZ = l[i]->zValue(); if (currentZ <= max) { // qCDebug(KDENLIVE_LOG) << "Updating item " << i << ", is " << currentZ << ".\n"; l[i]->setZValue(max + 1); } else { // qCDebug(KDENLIVE_LOG) << "Not updating " << i << ", is " << currentZ << ".\n"; } } // Update the z index value in the GUI if (!l.isEmpty()) { updateDimension(l[0]); } } void TitleWidget::slotZIndexDown() { QList l = graphicsView->scene()->selectedItems(); if (l.size() >= 1) { qreal currentZ = l[0]->zValue(); qreal min = zIndexBounds(false, true); if (currentZ >= min) { l[0]->setZValue(currentZ - 1); updateDimension(l[0]); } } } void TitleWidget::slotZIndexBottom() { QList l = graphicsView->scene()->selectedItems(); qreal min = zIndexBounds(false, false); for (int i = 0; i < l.size(); ++i) { qreal currentZ = l[i]->zValue(); if (currentZ >= min) { l[i]->setZValue(min - 1); } } // Update the z index value in the GUI if (!l.isEmpty()) { updateDimension(l[0]); } } void TitleWidget::slotSelectAll() { QList l = graphicsView->scene()->items(); for (int i = 0; i < l.size(); ++i) { l.at(i)->setSelected(true); } } void TitleWidget::selectItems(int itemType) { QList l; if (!graphicsView->scene()->selectedItems().isEmpty()) { l = graphicsView->scene()->selectedItems(); for (int i = 0; i < l.size(); ++i) { if (l.at(i)->type() != itemType) { l.at(i)->setSelected(false); } } } else { l = graphicsView->scene()->items(); for (int i = 0; i < l.size(); ++i) { if (l.at(i)->type() == itemType) { l.at(i)->setSelected(true); } } } } void TitleWidget::slotSelectText() { selectItems(TEXTITEM); } void TitleWidget::slotSelectRects() { selectItems(RECTITEM); } void TitleWidget::slotSelectImages() { selectItems(IMAGEITEM); } void TitleWidget::slotSelectNone() { graphicsView->blockSignals(true); QList l = graphicsView->scene()->items(); for (int i = 0; i < l.size(); ++i) { l.at(i)->setSelected(false); } graphicsView->blockSignals(false); selectionChanged(); } QString TitleWidget::getTooltipWithShortcut(const QString &tipText, QAction *button) { return tipText + QStringLiteral(" ") + button->shortcut().toString() + QStringLiteral(""); } void TitleWidget::prepareTools(QGraphicsItem *referenceItem) { // Let some GUI elements block signals. We may want to change their values without any sideeffects. // Additionally, store the previous blocking state to avoid side effects when this function is called from within another one. // Note: Disabling an element also blocks signals. So disabled elements don't need to be set to blocking too. bool blockOX = origin_x_left->signalsBlocked(); bool blockOY = origin_y_top->signalsBlocked(); bool blockEff = effect_list->signalsBlocked(); bool blockRX = itemrotatex->signalsBlocked(); bool blockRY = itemrotatey->signalsBlocked(); bool blockRZ = itemrotatez->signalsBlocked(); bool blockZoom = itemzoom->signalsBlocked(); bool blockX = value_x->signalsBlocked(); bool blockY = value_y->signalsBlocked(); bool blockW = value_w->signalsBlocked(); bool blockH = value_h->signalsBlocked(); origin_x_left->blockSignals(true); origin_y_top->blockSignals(true); effect_list->blockSignals(true); itemrotatex->blockSignals(true); itemrotatey->blockSignals(true); itemrotatez->blockSignals(true); itemzoom->blockSignals(true); value_x->blockSignals(true); value_y->blockSignals(true); value_w->blockSignals(true); value_h->blockSignals(true); if (referenceItem == nullptr) { // qCDebug(KDENLIVE_LOG) << "nullptr item.\n"; effect_list->setCurrentIndex(0); origin_x_left->setChecked(false); origin_y_top->setChecked(false); updateTextOriginX(); updateTextOriginY(); enableToolbars(TITLE_SELECT); showToolbars(TITLE_SELECT); itemzoom->setEnabled(false); itemrotatex->setEnabled(false); itemrotatey->setEnabled(false); itemrotatez->setEnabled(false); frame_properties->setEnabled(false); toolbar_stack->setEnabled(false); /*letter_spacing->setEnabled(false); line_spacing->setEnabled(false); letter_spacing->setValue(0); line_spacing->setValue(0);*/ } else { toolbar_stack->setEnabled(true); frame_properties->setEnabled(true); if (referenceItem != m_startViewport && referenceItem != m_endViewport) { itemzoom->setEnabled(true); itemrotatex->setEnabled(true); itemrotatey->setEnabled(true); itemrotatez->setEnabled(true); } else { itemzoom->setEnabled(false); itemrotatex->setEnabled(false); itemrotatey->setEnabled(false); itemrotatez->setEnabled(false); updateInfoText(); } letter_spacing->setEnabled(referenceItem->type() == TEXTITEM); line_spacing->setEnabled(referenceItem->type() == TEXTITEM); if (referenceItem->type() == TEXTITEM) { showToolbars(TITLE_TEXT); MyTextItem *i = static_cast(referenceItem); if (!i->document()->isEmpty()) { // We have an existing text item selected if (!i->data(100).isNull()) { // Item has an effect QStringList effdata = i->data(100).toStringList(); QString effectName = effdata.takeFirst(); if (effectName == QLatin1String("typewriter")) { QStringList params = effdata.at(0).split(QLatin1Char(';')); typewriter_delay->setValue(params.at(0).toInt()); typewriter_start->setValue(params.at(1).toInt()); effect_list->setCurrentIndex(effect_list->findData((int)TYPEWRITEREFFECT)); } } else { /*if (i->graphicsEffect()) { QGraphicsBlurEffect *blur = static_cast (i->graphicsEffect()); if (blur) { effect_list->setCurrentIndex(effect_list->findData((int) BLUREFFECT)); int rad = (int) blur->blurRadius(); blur_radius->setValue(rad); effect_stack->setHidden(false); } else { QGraphicsDropShadowEffect *shad = static_cast (i->graphicsEffect()); if (shad) { effect_list->setCurrentIndex(effect_list->findData((int) SHADOWEFFECT)); shadow_radius->setValue(shad->blurRadius()); shadow_x->setValue(shad->xOffset()); shadow_y->setValue(shad->yOffset()); effect_stack->setHidden(false); } } } else { effect_list->setCurrentIndex(effect_list->findData((int) NOEFFECT)); effect_stack->setHidden(true); }*/ } font_size->blockSignals(true); font_family->blockSignals(true); font_weight_box->blockSignals(true); buttonItalic->blockSignals(true); buttonUnder->blockSignals(true); fontColorButton->blockSignals(true); buttonAlignLeft->blockSignals(true); buttonAlignRight->blockSignals(true); buttonAlignCenter->blockSignals(true); QFont font = i->font(); font_family->setCurrentFont(font); font_size->setValue(font.pixelSize()); m_scene->slotUpdateFontSize(font.pixelSize()); buttonItalic->setChecked(font.italic()); buttonUnder->setChecked(font.underline()); setFontBoxWeight(font.weight()); QTextCursor cursor(i->document()); cursor.select(QTextCursor::Document); QColor color = cursor.charFormat().foreground().color(); fontColorButton->setColor(color); if (!i->data(TitleDocument::OutlineWidth).isNull()) { textOutline->blockSignals(true); textOutline->setValue(i->data(TitleDocument::OutlineWidth).toDouble() * 10); textOutline->blockSignals(false); } else { textOutline->blockSignals(true); textOutline->setValue(0); textOutline->blockSignals(false); } if (!i->data(TitleDocument::OutlineColor).isNull()) { textOutlineColor->blockSignals(true); QVariant variant = i->data(TitleDocument::OutlineColor); color = variant.value(); textOutlineColor->setColor(color); textOutlineColor->blockSignals(false); } if (!i->data(TitleDocument::Gradient).isNull()) { gradients_combo->blockSignals(true); gradient_color->setChecked(true); QString gradientData = i->data(TitleDocument::Gradient).toString(); int ix = gradients_combo->findData(gradientData); if (ix == -1) { // This gradient does not exist in our settings, store it storeGradient(gradientData); ix = gradients_combo->findData(gradientData); } gradients_combo->setCurrentIndex(ix); gradients_combo->blockSignals(false); } else { plain_color->setChecked(true); } if (i->alignment() == Qt::AlignHCenter) { buttonAlignCenter->setChecked(true); } else if (i->alignment() == Qt::AlignRight) { buttonAlignRight->setChecked(true); } else { buttonAlignLeft->setChecked(true); } QStringList sInfo = i->shadowInfo(); if (sInfo.count() >= 5) { shadowBox->setChecked(static_cast(sInfo.at(0).toInt())); shadowBox->blockSignals(true); shadowColor->setColor(QColor(sInfo.at(1))); blur_radius->setValue(sInfo.at(2).toInt()); shadowX->setValue(sInfo.at(3).toInt()); shadowY->setValue(sInfo.at(4).toInt()); shadowBox->blockSignals(false); } letter_spacing->blockSignals(true); line_spacing->blockSignals(true); QTextCursor cur = i->textCursor(); QTextBlockFormat format = cur.blockFormat(); letter_spacing->setValue(font.letterSpacing()); line_spacing->setValue(format.lineHeight()); letter_spacing->blockSignals(false); line_spacing->blockSignals(false); font_size->blockSignals(false); font_family->blockSignals(false); font_weight_box->blockSignals(false); buttonItalic->blockSignals(false); buttonUnder->blockSignals(false); fontColorButton->blockSignals(false); buttonAlignLeft->blockSignals(false); buttonAlignRight->blockSignals(false); buttonAlignCenter->blockSignals(false); // mbt 1607: Select text if the text item is an unchanged template item. if (i->property("isTemplate").isValid()) { cur.setPosition(0, QTextCursor::MoveAnchor); cur.select(QTextCursor::Document); i->setTextCursor(cur); // Make text editable now. i->grabKeyboard(); i->setTextInteractionFlags(Qt::TextEditorInteraction); } } updateAxisButtons(i); updateCoordinates(i); updateDimension(i); enableToolbars(TITLE_TEXT); } else if ((referenceItem)->type() == RECTITEM) { showToolbars(TITLE_RECTANGLE); settingUp = 1; QGraphicsRectItem *rec = static_cast(referenceItem); if (rec == m_startViewport || rec == m_endViewport) { enableToolbars(TITLE_SELECT); } else { QColor fcol = rec->pen().color(); QColor bcol = rec->brush().color(); rectFColor->setColor(fcol); QString gradientData = rec->data(TitleDocument::Gradient).toString(); if (gradientData.isEmpty()) { plain_rect->setChecked(true); rectBColor->setColor(bcol); } else { gradient_rect->setChecked(true); gradients_rect_combo->blockSignals(true); int ix = gradients_rect_combo->findData(gradientData); if (ix == -1) { storeGradient(gradientData); ix = gradients_rect_combo->findData(gradientData); } gradients_rect_combo->setCurrentIndex(ix); gradients_rect_combo->blockSignals(false); } settingUp = 0; if (rec->pen() == Qt::NoPen) { rectLineWidth->setValue(0); } else { rectLineWidth->setValue(rec->pen().width()); } enableToolbars(TITLE_RECTANGLE); } updateAxisButtons(referenceItem); updateCoordinates(rec); updateDimension(rec); } else if (referenceItem->type() == IMAGEITEM) { showToolbars(TITLE_IMAGE); updateCoordinates(referenceItem); updateDimension(referenceItem); enableToolbars(TITLE_IMAGE); } else { showToolbars(TITLE_SELECT); enableToolbars(TITLE_SELECT); frame_properties->setEnabled(false); } zValue->setValue((int)referenceItem->zValue()); if (!referenceItem->data(TitleDocument::ZoomFactor).isNull()) { itemzoom->setValue(referenceItem->data(TitleDocument::ZoomFactor).toInt()); } else { itemzoom->setValue((int)(m_transformations.value(referenceItem).scalex * 100.0 + 0.5)); } itemrotatex->setValue((int)(m_transformations.value(referenceItem).rotatex)); itemrotatey->setValue((int)(m_transformations.value(referenceItem).rotatey)); itemrotatez->setValue((int)(m_transformations.value(referenceItem).rotatez)); } effect_list->blockSignals(blockEff); itemrotatex->blockSignals(blockRX); itemrotatey->blockSignals(blockRY); itemrotatez->blockSignals(blockRZ); itemzoom->blockSignals(blockZoom); origin_x_left->blockSignals(blockOX); origin_y_top->blockSignals(blockOY); value_x->blockSignals(blockX); value_y->blockSignals(blockY); value_w->blockSignals(blockW); value_h->blockSignals(blockH); } void TitleWidget::slotEditGradient() { QToolButton *caller = qobject_cast(QObject::sender()); if (!caller) { return; } QComboBox *combo = nullptr; if (caller == edit_gradient) { combo = gradients_combo; } else { combo = gradients_rect_combo; } QMap gradients; for (int i = 0; i < combo->count(); i++) { gradients.insert(combo->itemText(i), combo->itemData(i).toString()); } GradientWidget d(gradients, combo->currentIndex()); if (d.exec() == QDialog::Accepted) { // Save current gradients QMap gradMap = d.gradients(); QList icons = d.icons(); QMap::const_iterator i = gradMap.constBegin(); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group(config, "TitleGradients"); group.deleteGroup(); combo->clear(); gradients_rect_combo->clear(); int ix = 0; while (i != gradMap.constEnd()) { group.writeEntry(i.key(), i.value()); gradients_combo->addItem(icons.at(ix), i.key(), i.value()); gradients_rect_combo->addItem(icons.at(ix), i.key(), i.value()); ++i; ix++; } group.sync(); combo->setCurrentIndex(d.selectedGradient()); } } void TitleWidget::storeGradient(const QString &gradientData) { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group(config, "TitleGradients"); QMap values = group.entryMap(); int ix = qMax(1, values.count()); QString gradName = i18n("Gradient %1", ix); while (values.contains(gradName)) { ix++; gradName = i18n("Gradient %1", ix); } group.writeEntry(gradName, gradientData); group.sync(); QPixmap pix(30, 30); pix.fill(Qt::transparent); QLinearGradient gr = GradientWidget::gradientFromString(gradientData, pix.width(), pix.height()); gr.setStart(0, pix.height() / 2); gr.setFinalStop(pix.width(), pix.height() / 2); QPainter painter(&pix); painter.fillRect(0, 0, pix.width(), pix.height(), QBrush(gr)); painter.end(); QIcon icon(pix); gradients_combo->addItem(icon, gradName, gradientData); gradients_rect_combo->addItem(icon, gradName, gradientData); } void TitleWidget::loadGradients() { QMap gradients; gradients_combo->blockSignals(true); gradients_rect_combo->blockSignals(true); QString grad_data = gradients_combo->currentData().toString(); QString rect_data = gradients_rect_combo->currentData().toString(); gradients_combo->clear(); gradients_rect_combo->clear(); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group(config, "TitleGradients"); QMap values = group.entryMap(); if (values.isEmpty()) { // Ensure we at least always have one sample black to white gradient values.insert(i18n("Gradient"), QStringLiteral("#ffffffff;#ff000000;0;100;90")); } QMapIterator k(values); while (k.hasNext()) { k.next(); QPixmap pix(30, 30); pix.fill(Qt::transparent); QLinearGradient gr = GradientWidget::gradientFromString(k.value(), pix.width(), pix.height()); gr.setStart(0, pix.height() / 2); gr.setFinalStop(pix.width(), pix.height() / 2); QPainter painter(&pix); painter.fillRect(0, 0, pix.width(), pix.height(), QBrush(gr)); painter.end(); QIcon icon(pix); gradients_combo->addItem(icon, k.key(), k.value()); gradients_rect_combo->addItem(icon, k.key(), k.value()); } int ix = gradients_combo->findData(grad_data); if (ix >= 0) { gradients_combo->setCurrentIndex(ix); } ix = gradients_rect_combo->findData(rect_data); if (ix >= 0) { gradients_rect_combo->setCurrentIndex(ix); } gradients_combo->blockSignals(false); gradients_rect_combo->blockSignals(false); } void TitleWidget::slotUpdateShadow() { QList l = graphicsView->scene()->selectedItems(); for (int i = 0; i < graphicsView->scene()->selectedItems().length(); ++i) { MyTextItem *item = nullptr; if (l.at(i)->type() == TEXTITEM) { item = static_cast(l.at(i)); } if (!item) { // No text item, try next one. continue; } item->updateShadow(shadowBox->isChecked(), blur_radius->value(), shadowX->value(), shadowY->value(), shadowColor->color()); } } const QString TitleWidget::titleSuggest() { // Find top item to extract title proposal QList list = graphicsView->scene()->items(); int y = m_frameHeight; QString title; for (QGraphicsItem *qgItem : list) { if (qgItem->pos().y() < y && qgItem->type() == TEXTITEM) { MyTextItem *i = static_cast(qgItem); QString currentTitle = i->toPlainText().simplified(); if (currentTitle.length() > 2) { title = currentTitle.length() > 12 ? currentTitle.left(12) + QStringLiteral("...") : currentTitle; y = qgItem->pos().y(); } } } return title; } void TitleWidget::showGuides(int state) { for(QGraphicsLineItem *it : m_guides) { it->setVisible(state == Qt::Checked); } KdenliveSettings::setTitlerShowGuides(state == Qt::Checked); } void TitleWidget::updateGuides(int) { KdenliveSettings::setTitlerHGuides(hguides->value()); KdenliveSettings::setTitlerVGuides(vguides->value()); if (!m_guides.isEmpty()) { qDeleteAll(m_guides); m_guides.clear(); } QPen framepen; QColor gColor(KdenliveSettings::titleGuideColor()); framepen.setColor(gColor); // Guides // Horizontal guides int max = hguides->value(); bool guideVisible = show_guides->checkState() == Qt::Checked; for (int i = 0; i < max; i++) { QGraphicsLineItem *line1 = new QGraphicsLineItem(0, (i + 1) * m_frameHeight / (max + 1), m_frameWidth, (i + 1) * m_frameHeight / (max + 1), m_frameBorder); line1->setPen(framepen); line1->setFlags(nullptr); line1->setData(-1, -1); line1->setVisible(guideVisible); m_guides << line1; } max = vguides->value(); for (int i = 0; i < max; i++) { QGraphicsLineItem *line1 = new QGraphicsLineItem((i + 1) * m_frameWidth / (max + 1), 0, (i + 1) * m_frameWidth / (max + 1), m_frameHeight, m_frameBorder); line1->setPen(framepen); line1->setFlags(nullptr); line1->setData(-1, -1); line1->setVisible(guideVisible); m_guides << line1; } gColor.setAlpha(160); framepen.setColor(gColor); QGraphicsLineItem *line6 = new QGraphicsLineItem(0, 0, m_frameWidth, m_frameHeight, m_frameBorder); line6->setPen(framepen); line6->setFlags(nullptr); line6->setData(-1, -1); line6->setVisible(guideVisible); m_guides << line6; QGraphicsLineItem *line7 = new QGraphicsLineItem(m_frameWidth, 0, 0, m_frameHeight, m_frameBorder); line7->setPen(framepen); line7->setFlags(nullptr); line7->setData(-1, -1); line7->setVisible(guideVisible); m_guides << line7; } void TitleWidget::guideColorChanged(const QColor &col) { KdenliveSettings::setTitleGuideColor(col); QColor guideCol(col); for(QGraphicsLineItem *it : m_guides) { int alpha = it->pen().color().alpha(); guideCol.setAlpha(alpha); QPen framePen(guideCol); it->setPen(framePen); } } diff --git a/src/titler/titlewidget.h b/src/titler/titlewidget.h index 929dcef89..d19f0e3e2 100644 --- a/src/titler/titlewidget.h +++ b/src/titler/titlewidget.h @@ -1,377 +1,380 @@ /*************************************************************************** titlewidget.h - description ------------------- begin : Feb 28 2008 copyright : (C) 2008 by Marco Gittler email : g.marco@freenet.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef TITLEWIDGET_H #define TITLEWIDGET_H #include "graphicsscenerectmove.h" #include "timecode.h" #include "titler/titledocument.h" #include "titler/unicodedialog.h" #include "ui_titlewidget_ui.h" #include #include class Monitor; - +class KMessageWidget; class TitleTemplate { public: QString file; QString name; QIcon icon; }; class Transform { public: Transform() { scalex = 1.0; scaley = 1.0; rotatex = 0.0; rotatey = 0.0; rotatez = 0.0; } double scalex, scaley; int rotatex, rotatey, rotatez; }; /*! \class TitleWidget \brief Title creation dialog Instances of TitleWidget classes are instantiated by KdenliveDoc::slotCreateTextClip () */ class TitleWidget : public QDialog, public Ui::TitleWidget_UI { Q_OBJECT public: /** @brief Draws the dialog and loads a title document (if any). * @param url title document to load * @param tc timecode of the project * @param projectPath default path to save to or load from title documents * @param render project renderer * @param parent (optional) parent widget */ explicit TitleWidget(const QUrl &url, const Timecode &tc, const QString &projectTitlePath, Monitor *monitor, QWidget *parent = nullptr); virtual ~TitleWidget(); QDomDocument xml(); void setXml(const QDomDocument &doc, const QString &id = QString()); /** @brief Checks for the images referenced by a title clip. * @param xml XML data representing the title * @return list of the image files */ static QStringList extractImageList(const QString &xml); /** @brief Checks for the fonts referenced by a title clip.\n * Called by DocumentChecker::hasErrorInClips () \n * @param xml XML data representing the title * @return list of the fonts in the title */ static QStringList extractFontList(const QString &xml); /** @brief Returns clip duration. */ int duration() const; /** @brief Retrieves a list of all available title templates. */ static void refreshTitleTemplates(const QString &projectPath); /** @brief Returns a title name suggestion based on content */ const QString titleSuggest(); protected: void resizeEvent(QResizeEvent *event) override; void keyPressEvent(QKeyEvent *e) override; private: /** @brief Rectangle describing the animation start viewport. */ QGraphicsRectItem *m_startViewport; /** @brief Rectangle describing the animation end viewport. */ QGraphicsRectItem *m_endViewport; /** @brief Scene for the titler. */ GraphicsSceneRectMove *m_scene; /** @brief Initialises the animation properties (viewport size, etc.). */ void initAnimation(); QMap m_transformations; TitleDocument m_titledocument; QGraphicsRectItem *m_frameBorder; QGraphicsRectItem *m_frameBackground; QGraphicsPixmapItem *m_frameImage; int m_frameWidth; int m_frameHeight; int m_count; /** @brief Dialog for entering Unicode characters in text fields. */ UnicodeDialog *m_unicodeDialog; + KMessageWidget *m_missingMessage; /** @brief Project path for storing title documents. */ QString m_projectTitlePath; Timecode m_tc; /** @brief The project framerate. */ double m_fps; /** @brief The bin id of the clip currently edited, can be empty if this is a new clip. */ QString m_clipId; QAction *m_buttonRect; QAction *m_buttonText; QAction *m_buttonImage; QAction *m_buttonCursor; QAction *m_buttonSave; QAction *m_buttonLoad; QAction *m_buttonDownload; QAction *m_unicodeAction; QAction *m_zUp; QAction *m_zDown; QAction *m_zTop; QAction *m_zBottom; QAction *m_selectAll; QAction *m_selectText; QAction *m_selectRects; QAction *m_selectImages; QAction *m_unselectAll; QString lastDocumentHash; QList m_guides; // See http://doc.trolltech.com/4.5/signalsandslots.html#advanced-signals-and-slots-usage. QSignalMapper *m_signalMapper; enum ValueType { ValueWidth = 1, ValueHeight = 2, ValueX = 4, ValueY = 8 }; /** @brief Sets the font weight value in the combo box. (#909) */ void setFontBoxWeight(int weight); /** @brief Stores the choices of font, background and rectangle values. */ void writeChoices(); /** @brief Reads the last stored choices into the dialog. */ void readChoices(); /** @brief Updates the displayed X/Y coordinates. */ void updateCoordinates(QGraphicsItem *i); /** @brief Updates the displayed width/height/zindex values. */ void updateDimension(QGraphicsItem *i); /** @brief Updates the displayed rotation/zoom values. Changes values of rotation/zoom GUI elements. */ void updateRotZoom(QGraphicsItem *i); /** @brief Updates the item position (position read directly from the GUI). Does not change GUI elements. */ void updatePosition(QGraphicsItem *i); /** @brief Updates the item position. Does not change GUI elements. */ void updatePosition(QGraphicsItem *i, int x, int y); void textChanged(MyTextItem *i); void updateAxisButtons(QGraphicsItem *i); void updateTextOriginX(); void updateTextOriginY(); /** @brief Enables the toolbars suiting to toolType. */ void enableToolbars(TITLETOOL toolType); /** @brief Shows the toolbars suiting to toolType. */ void showToolbars(TITLETOOL toolType); /** @brief Set up the tools suiting referenceItem */ void prepareTools(QGraphicsItem *referenceItem); /** @brief Checks a tool button. */ void checkButton(TITLETOOL toolType); void adjustFrameSize(); /** @brief Adds a "start" and "end" info text to the animation viewports. */ void addAnimInfoText(); /** @brief Updates the font for the "start" and "end" info text. */ void updateInfoText(); /** @brief Removes the "start" and "end" info text from animation viewports. */ void deleteAnimInfoText(); /** @brief Refreshes the contents of combobox based on list of title templates. */ void refreshTemplateBoxContents(); qreal maxZIndex(); /** @brief Gets the minimum/maximum Z index of items. * @param maxBound true: use maximum Z index; false: use minimum * @param intersectingOnly if true, consider only the items intersecting * with the currently selected item */ qreal zIndexBounds(bool maxBound, bool intersectingOnly); void itemRotate(int val, int axis); void selectItems(int itemType); /** @brief Appends the shortcut of a QAction to a tooltip text */ QString getTooltipWithShortcut(const QString &tipText, QAction *button); void loadGradients(); void storeGradient(const QString &gradientData); /** Open title download dialog */ void downloadTitleTemplates(); int getNewStuff(const QString &configFile); public slots: void slotNewText(MyTextItem *tt); void slotNewRect(QGraphicsRectItem *rect); void slotChangeBackground(); /** @brief Sets up the tools (toolbars etc.) according to the selected item. */ void selectionChanged(); void rectChanged(); void zIndexChanged(int); void itemScaled(int); void itemRotateX(int); void itemRotateY(int); void itemRotateZ(int); /** Save a title to a title file */ void saveTitle(QUrl url = QUrl()); /** Load a title from a title file */ void loadTitle(QUrl url = QUrl()); void slotGotBackground(const QImage &img); private slots: /** @brief Switches the origin of the X axis between left and right border. * * It's called when the origin of the X coordinate has been changed. The X * origin will either be at the left or at the right side of the frame. * * When the origin of the X axis is at the left side, the user can enter the * distance between an element's left border and the left side of the frame. * * When on the right, the distance from the right border of the frame to the * right border of the element can be entered. This would result in negative * values as long as the element's right border is at the left of the * frame's right border. As that is usually the case, I additionally invert * the X axis. * * Default value is left. * * |----l----->|#######|----r--->| * | |---w-->| | * | |#######| | * | | * |----------m_frameWidth------>| * | | * * Left selected: Value = l * Right selected: Value = r * * To calculate between the two coordinate systems: * l = m_frameWidth - w - r * r = m_frameWidth - w - l */ void slotOriginXClicked(); /** @brief Same as slotOriginXClicked(), but for the Y axis; default is top. * @ref slotOriginXClicked */ void slotOriginYClicked(); /** @brief Updates coordinates of text fields if necessary. * * It's called when something changes in the QGraphicsScene. */ void slotChanged(); /** * Reacts to changes of widht/height/x/y QSpinBox values. * @brief Updates width, height, and position of the selected items. * @param valueType of type ValueType */ void slotValueChanged(int valueType); void slotZoom(bool up); void slotUpdateZoom(int pos); void slotAdjustZoom(); void slotZoomOneToOne(); void slotSelectAll(); void slotSelectText(); void slotSelectRects(); void slotSelectImages(); void slotSelectNone(); /** Called whenever text properties change (font e.g.) */ void slotUpdateText(); void slotInsertUnicode(); void slotInsertUnicodeString(const QString &string); void displayBackgroundFrame(); void setCurrentItem(QGraphicsItem *item); void slotTextTool(); void slotRectTool(); void slotSelectTool(); void slotImageTool(); void slotAnimStart(bool); void slotAnimEnd(bool); void slotKeepAspect(bool keep); void itemHCenter(); void itemVCenter(); void itemTop(); void itemBottom(); void itemLeft(); void itemRight(); void slotResize50(); void slotResize100(); void slotResize200(); /** @brief Show hide guides */ void showGuides(int state); /** @brief Build guides */ void updateGuides(int); /** @brief guide color changed, repaint */ void guideColorChanged(const QColor &col); /** @brief Called when accepted, stores user selections for next time use. * @ref writeChoices */ void slotAccepted(); /** @brief Adds an effect to an element. * @param ix index of the effect in the effects menu * * The current implementation allows for one QGraphicsEffect to be added * along with the typewriter effect. This is not clear to the user: the * stack would help, and would permit us to make more QGraphicsEffects * coexist (with different layers of QGraphicsItems). */ void slotAddEffect(int ix); void slotEditTypewriter(int ix); /** @brief Changes the Z index of objects. */ void slotZIndexUp(); void slotZIndexDown(); void slotZIndexTop(); void slotZIndexBottom(); /** Called when the user wants to apply a different template to the title */ void templateIndexChanged(int); void slotEditGradient(); void slotUpdateShadow(); + /** @brief Remove missing items from the scene. */ + void deleteMissingItems(); signals: void requestBackgroundFrame(const QString &clipId, bool request); }; #endif diff --git a/src/ui/configmisc_ui.ui b/src/ui/configmisc_ui.ui index 59a59eda5..a872978a9 100644 --- a/src/ui/configmisc_ui.ui +++ b/src/ui/configmisc_ui.ui @@ -1,211 +1,204 @@ ConfigMisc_UI 0 0 - 467 - 578 + 414 + 546 + + + + Get clip metadata with exiftool + + + - + - Disable parameters when the effect is disabled + Automatically import all streams in multi stream clips - + - Get clip metadata with exiftool + Get clip metadata created by Magic Lantern - + + + + Disable parameters when the effect is disabled + + + + Default Durations Color clips Image clips Title clips Image sequence Transitions - - - - Do not validate the video files when loading a project (faster) - - - - - - - Activate crash recovery (auto save) - - - - - + + - Automatically import image sequences + Tab position - - - - Qt::Vertical - - - - 20 - 40 - - - - - + Top Bottom Left Right - + + + + Automatically import image sequences + + + + Qt::Horizontal 40 20 - - + + - Check if first added clip matches project profile + Bypass codec verification - - - - Open last project on startup + + + + Qt::Vertical - - - - - - Use KDE job tracking for render jobs + + + 20 + 40 + - + - - + + - Bypass codec verification + Activate crash recovery (auto save) - - + + - Tab position + Check if first added clip matches project profile - - + + - Automatically import all streams in multi stream clips + Use KDE job tracking for render jobs - - + + - Get clip metadata created by Magic Lantern + Open last project on startup diff --git a/src/ui/titlewidget_ui.ui b/src/ui/titlewidget_ui.ui index be26f82bc..5b896a211 100644 --- a/src/ui/titlewidget_ui.ui +++ b/src/ui/titlewidget_ui.ui @@ -1,1604 +1,1611 @@ TitleWidget_UI 0 0 1281 1306 Title Clip 0 0 QFrame::NoFrame QFrame::Plain 0 0 0 0 - - + + - +X - - - true - - - false + Z-Index: - - - - -5000 - - - 5000 + + + + - - + + - +Y + W - - true + + + + + + -1000 - - false + + 5000 -5000 5000 - - + + - W + +X - - - - - - -1000 + + true - - 5000 + + false -1000 5000 - - + + - H + + + + + + + + -5000 + + + 5000 Qt::Horizontal 82 20 - - - - - - - - - - - - - - - - + + - + +Y + + + true + + + false -5000 5000 - - + + - Z-Index: + + + + + + + + H 0 V true V true Qt::Vertical 100 0 150 16777215 1 1000 30 Qt::Horizontal false % 1 1000 Qt::Vertical Show background Qt::Vertical Template: QComboBox::AdjustToContents Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok 0 0 QFrame::StyledPanel QFrame::Raised 0 0 Qt::Horizontal - + 6 - - - - false - - - true - - - false - - - - 255 - 0 - 0 - - - - - 255 - 0 - 0 - - - - - + false N - - + + + + false + - Use grid + I - + Qt::Horizontal 40 20 - + 0 0 - - - - Selects all items on the canvas. - + + - A + Use grid - - + + false - I + T - - + + false - - T + + Horizontal guides + + + 2 - - + + false - - R + + Vertical guides + + + 99 + + + 3 - + Show guides - - + + false - - Horizontal guides - - - 2 + + R - - + + false - - Vertical guides + + true - - 99 + + false - - 3 + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + + + + Selects all items on the canvas. + + + A + + + + 0 + + + Rotate Y: Rotate Z: Qt::Horizontal -360 360 0 0 QTabWidget::North 0 Qt::ElideNone Properties 0 0 0 Letter Spacing ... true false true ... So&lid Color true Line Spacing ... true ... ... true true ... true false true true -1000 1000 5000 Outline Align 0 0 ... true 0 0 false - + 85 170 255 - + 0 0 0 -300 300 8 1000 20 &Gradient Qt::Vertical 20 40 Qt::Horizontal 40 20 false - Shadow + Sh&adow true false Blur 3 0 0 - + 0 0 0 - + true Offset -100 100 3 -100 100 3 So&lid Color true 0 0 - + 0 0 0 - + 0 0 0 &Gradient false ... Qt::Horizontal 40 20 Border 0 0 - + 0 0 0 - + 0 0 0 Border Width 5000 Qt::Vertical 20 40 false Preserve aspect ratio true Qt::Vertical 20 332 Background Qt::Vertical 20 40 Qt::Horizontal 256 0 0 - + 0 0 0 - + 0 0 0 Animation Edit start true Edit end true 0 0 0 Resize 50% 100% 200% Keep aspect ratio Qt::Vertical 20 17 Effect Effect 0 0 Delay 0 0 frames 1 5000 5 Start at frames 5000 Qt::Vertical 20 571 Zoom: -360 360 Rotate X: 0 0 QFrame::NoFrame QFrame::Plain ... ... ... ... ... ... Qt::Horizontal 40 20 -360 360 100000 1 Duration KComboBox QComboBox
kcombobox.h
KColorButton QPushButton
kcolorbutton.h
buttonBox accepted() TitleWidget_UI accept() 253 558 157 274 buttonBox rejected() TitleWidget_UI reject() 321 558 286 274 gradient_color toggled(bool) gradients_combo setEnabled(bool) 523 494 672 494 plain_color toggled(bool) fontColorButton setEnabled(bool) 523 454 652 454 plain_rect toggled(bool) rectBColor setEnabled(bool) 523 424 674 431 gradient_rect toggled(bool) gradients_rect_combo setEnabled(bool) 523 464 672 464 backgroundAlpha valueChanged(int) bgAlphaSlider setValue(int) 994 453 719 453 bgAlphaSlider valueChanged(int) backgroundAlpha setValue(int) 719 453 994 453 show_guides toggled(bool) hguides setEnabled(bool) 488 1211 813 1211 show_guides toggled(bool) vguides setEnabled(bool) 504 1211 640 1211 show_guides toggled(bool) guideColor setEnabled(bool) 471 1211 726 1211