File indexing completed on 2024-04-28 05:50:54

0001 /*
0002     SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
0003     SPDX-FileCopyrightText: 2018 Harald Sitter <sitter@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 // Own
0009 #include "EditProfileDialog.h"
0010 
0011 // Qt
0012 #include <QDialog>
0013 #include <QDialogButtonBox>
0014 #include <QFileDialog>
0015 #include <QIcon>
0016 #include <QInputDialog>
0017 #include <QMenu>
0018 #include <QPushButton>
0019 #include <QRegularExpressionValidator>
0020 #include <QStandardItem>
0021 #include <QStandardPaths>
0022 #include <QStringListModel>
0023 #include <QTextCodec>
0024 #include <QTimer>
0025 #include <QUrl>
0026 
0027 // KDE
0028 #include <KCodecAction>
0029 #include <KIconDialog>
0030 #include <KLocalizedString>
0031 #include <KWindowSystem>
0032 #include <kconfigwidgets_version.h>
0033 
0034 #include <KNSWidgets/Button>
0035 
0036 // Konsole
0037 #include "ui_EditProfileAdvancedPage.h"
0038 #include "ui_EditProfileAppearancePage.h"
0039 #include "ui_EditProfileGeneralPage.h"
0040 #include "ui_EditProfileKeyboardPage.h"
0041 #include "ui_EditProfileMousePage.h"
0042 #include "ui_EditProfileScrollingPage.h"
0043 #include "ui_EditProfileTabsPage.h"
0044 
0045 #include "colorscheme/ColorSchemeManager.h"
0046 
0047 #include "keyboardtranslator/KeyboardTranslator.h"
0048 
0049 #include "KeyBindingEditor.h"
0050 #include "ShellCommand.h"
0051 #include "WindowSystemInfo.h"
0052 #include "profile/ProfileManager.h"
0053 
0054 // Others
0055 #if defined(Q_OS_LINUX) | defined(Q_OS_FREEBSD) | defined(Q_OS_OPENBSD) | defined(Q_OS_SOLARIS) | defined(Q_OS_MACOS)
0056 #include <unistd.h>
0057 #endif
0058 
0059 using namespace Konsole;
0060 
0061 EditProfileDialog::EditProfileDialog(QWidget *parent)
0062     : KPageDialog(parent)
0063     , _tempProfile(new Profile{})
0064 {
0065     _tempProfile->setHidden(true);
0066 
0067     setWindowTitle(i18n("Edit Profile"));
0068     setFaceType(KPageDialog::List);
0069 
0070     _buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply);
0071     setButtonBox(_buttonBox);
0072 
0073     _buttonBox->button(QDialogButtonBox::Ok)->setDefault(true);
0074 
0075     auto *applyButton = _buttonBox->button(QDialogButtonBox::Apply);
0076     // Disable it, since no modifications have been made yet
0077     applyButton->setEnabled(false);
0078     connect(applyButton, &QPushButton::clicked, this, [this]() {
0079         if (isProfileNameValid()) {
0080             save();
0081         }
0082     });
0083 
0084     // Set a fallback icon for non-plasma desktops as this dialog looks
0085     // terrible without all the icons on the left sidebar.  On GTK related
0086     // desktops, this dialog look good enough without installing
0087     // oxygen-icon-theme, qt5ct and setting export QT_QPA_PLATFORMTHEME=qt5ct
0088     // Plain Xorg desktops still look terrible as there are no icons
0089     // visible.
0090     const auto defaultIcon = QIcon::fromTheme(QStringLiteral("utilities-terminal"));
0091 
0092     // General page
0093 
0094     const QString generalPageName = i18nc("@title:tab Generic, common options", "General");
0095     auto *generalPageWidget = new QWidget(this);
0096     _generalUi = new Ui::EditProfileGeneralPage();
0097     _generalUi->setupUi(generalPageWidget);
0098     _generalPageItem = addPage(generalPageWidget, generalPageName);
0099     _generalPageItem->setHeader(generalPageName);
0100     _generalPageItem->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal")));
0101     _pages[_generalPageItem] = Page(&EditProfileDialog::setupGeneralPage);
0102 
0103     // Tabs page
0104 
0105     const QString tabsPageName = i18n("Tabs");
0106     auto *tabsPageWidget = new QWidget(this);
0107     _tabsUi = new Ui::EditProfileTabsPage();
0108     _tabsUi->setupUi(tabsPageWidget);
0109     auto *tabsPageItem = addPage(tabsPageWidget, tabsPageName);
0110     tabsPageItem->setHeader(tabsPageName);
0111     tabsPageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-tabs"), defaultIcon));
0112     _pages[tabsPageItem] = Page(&EditProfileDialog::setupTabsPage);
0113 
0114     LabelsAligner tabsAligner(tabsPageWidget);
0115     tabsAligner.addLayout(dynamic_cast<QGridLayout *>(_tabsUi->tabMonitoringGroup->layout()));
0116     tabsAligner.addLayout(dynamic_cast<QGridLayout *>(_tabsUi->renameTabWidget->layout()));
0117     tabsAligner.updateLayouts();
0118     tabsAligner.align();
0119 
0120     // Appearance page
0121 
0122     const QString appearancePageName = i18n("Appearance");
0123     auto *appearancePageWidget = new QWidget(this);
0124     _appearanceUi = new Ui::EditProfileAppearancePage();
0125     _appearanceUi->setupUi(appearancePageWidget);
0126     auto *appearancePageItem = addPage(appearancePageWidget, appearancePageName);
0127     appearancePageItem->setHeader(appearancePageName);
0128     appearancePageItem->setIcon(QIcon::fromTheme(QStringLiteral("kcolorchooser"), defaultIcon));
0129     _pages[appearancePageItem] = Page(&EditProfileDialog::setupAppearancePage);
0130 
0131     LabelsAligner appearanceAligner(appearancePageWidget);
0132     appearanceAligner.addLayout(dynamic_cast<QGridLayout *>(_appearanceUi->contentsGroup->layout()));
0133     appearanceAligner.updateLayouts();
0134     appearanceAligner.align();
0135 
0136     // Scrolling page
0137 
0138     const QString scrollingPageName = i18n("Scrolling");
0139     auto *scrollingPageWidget = new QWidget(this);
0140     _scrollingUi = new Ui::EditProfileScrollingPage();
0141     _scrollingUi->setupUi(scrollingPageWidget);
0142     auto *scrollingPageItem = addPage(scrollingPageWidget, scrollingPageName);
0143     scrollingPageItem->setHeader(scrollingPageName);
0144     scrollingPageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-scroll"), defaultIcon));
0145     _pages[scrollingPageItem] = Page(&EditProfileDialog::setupScrollingPage);
0146 
0147     // adjust "history size" label height to match history size widget's first radio button
0148     _scrollingUi->historySizeLabel->setFixedHeight(_scrollingUi->historySizeWidget->preferredLabelHeight());
0149 
0150     // Keyboard page
0151 
0152     const QString keyboardPageName = i18n("Keyboard");
0153     const QString keyboardPageTitle = i18n("Key bindings");
0154     auto *keyboardPageWidget = new QWidget(this);
0155     _keyboardUi = new Ui::EditProfileKeyboardPage();
0156     _keyboardUi->setupUi(keyboardPageWidget);
0157     auto *keyboardPageItem = addPage(keyboardPageWidget, keyboardPageName);
0158     keyboardPageItem->setHeader(keyboardPageTitle);
0159     keyboardPageItem->setIcon(QIcon::fromTheme(QStringLiteral("input-keyboard"), defaultIcon));
0160     _pages[keyboardPageItem] = Page(&EditProfileDialog::setupKeyboardPage);
0161 
0162     // Mouse page
0163 
0164     const QString mousePageName = i18n("Mouse");
0165     auto *mousePageWidget = new QWidget(this);
0166     _mouseUi = new Ui::EditProfileMousePage();
0167     _mouseUi->setupUi(mousePageWidget);
0168 
0169     const auto regExp = QRegularExpression(QStringLiteral(R"(([a-z]*:\/\/;)*([A-Za-z*]:\/\/))"));
0170     auto validator = new QRegularExpressionValidator(regExp, this);
0171     _mouseUi->linkEscapeSequenceTexts->setValidator(validator);
0172 
0173     auto *mousePageItem = addPage(mousePageWidget, mousePageName);
0174     mousePageItem->setHeader(mousePageName);
0175     mousePageItem->setIcon(QIcon::fromTheme(QStringLiteral("input-mouse"), defaultIcon));
0176     _pages[mousePageItem] = Page(&EditProfileDialog::setupMousePage);
0177 
0178     // Advanced page
0179 
0180     const QString advancedPageName = i18nc("@title:tab Complex options", "Advanced");
0181     auto *advancedPageWidget = new QWidget(this);
0182     _advancedUi = new Ui::EditProfileAdvancedPage();
0183     _advancedUi->setupUi(advancedPageWidget);
0184     auto *advancedPageItem = addPage(advancedPageWidget, advancedPageName);
0185     advancedPageItem->setHeader(advancedPageName);
0186     advancedPageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other"), defaultIcon));
0187     _pages[advancedPageItem] = Page(&EditProfileDialog::setupAdvancedPage);
0188 
0189     // there are various setupXYZPage() methods to load the items
0190     // for each page and update their states to match the profile
0191     // being edited.
0192     //
0193     // these are only called when needed ( ie. when the user clicks
0194     // the tab to move to that page ).
0195     //
0196     // the _pageNeedsUpdate vector keeps track of the pages that have
0197     // not been updated since the last profile change and will need
0198     // to be refreshed when the user switches to them
0199     connect(this, &KPageDialog::currentPageChanged, this, &Konsole::EditProfileDialog::preparePage);
0200 }
0201 
0202 EditProfileDialog::~EditProfileDialog()
0203 {
0204     delete _generalUi;
0205     delete _tabsUi;
0206     delete _appearanceUi;
0207     delete _scrollingUi;
0208     delete _keyboardUi;
0209     delete _mouseUi;
0210     delete _advancedUi;
0211 }
0212 
0213 void EditProfileDialog::save()
0214 {
0215     const bool isNewProfile = _profileState == EditProfileDialog::NewProfile;
0216 
0217     if (isNewProfile) {
0218         ProfileManager::instance()->addProfile(_profile);
0219         // The profile exists now, no longer treat it as initial creation
0220         _profileState = EditProfileDialog::ExistingProfile;
0221     }
0222 
0223     bool defaultChanged = _isDefault != _generalUi->setAsDefaultButton->isChecked();
0224 
0225     if (_tempProfile->isEmpty() && !defaultChanged) {
0226         if (isNewProfile) {
0227             // New profile, we need to save it to disk, even if no settings
0228             // were changed and _tempProfile is empty
0229             ProfileManager::instance()->changeProfile(_profile, _profile->properties());
0230         }
0231         // no changes since last save
0232         return;
0233     }
0234 
0235     ProfileManager::instance()->changeProfile(_profile, _tempProfile->properties());
0236 
0237     // ensure that these settings are not undone by a call
0238     // to unpreview()
0239     _previewedProperties.clear();
0240 
0241     // Update the default profile if needed
0242     if (defaultChanged) {
0243         Q_ASSERT(_profile != ProfileManager::instance()->builtinProfile());
0244 
0245         bool defaultChecked = _generalUi->setAsDefaultButton->isChecked();
0246         Profile::Ptr newDefault = defaultChecked ? _profile : ProfileManager::instance()->builtinProfile();
0247         ProfileManager::instance()->setDefaultProfile(newDefault);
0248         _isDefault = defaultChecked;
0249     }
0250 
0251     resetTempProfile();
0252 
0253     _buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
0254 }
0255 
0256 void EditProfileDialog::reject()
0257 {
0258     unpreviewAll();
0259     QDialog::reject();
0260 }
0261 
0262 void EditProfileDialog::accept()
0263 {
0264     if (isProfileNameValid()) {
0265         save();
0266         unpreviewAll();
0267         QDialog::accept();
0268     }
0269 }
0270 
0271 void EditProfileDialog::setMessageGeneralPage(const QString &msg)
0272 {
0273     _generalUi->generalPageMessageWidget->setText(msg);
0274     _generalUi->generalPageMessageWidget->setMessageType(KMessageWidget::Error);
0275     setCurrentPage(_generalPageItem);
0276     _generalUi->generalPageMessageWidget->animatedShow();
0277 }
0278 
0279 bool EditProfileDialog::isProfileNameValid()
0280 {
0281     Q_ASSERT(_profile);
0282     Q_ASSERT(_tempProfile);
0283 
0284     // check whether the user has enough permissions to save the profile
0285     QFileInfo fileInfo(_profile->path());
0286     if (fileInfo.exists() && !fileInfo.isWritable() && (!_tempProfile->isPropertySet(Profile::Name) || _tempProfile->name() == _profile->name())) {
0287         setMessageGeneralPage(xi18nc("@info",
0288                                      "Insufficient permissions to save settings to: <filename>%1</filename>.<nl/>"
0289                                      "Either change the permissions of that file or set a different name to save "
0290                                      "the settings to a new profile.",
0291                                      _profile->path()));
0292         return false;
0293     }
0294 
0295     const QList<Profile::Ptr> existingProfiles = ProfileManager::instance()->allProfiles();
0296     QStringList otherExistingProfileNames;
0297 
0298     for (const Profile::Ptr &existingProfile : existingProfiles) {
0299         if (existingProfile->name() != _profile->name()) {
0300             otherExistingProfileNames.append(existingProfile->name());
0301         }
0302     }
0303 
0304     if ((_tempProfile->isPropertySet(Profile::Name) && _tempProfile->name().isEmpty()) || (_profile->name().isEmpty() && _tempProfile->name().isEmpty())) {
0305         setMessageGeneralPage(i18nc("@info", "Profile Name was empty; please set a name to be able to save settings."));
0306         // Revert the name in the dialog
0307         _generalUi->profileNameEdit->setText(_profile->name());
0308         selectProfileName();
0309         return false;
0310     }
0311 
0312     if (!_tempProfile->name().isEmpty() && otherExistingProfileNames.contains(_tempProfile->name())) {
0313         setMessageGeneralPage(i18nc("@info", "A profile with the name \"%1\" already exists.", _generalUi->profileNameEdit->text()));
0314         // Revert the name in the dialog
0315         _generalUi->profileNameEdit->setText(_profile->name());
0316         selectProfileName();
0317         return false;
0318     }
0319 
0320     const QString profileStoragePath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + QStringLiteral("konsole"));
0321     int nameLenMax = 256;
0322 
0323 #if defined(Q_OS_LINUX) | defined(Q_OS_OPENBSD) | defined(Q_OS_FREEBSD) | defined(Q_OS_MACOS) | defined(Q_OS_SOLARIS)
0324     const int maxPath = pathconf(profileStoragePath.toLocal8Bit().constData(), _PC_PATH_MAX);
0325     const int nameLenMaxPath = maxPath - _profile->path().lastIndexOf(QLatin1Char('/')) + 1;
0326     const int nameLenMaxName = pathconf(profileStoragePath.toLocal8Bit().constData(), _PC_NAME_MAX);
0327     nameLenMax = qMin(nameLenMaxPath, nameLenMaxName);
0328 #elif defined(Q_OS_WIN)
0329     // If MAX_PATH not found, use default (260 - 1)
0330     const int maxPath = qMax(259, qEnvironmentVariable("MAX_PATH").toInt());
0331     nameLenMax = maxPath - _profile->path().lastIndexOf(QLatin1Char('/')) + 1;
0332 #endif
0333 
0334     nameLenMax -= QStringLiteral(".profile").size();
0335 
0336     if (_tempProfile->name().size() > nameLenMax) {
0337         setMessageGeneralPage(i18nc("@info", "Profile name exceeded maximum allowed length (%1).", nameLenMax));
0338         // Revert the name in the dialog
0339         _generalUi->profileNameEdit->setText(_profile->name());
0340         selectProfileName();
0341         return false;
0342     }
0343 
0344     if (!_tempProfile->name().isEmpty()) {
0345         static const QString illegalCharacters(QStringLiteral("#%&{}/\\\"<>*?$!':@`+=|"));
0346         static const QString colorTagOpen(QStringLiteral("<font color=\"red\">"));
0347         static const QString colorTagClose(QStringLiteral("</font>"));
0348         const QString name(_tempProfile->name());
0349         bool hasIllegal = false;
0350         bool unclosedTag = false;
0351         QString highlightedName;
0352 
0353         for (int i = 0; i < name.size(); ++i) {
0354             const auto currChar = name.at(i);
0355 
0356             if (illegalCharacters.contains(currChar)) {
0357                 if (!unclosedTag) {
0358                     highlightedName += colorTagOpen;
0359                 }
0360                 hasIllegal = true;
0361                 unclosedTag = true;
0362             } else if (unclosedTag) {
0363                 unclosedTag = false;
0364                 highlightedName += colorTagClose;
0365             }
0366 
0367             highlightedName += currChar;
0368 
0369             if (i + 1 == name.size() && unclosedTag) {
0370                 highlightedName += colorTagClose;
0371             }
0372         }
0373 
0374         if (hasIllegal) {
0375             setMessageGeneralPage(i18nc("@info", "The highlighted characters are invalid in profile names: %1.", highlightedName));
0376             // Revert the name in the dialog
0377             _generalUi->profileNameEdit->setText(_profile->name());
0378             selectProfileName();
0379             return false;
0380         }
0381     }
0382 
0383     // Valid name
0384     return true;
0385 }
0386 
0387 QString EditProfileDialog::groupProfileNames(const ProfileGroup::Ptr &group, int maxLength)
0388 {
0389     QString caption;
0390     int count = group->profiles().count();
0391     for (int i = 0; i < count; i++) {
0392         caption += group->profiles()[i]->name();
0393         if (i < (count - 1)) {
0394             caption += QLatin1Char(',');
0395             // limit caption length to prevent very long window titles
0396             if (maxLength > 0 && caption.length() > maxLength) {
0397                 caption += QLatin1String("...");
0398                 break;
0399             }
0400         }
0401     }
0402     return caption;
0403 }
0404 
0405 void EditProfileDialog::updateCaption(const Profile::Ptr &profile)
0406 {
0407     const int MAX_GROUP_CAPTION_LENGTH = 25;
0408     ProfileGroup::Ptr group = profile->asGroup();
0409     if (group && group->profiles().count() > 1) {
0410         QString caption = groupProfileNames(group, MAX_GROUP_CAPTION_LENGTH);
0411         setWindowTitle(i18np("Editing profile: %2", "Editing %1 profiles: %2", group->profiles().count(), caption));
0412         updateButtonApply();
0413     } else {
0414         if (_profileState == EditProfileDialog::NewProfile) {
0415             setWindowTitle(i18n("Create New Profile"));
0416             _buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
0417         } else {
0418             setWindowTitle(i18n("Edit Profile \"%1\"", profile->name()));
0419             updateButtonApply();
0420         }
0421     }
0422 }
0423 
0424 void EditProfileDialog::setProfile(const Konsole::Profile::Ptr &profile, EditProfileDialog::InitialProfileState state)
0425 {
0426     Q_ASSERT(profile);
0427 
0428     _profile = profile;
0429 
0430     _profileState = state;
0431 
0432     // update caption
0433     updateCaption(profile);
0434 
0435     // mark each page of the dialog as out of date
0436     // and force an update of the currently visible page
0437     //
0438     // the other pages will be updated as necessary
0439     for (Page &page : _pages) {
0440         page.needsUpdate = true;
0441     }
0442     preparePage(currentPage());
0443 
0444     resetTempProfile();
0445 }
0446 
0447 const QString EditProfileDialog::currentColorSchemeName() const
0448 {
0449     const QString &currentColorSchemeName = _profile->colorScheme();
0450     return currentColorSchemeName;
0451 }
0452 
0453 void EditProfileDialog::preparePage(KPageWidgetItem *current, KPageWidgetItem *before)
0454 {
0455     Q_UNUSED(before)
0456     Q_ASSERT(current);
0457     Q_ASSERT(_pages.contains(current));
0458 
0459     const Profile::Ptr profile = _profile;
0460     auto setupPage = _pages[current].setupPage;
0461     Q_ASSERT(profile);
0462     Q_ASSERT(setupPage);
0463 
0464     if (_pages[current].needsUpdate) {
0465         (*this.*setupPage)(profile);
0466         _pages[current].needsUpdate = false;
0467     }
0468 }
0469 
0470 void Konsole::EditProfileDialog::selectProfileName()
0471 {
0472     _generalUi->profileNameEdit->setFocus();
0473     _generalUi->profileNameEdit->selectAll();
0474 }
0475 
0476 void EditProfileDialog::setupGeneralPage(const Profile::Ptr &profile)
0477 {
0478     _generalUi->generalPageMessageWidget->setVisible(false);
0479     _generalUi->generalPageMessageWidget->setWordWrap(true);
0480     _generalUi->generalPageMessageWidget->setCloseButtonVisible(true);
0481 
0482     // basic profile options
0483     {
0484         ProfileGroup::Ptr group = profile->asGroup();
0485         if (!group || group->profiles().count() < 2) {
0486             _generalUi->profileNameEdit->setText(profile->name());
0487             _generalUi->profileNameEdit->setClearButtonEnabled(true);
0488         } else {
0489             _generalUi->profileNameEdit->setText(groupProfileNames(group, -1));
0490             _generalUi->profileNameEdit->setEnabled(false);
0491         }
0492     }
0493 
0494     ShellCommand command(profile->command(), profile->arguments());
0495     _generalUi->commandEdit->setText(command.fullCommand());
0496     // If a "completion" is requested, consider changing this to KLineEdit
0497     // and using KCompletion.
0498     _generalUi->initialDirEdit->setText(profile->defaultWorkingDirectory());
0499     _generalUi->initialDirEdit->setClearButtonEnabled(true);
0500     _generalUi->initialDirEdit->setPlaceholderText(QStandardPaths::standardLocations(QStandardPaths::HomeLocation).value(0));
0501 
0502     _generalUi->dirSelectButton->setIcon(QIcon::fromTheme(QStringLiteral("folder-open")));
0503     _generalUi->iconSelectButton->setIcon(QIcon::fromTheme(profile->icon()));
0504     _generalUi->environmentEditButton->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
0505     _generalUi->startInSameDirButton->setChecked(profile->startInCurrentSessionDir());
0506 
0507     // initial terminal size
0508     const auto colsSuffix =
0509         ki18ncp("Suffix of the number of columns (N columns). The leading space is needed to separate it from the number value.", " column", " columns");
0510     const auto rowsSuffix =
0511         ki18ncp("Suffix of the number of rows (N rows). The leading space is needed to separate it from the number value.", " row", " rows");
0512     _generalUi->terminalColumnsEntry->setValue(profile->terminalColumns());
0513     _generalUi->terminalRowsEntry->setValue(profile->terminalRows());
0514     _generalUi->terminalColumnsEntry->setSuffix(colsSuffix);
0515     _generalUi->terminalRowsEntry->setSuffix(rowsSuffix);
0516     // make width of initial terminal size spinboxes equal
0517     const int sizeEntryWidth = qMax(maxSpinBoxWidth(_generalUi->terminalColumnsEntry, colsSuffix), maxSpinBoxWidth(_generalUi->terminalRowsEntry, rowsSuffix));
0518     _generalUi->terminalColumnsEntry->setFixedWidth(sizeEntryWidth);
0519     _generalUi->terminalRowsEntry->setFixedWidth(sizeEntryWidth);
0520 
0521     auto *bellModeModel = new QStringListModel({i18n("System Bell"), i18n("System Notifications"), i18n("Visual Bell"), i18n("Ignore Bell Events")}, this);
0522     _generalUi->terminalBellCombo->setModel(bellModeModel);
0523     _generalUi->terminalBellCombo->setCurrentIndex(profile->property<int>(Profile::BellMode));
0524 
0525     _isDefault = profile == ProfileManager::instance()->defaultProfile();
0526     _generalUi->setAsDefaultButton->setChecked(_isDefault);
0527     QString appName = QCoreApplication::applicationName();
0528     if (!appName.isEmpty() && appName != QLatin1String("konsole")) {
0529         appName[0] = appName.at(0).toUpper();
0530         _generalUi->setAsDefaultButton->setText(i18n("Default profile for new terminal sessions in %1", appName));
0531     } else {
0532         _generalUi->setAsDefaultButton->setText(i18n("Default profile"));
0533     }
0534     _generalUi->semanticUpDown->setChecked(profile->semanticUpDown());
0535     _generalUi->semanticInputClick->setChecked(profile->semanticInputClick());
0536 
0537     // signals and slots
0538     connect(_generalUi->dirSelectButton, &QToolButton::clicked, this, &Konsole::EditProfileDialog::selectInitialDir);
0539     connect(_generalUi->iconSelectButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::selectIcon);
0540     connect(_generalUi->startInSameDirButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::startInSameDir);
0541     connect(_generalUi->profileNameEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::profileNameChanged);
0542     connect(_generalUi->initialDirEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::initialDirChanged);
0543     connect(_generalUi->commandEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::commandChanged);
0544     connect(_generalUi->environmentEditButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::showEnvironmentEditor);
0545     connect(_generalUi->semanticUpDown, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::semanticUpDown);
0546     connect(_generalUi->semanticInputClick, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::semanticInputClick);
0547 
0548     connect(_generalUi->terminalColumnsEntry, &QSpinBox::valueChanged, this, &Konsole::EditProfileDialog::terminalColumnsEntryChanged);
0549     connect(_generalUi->terminalRowsEntry, &QSpinBox::valueChanged, this, &Konsole::EditProfileDialog::terminalRowsEntryChanged);
0550 
0551     connect(_generalUi->terminalBellCombo, &QComboBox::currentIndexChanged, this, [this](const int index) {
0552         updateTempProfileProperty(Profile::BellMode, index);
0553     });
0554 
0555     connect(_generalUi->setAsDefaultButton, &QAbstractButton::toggled, this, &Konsole::EditProfileDialog::updateButtonApply);
0556     const ButtonGroupOptions semanticHints = {
0557         _generalUi->semanticHints, // group
0558         Profile::SemanticHints, // profileProperty
0559         false, // preview
0560         {
0561             // buttons
0562             {_generalUi->semanticHintsNever, Enum::HintsNever},
0563             {_generalUi->semanticHintsURL, Enum::HintsURL},
0564             {_generalUi->semanticHintsAlways, Enum::HintsAlways},
0565         },
0566     };
0567     setupButtonGroup(semanticHints, profile);
0568     const ButtonGroupOptions errorBars = {
0569         _generalUi->errorBars, // group
0570         Profile::ErrorBars, // profileProperty
0571         false, // preview
0572         {
0573             // buttons
0574             {_generalUi->errorBarsNever, Enum::HintsNever},
0575             {_generalUi->errorBarsURL, Enum::HintsURL},
0576             {_generalUi->errorBarsAlways, Enum::HintsAlways},
0577         },
0578     };
0579     setupButtonGroup(errorBars, profile);
0580     const ButtonGroupOptions errorBackground = {
0581         _generalUi->errorBackground, // group
0582         Profile::ErrorBackground, // profileProperty
0583         false, // preview
0584         {
0585             // buttons
0586             {_generalUi->errorBackgroundNever, Enum::HintsNever},
0587             {_generalUi->errorBackgroundURL, Enum::HintsURL},
0588             {_generalUi->errorBackgroundAlways, Enum::HintsAlways},
0589         },
0590     };
0591     setupButtonGroup(errorBackground, profile);
0592     const ButtonGroupOptions alternatingBars = {
0593         _generalUi->alternatingBars, // group
0594         Profile::AlternatingBars, // profileProperty
0595         false, // preview
0596         {
0597             // buttons
0598             {_generalUi->alternatingBarsNever, Enum::HintsNever},
0599             {_generalUi->alternatingBarsURL, Enum::HintsURL},
0600             {_generalUi->alternatingBarsAlways, Enum::HintsAlways},
0601         },
0602     };
0603     setupButtonGroup(alternatingBars, profile);
0604     const ButtonGroupOptions alternatingBackground = {
0605         _generalUi->alternatingBackground, // group
0606         Profile::AlternatingBackground, // profileProperty
0607         false, // preview
0608         {
0609             // buttons
0610             {_generalUi->alternatingBackgroundNever, Enum::HintsNever},
0611             {_generalUi->alternatingBackgroundURL, Enum::HintsURL},
0612             {_generalUi->alternatingBackgroundAlways, Enum::HintsAlways},
0613         },
0614     };
0615     setupButtonGroup(alternatingBackground, profile);
0616 }
0617 
0618 void EditProfileDialog::showEnvironmentEditor()
0619 {
0620     bool ok;
0621     const Profile::Ptr profile = _profile;
0622 
0623     QStringList currentEnvironment;
0624 
0625     // The user could re-open the environment editor before clicking
0626     // OK/Apply in the parent edit profile dialog, so we make sure
0627     // to show the new environment vars
0628     if (_tempProfile->isPropertySet(Profile::Environment)) {
0629         currentEnvironment = _tempProfile->environment();
0630     } else {
0631         currentEnvironment = profile->environment();
0632     }
0633 
0634     QString text = QInputDialog::getMultiLineText(this,
0635                                                   i18n("Edit Environment"),
0636                                                   i18n("One environment variable per line"),
0637                                                   currentEnvironment.join(QStringLiteral("\n")),
0638                                                   &ok);
0639 
0640     QStringList newEnvironment;
0641 
0642     if (ok) {
0643         if (!text.isEmpty()) {
0644             newEnvironment = text.split(QLatin1Char('\n'));
0645             updateTempProfileProperty(Profile::Environment, newEnvironment);
0646         } else {
0647             // the user could have removed all entries so we return an empty list
0648             updateTempProfileProperty(Profile::Environment, newEnvironment);
0649         }
0650     }
0651 }
0652 
0653 void EditProfileDialog::setupTabsPage(const Profile::Ptr &profile)
0654 {
0655     // tab title format
0656     _tabsUi->renameTabWidget->setTabTitleText(profile->localTabTitleFormat());
0657     _tabsUi->renameTabWidget->setRemoteTabTitleText(profile->remoteTabTitleFormat());
0658     _tabsUi->renameTabWidget->setColor(profile->tabColor());
0659 
0660     connect(_tabsUi->renameTabWidget, &Konsole::RenameTabWidget::tabTitleFormatChanged, this, &Konsole::EditProfileDialog::tabTitleFormatChanged);
0661     connect(_tabsUi->renameTabWidget, &Konsole::RenameTabWidget::remoteTabTitleFormatChanged, this, &Konsole::EditProfileDialog::remoteTabTitleFormatChanged);
0662     connect(_tabsUi->renameTabWidget, &Konsole::RenameTabWidget::tabColorChanged, this, &Konsole::EditProfileDialog::tabColorChanged);
0663 
0664     // tab monitoring
0665     const int silenceSeconds = profile->silenceSeconds();
0666     _tabsUi->silenceSecondsSpinner->setValue(silenceSeconds);
0667     auto suffix = ki18ncp("Unit of time", " second", " seconds");
0668     _tabsUi->silenceSecondsSpinner->setSuffix(suffix);
0669     int silenceCheckBoxWidth = maxSpinBoxWidth(_generalUi->terminalColumnsEntry, suffix);
0670     _tabsUi->silenceSecondsSpinner->setFixedWidth(silenceCheckBoxWidth);
0671 
0672     connect(_tabsUi->silenceSecondsSpinner, &QSpinBox::valueChanged, this, &Konsole::EditProfileDialog::silenceSecondsChanged);
0673 }
0674 
0675 void EditProfileDialog::terminalColumnsEntryChanged(int value)
0676 {
0677     updateTempProfileProperty(Profile::TerminalColumns, value);
0678 }
0679 
0680 void EditProfileDialog::terminalRowsEntryChanged(int value)
0681 {
0682     updateTempProfileProperty(Profile::TerminalRows, value);
0683 }
0684 
0685 void EditProfileDialog::showTerminalSizeHint(bool value)
0686 {
0687     updateTempProfileProperty(Profile::ShowTerminalSizeHint, value);
0688 }
0689 
0690 void EditProfileDialog::setDimWhenInactive(bool value)
0691 {
0692     updateTempProfileProperty(Profile::DimWhenInactive, value);
0693 }
0694 
0695 void EditProfileDialog::setDimValue(int value)
0696 {
0697     updateTempProfileProperty(Profile::DimValue, value);
0698 }
0699 
0700 void EditProfileDialog::setBorderWhenActive(bool value)
0701 {
0702     updateTempProfileProperty(Profile::BorderWhenActive, value);
0703 }
0704 
0705 void EditProfileDialog::setFocusBorderColor(const QColor &color)
0706 {
0707     updateTempProfileProperty(Profile::FocusBorderColor, color);
0708 }
0709 
0710 void EditProfileDialog::tabTitleFormatChanged(const QString &format)
0711 {
0712     updateTempProfileProperty(Profile::LocalTabTitleFormat, format);
0713 }
0714 
0715 void EditProfileDialog::remoteTabTitleFormatChanged(const QString &format)
0716 {
0717     updateTempProfileProperty(Profile::RemoteTabTitleFormat, format);
0718 }
0719 
0720 void EditProfileDialog::tabColorChanged(const QColor &color)
0721 {
0722     updateTempProfileProperty(Profile::TabColor, color);
0723 }
0724 
0725 void EditProfileDialog::focusBorderColorChanged(const QColor &color)
0726 {
0727     updateTempProfileProperty(Profile::FocusBorderColor, color);
0728 }
0729 
0730 void EditProfileDialog::silenceSecondsChanged(int seconds)
0731 {
0732     updateTempProfileProperty(Profile::SilenceSeconds, seconds);
0733 }
0734 
0735 void EditProfileDialog::selectIcon()
0736 {
0737     const QString &icon = KIconDialog::getIcon(KIconLoader::Desktop, KIconLoader::Application, false, 0, false, this);
0738     if (!icon.isEmpty()) {
0739         _generalUi->iconSelectButton->setIcon(QIcon::fromTheme(icon));
0740         updateTempProfileProperty(Profile::Icon, icon);
0741     }
0742 }
0743 
0744 void EditProfileDialog::profileNameChanged(const QString &name)
0745 {
0746     updateTempProfileProperty(Profile::Name, name);
0747     updateTempProfileProperty(Profile::UntranslatedName, name);
0748     updateCaption(_tempProfile);
0749 }
0750 
0751 void EditProfileDialog::startInSameDir(bool sameDir)
0752 {
0753     updateTempProfileProperty(Profile::StartInCurrentSessionDir, sameDir);
0754 }
0755 
0756 void EditProfileDialog::semanticUpDown(bool enable)
0757 {
0758     updateTempProfileProperty(Profile::SemanticUpDown, enable);
0759 }
0760 
0761 void EditProfileDialog::semanticInputClick(bool enable)
0762 {
0763     updateTempProfileProperty(Profile::SemanticInputClick, enable);
0764 }
0765 
0766 void EditProfileDialog::initialDirChanged(const QString &dir)
0767 {
0768     updateTempProfileProperty(Profile::Directory, dir);
0769 }
0770 
0771 void EditProfileDialog::commandChanged(const QString &command)
0772 {
0773     ShellCommand shellCommand(command);
0774 
0775     updateTempProfileProperty(Profile::Command, shellCommand.command());
0776     updateTempProfileProperty(Profile::Arguments, shellCommand.arguments());
0777 }
0778 
0779 void EditProfileDialog::selectInitialDir()
0780 {
0781     const QUrl url = QFileDialog::getExistingDirectoryUrl(this, i18n("Select Initial Directory"), QUrl::fromUserInput(_generalUi->initialDirEdit->text()));
0782 
0783     if (!url.isEmpty()) {
0784         _generalUi->initialDirEdit->setText(url.path());
0785     }
0786 }
0787 
0788 void EditProfileDialog::setupAppearancePage(const Profile::Ptr &profile)
0789 {
0790     auto compositingActiveHelper = [] {
0791         return WindowSystemInfo::compositingActive();
0792     };
0793     auto delegate = new ColorSchemeViewDelegate(compositingActiveHelper, this);
0794     _appearanceUi->colorSchemeList->setItemDelegate(delegate);
0795 
0796     _appearanceUi->transparencyWarningWidget->setVisible(false);
0797     _appearanceUi->transparencyWarningWidget->setWordWrap(true);
0798     _appearanceUi->transparencyWarningWidget->setCloseButtonVisible(false);
0799     _appearanceUi->transparencyWarningWidget->setMessageType(KMessageWidget::Warning);
0800 
0801     _appearanceUi->colorSchemeMessageWidget->setVisible(false);
0802     _appearanceUi->colorSchemeMessageWidget->setWordWrap(true);
0803     _appearanceUi->colorSchemeMessageWidget->setCloseButtonVisible(false);
0804     _appearanceUi->colorSchemeMessageWidget->setMessageType(KMessageWidget::Warning);
0805 
0806     _appearanceUi->editColorSchemeButton->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
0807     _appearanceUi->removeColorSchemeButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
0808     _appearanceUi->newColorSchemeButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0809     _appearanceUi->chooseFontButton->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font")));
0810     _appearanceUi->resetColorSchemeButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
0811 
0812     _appearanceUi->editColorSchemeButton->setEnabled(false);
0813     _appearanceUi->removeColorSchemeButton->setEnabled(false);
0814     _appearanceUi->resetColorSchemeButton->setEnabled(false);
0815 
0816     // setup color list
0817     // select the colorScheme used in the current profile
0818     updateColorSchemeList(currentColorSchemeName());
0819 
0820     _appearanceUi->colorSchemeList->setMouseTracking(true);
0821     _appearanceUi->colorSchemeList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
0822 
0823     connect(_appearanceUi->colorSchemeList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Konsole::EditProfileDialog::colorSchemeSelected);
0824     connect(_appearanceUi->colorSchemeList, &QListView::activated, this, &Konsole::EditProfileDialog::previewColorScheme);
0825 
0826     updateColorSchemeButtons();
0827 
0828     connect(_appearanceUi->editColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::editColorScheme);
0829     connect(_appearanceUi->removeColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::removeColorScheme);
0830     connect(_appearanceUi->newColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::newColorScheme);
0831 
0832     connect(_appearanceUi->resetColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::resetColorScheme);
0833 
0834     connect(_appearanceUi->chooseFontButton, &QAbstractButton::clicked, this, &EditProfileDialog::showFontDialog);
0835 
0836     connect(_appearanceUi->emojiFontPreview, &QAbstractButton::clicked, this, &EditProfileDialog::showEmojiFontDialog);
0837 
0838     // setup font preview
0839     const bool antialias = profile->antiAliasFonts();
0840 
0841     QFont profileFont = profile->font();
0842     profileFont.setStyleStrategy(antialias ? QFont::PreferAntialias : QFont::NoAntialias);
0843 
0844     _appearanceUi->fontPreview->setFont(profileFont);
0845     _appearanceUi->fontPreview->setText(QStringLiteral("%1 %2pt").arg(profileFont.family()).arg(profileFont.pointSize()));
0846 
0847     QFont emojiFont = profile->emojiFont();
0848     _appearanceUi->emojiFontPreview->setFont(profileFont);
0849     _appearanceUi->emojiFontPreview->setText(QStringLiteral("%1 %2pt").arg(emojiFont.family()).arg(emojiFont.pointSize()));
0850 
0851     // setup font smoothing
0852     _appearanceUi->antialiasTextButton->setChecked(antialias);
0853     connect(_appearanceUi->antialiasTextButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setAntialiasText);
0854 
0855     _appearanceUi->boldIntenseButton->setChecked(profile->boldIntense());
0856     connect(_appearanceUi->boldIntenseButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setBoldIntense);
0857 
0858     _appearanceUi->useFontLineCharactersButton->setChecked(profile->useFontLineCharacters());
0859     connect(_appearanceUi->useFontLineCharactersButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::useFontLineCharacters);
0860 
0861     _mouseUi->enableMouseWheelZoomButton->setChecked(profile->mouseWheelZoomEnabled());
0862     connect(_mouseUi->enableMouseWheelZoomButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::toggleMouseWheelZoom);
0863 
0864     // cursor options
0865     _appearanceUi->enableBlinkingCursorButton->setChecked(profile->property<bool>(Profile::BlinkingCursorEnabled));
0866     connect(_appearanceUi->enableBlinkingCursorButton, &QToolButton::toggled, this, &EditProfileDialog::toggleBlinkingCursor);
0867 
0868     if (profile->useCustomCursorColor()) {
0869         _appearanceUi->customCursorColorButton->setChecked(true);
0870     } else {
0871         _appearanceUi->autoCursorColorButton->setChecked(true);
0872     }
0873 
0874     _appearanceUi->customColorSelectButton->setColor(profile->customCursorColor());
0875     _appearanceUi->customTextColorSelectButton->setColor(profile->customCursorTextColor());
0876 
0877     connect(_appearanceUi->customCursorColorButton, &QRadioButton::clicked, this, &Konsole::EditProfileDialog::customCursorColor);
0878     connect(_appearanceUi->autoCursorColorButton, &QRadioButton::clicked, this, &Konsole::EditProfileDialog::autoCursorColor);
0879     connect(_appearanceUi->customColorSelectButton, &KColorButton::changed, this, &Konsole::EditProfileDialog::customCursorColorChanged);
0880     connect(_appearanceUi->customTextColorSelectButton, &KColorButton::changed, this, &Konsole::EditProfileDialog::customCursorTextColorChanged);
0881 
0882     const ButtonGroupOptions cursorShapeOptions = {
0883         _appearanceUi->cursorShape, // group
0884         Profile::CursorShape, // profileProperty
0885         true, // preview
0886         {
0887             // buttons
0888             {_appearanceUi->cursorShapeBlock, Enum::BlockCursor},
0889             {_appearanceUi->cursorShapeIBeam, Enum::IBeamCursor},
0890             {_appearanceUi->cursorShapeUnderline, Enum::UnderlineCursor},
0891         },
0892     };
0893     setupButtonGroup(cursorShapeOptions, profile);
0894 
0895     _appearanceUi->marginsSpinner->setValue(profile->terminalMargin());
0896     connect(_appearanceUi->marginsSpinner, &QSpinBox::valueChanged, this, &Konsole::EditProfileDialog::terminalMarginChanged);
0897 
0898     _appearanceUi->lineSpacingSpinner->setValue(profile->lineSpacing());
0899     connect(_appearanceUi->lineSpacingSpinner, &QSpinBox::valueChanged, this, &Konsole::EditProfileDialog::lineSpacingChanged);
0900 
0901     _appearanceUi->alignToCenterButton->setChecked(profile->terminalCenter());
0902     connect(_appearanceUi->alignToCenterButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setTerminalCenter);
0903 
0904     _appearanceUi->showTerminalSizeHintButton->setChecked(profile->showTerminalSizeHint());
0905     connect(_appearanceUi->showTerminalSizeHintButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::showTerminalSizeHint);
0906 
0907     _appearanceUi->dimWhenInactiveCheckbox->setChecked(profile->dimWhenInactive());
0908     connect(_appearanceUi->dimWhenInactiveCheckbox, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setDimWhenInactive);
0909 
0910     _appearanceUi->dimValue->setValue(profile->dimValue());
0911     _appearanceUi->dimValue->setEnabled(profile->dimWhenInactive());
0912     _appearanceUi->dimLabel->setEnabled(profile->dimWhenInactive());
0913     connect(_appearanceUi->dimValue, &QSlider::valueChanged, this, &Konsole::EditProfileDialog::setDimValue);
0914 
0915     _appearanceUi->borderWhenActiveCheckbox->setChecked(profile->borderWhenActive());
0916     connect(_appearanceUi->borderWhenActiveCheckbox, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setBorderWhenActive);
0917 
0918     _appearanceUi->focusBorderColor->setColor(profile->focusBorderColor());
0919     _appearanceUi->focusBorderColor->setEnabled(profile->borderWhenActive());
0920     _appearanceUi->borderLabel->setEnabled(profile->borderWhenActive());
0921     connect(_appearanceUi->focusBorderColor, &KColorButton::changed, this, &Konsole::EditProfileDialog::focusBorderColorChanged);
0922 
0923     _appearanceUi->invertSelectionColorsCheckbox->setChecked(profile->property<bool>(Profile::InvertSelectionColors));
0924     connect(_appearanceUi->invertSelectionColorsCheckbox, &QCheckBox::toggled, this, [this](bool checked) {
0925         updateTempProfileProperty(Profile::InvertSelectionColors, checked);
0926     });
0927 
0928     _appearanceUi->displayVerticalLine->setChecked(profile->verticalLine());
0929     connect(_appearanceUi->displayVerticalLine, &QCheckBox::toggled, this, &EditProfileDialog::setVerticalLine);
0930 
0931     _appearanceUi->displayVerticalLineAtColumn->setValue(profile->verticalLineAtChar());
0932     connect(_appearanceUi->displayVerticalLineAtColumn, &QSpinBox::valueChanged, this, &EditProfileDialog::setVerticalLineColumn);
0933 
0934     auto *getNewButton = new KNSWidgets::Button(this);
0935     connect(getNewButton, &KNSWidgets::Button::dialogFinished, this, &Konsole::EditProfileDialog::gotNewColorSchemes);
0936 
0937     getNewButton->setText(QStringLiteral("Get New..."));
0938     getNewButton->setConfigFile(QStringLiteral("konsole.knsrc"));
0939     _appearanceUi->colorSchemesBtnLayout->addWidget(getNewButton);
0940 
0941     _appearanceUi->enableBidiRenderingButton->setChecked(profile->bidiRenderingEnabled());
0942     connect(_appearanceUi->enableBidiRenderingButton, &QPushButton::toggled, this, &EditProfileDialog::togglebidiRendering);
0943 
0944     _appearanceUi->enableBidiTableDirOverrideButton->setChecked(profile->property<bool>(Profile::BidiTableDirOverride));
0945     connect(_appearanceUi->enableBidiTableDirOverrideButton, &QPushButton::toggled, this, &EditProfileDialog::togglebidiTableDirOverride);
0946     _appearanceUi->enableBidiTableDirOverrideButton->setEnabled(profile->bidiRenderingEnabled());
0947 
0948     _appearanceUi->bidiLineLTR->setChecked(profile->property<bool>(Profile::BidiLineLTR));
0949     connect(_appearanceUi->bidiLineLTR, &QPushButton::toggled, this, &EditProfileDialog::togglebidiLineLTR);
0950     _appearanceUi->bidiLineLTR->setEnabled(profile->bidiRenderingEnabled());
0951 
0952     _appearanceUi->wordMode->setChecked(profile->property<bool>(Profile::WordMode));
0953     connect(_appearanceUi->wordMode, &QPushButton::toggled, this, &EditProfileDialog::toggleWordMode);
0954     _appearanceUi->wordModeAttr->setEnabled(profile->property<bool>(Profile::WordMode));
0955     _appearanceUi->wordModeAscii->setEnabled(profile->property<bool>(Profile::WordMode));
0956     _appearanceUi->wordModeBrahmic->setEnabled(profile->property<bool>(Profile::WordMode));
0957 
0958     _appearanceUi->wordModeAttr->setChecked(profile->property<bool>(Profile::WordModeAttr));
0959     connect(_appearanceUi->wordModeAttr, &QPushButton::toggled, this, &EditProfileDialog::toggleWordModeAttr);
0960 
0961     _appearanceUi->wordModeAscii->setChecked(profile->property<bool>(Profile::WordModeAscii));
0962     connect(_appearanceUi->wordModeAscii, &QPushButton::toggled, this, &EditProfileDialog::toggleWordModeAscii);
0963 
0964     _appearanceUi->wordModeBrahmic->setChecked(profile->property<bool>(Profile::WordModeBrahmic));
0965     connect(_appearanceUi->wordModeBrahmic, &QPushButton::toggled, this, &EditProfileDialog::toggleWordModeBrahmic);
0966 
0967     _appearanceUi->ignoreWcWidth->setChecked(profile->property<bool>(Profile::IgnoreWcWidth));
0968     connect(_appearanceUi->ignoreWcWidth, &QPushButton::toggled, this, &EditProfileDialog::toggleIgnoreWcWidth);
0969 }
0970 
0971 void EditProfileDialog::setAntialiasText(bool enable)
0972 {
0973     preview(Profile::AntiAliasFonts, enable);
0974     updateTempProfileProperty(Profile::AntiAliasFonts, enable);
0975 
0976     const QFont font = _profile->font();
0977     updateFontPreview(font);
0978 }
0979 
0980 void EditProfileDialog::toggleAllowLinkEscapeSequence(bool enable)
0981 {
0982     updateTempProfileProperty(Profile::AllowEscapedLinks, enable);
0983 }
0984 
0985 void EditProfileDialog::linkEscapeSequenceTextsChanged()
0986 {
0987     updateTempProfileProperty(Profile::EscapedLinksSchema, _mouseUi->linkEscapeSequenceTexts->text());
0988 }
0989 void EditProfileDialog::setVerticalLine(bool value)
0990 {
0991     updateTempProfileProperty(Profile::VerticalLine, value);
0992 }
0993 
0994 void EditProfileDialog::setVerticalLineColumn(int value)
0995 {
0996     updateTempProfileProperty(Profile::VerticalLineAtChar, value);
0997 }
0998 
0999 void EditProfileDialog::setBoldIntense(bool enable)
1000 {
1001     preview(Profile::BoldIntense, enable);
1002     updateTempProfileProperty(Profile::BoldIntense, enable);
1003 }
1004 
1005 void EditProfileDialog::useFontLineCharacters(bool enable)
1006 {
1007     preview(Profile::UseFontLineCharacters, enable);
1008     updateTempProfileProperty(Profile::UseFontLineCharacters, enable);
1009 }
1010 
1011 void EditProfileDialog::toggleBlinkingCursor(bool enable)
1012 {
1013     preview(Profile::BlinkingCursorEnabled, enable);
1014     updateTempProfileProperty(Profile::BlinkingCursorEnabled, enable);
1015 }
1016 
1017 void EditProfileDialog::setCursorShape(int index)
1018 {
1019     preview(Profile::CursorShape, index);
1020     updateTempProfileProperty(Profile::CursorShape, index);
1021 }
1022 
1023 void EditProfileDialog::autoCursorColor()
1024 {
1025     preview(Profile::UseCustomCursorColor, false);
1026     updateTempProfileProperty(Profile::UseCustomCursorColor, false);
1027 }
1028 
1029 void EditProfileDialog::customCursorColor()
1030 {
1031     preview(Profile::UseCustomCursorColor, true);
1032     updateTempProfileProperty(Profile::UseCustomCursorColor, true);
1033 }
1034 
1035 void EditProfileDialog::customCursorColorChanged(const QColor &color)
1036 {
1037     preview(Profile::CustomCursorColor, color);
1038     updateTempProfileProperty(Profile::CustomCursorColor, color);
1039 
1040     // ensure that custom cursor colors are enabled
1041     _appearanceUi->customCursorColorButton->click();
1042 }
1043 
1044 void EditProfileDialog::customCursorTextColorChanged(const QColor &color)
1045 {
1046     preview(Profile::CustomCursorTextColor, color);
1047     updateTempProfileProperty(Profile::CustomCursorTextColor, color);
1048 
1049     // ensure that custom cursor colors are enabled
1050     _appearanceUi->customCursorColorButton->click();
1051 }
1052 
1053 void EditProfileDialog::terminalMarginChanged(int margin)
1054 {
1055     preview(Profile::TerminalMargin, margin);
1056     updateTempProfileProperty(Profile::TerminalMargin, margin);
1057 }
1058 
1059 void EditProfileDialog::lineSpacingChanged(int spacing)
1060 {
1061     preview(Profile::LineSpacing, spacing);
1062     updateTempProfileProperty(Profile::LineSpacing, spacing);
1063 }
1064 
1065 void EditProfileDialog::setTerminalCenter(bool enable)
1066 {
1067     preview(Profile::TerminalCenter, enable);
1068     updateTempProfileProperty(Profile::TerminalCenter, enable);
1069 }
1070 
1071 void EditProfileDialog::toggleMouseWheelZoom(bool enable)
1072 {
1073     updateTempProfileProperty(Profile::MouseWheelZoomEnabled, enable);
1074 }
1075 
1076 void EditProfileDialog::toggleAlternateScrolling(bool enable)
1077 {
1078     updateTempProfileProperty(Profile::AlternateScrolling, enable);
1079 }
1080 
1081 void EditProfileDialog::toggleAllowColorFilter(bool enable)
1082 {
1083     updateTempProfileProperty(Profile::ColorFilterEnabled, enable);
1084 }
1085 
1086 void EditProfileDialog::toggleAllowMouseTracking(bool allow)
1087 {
1088     updateTempProfileProperty(Profile::AllowMouseTracking, allow);
1089 }
1090 
1091 void EditProfileDialog::updateColorSchemeList(const QString &selectedColorSchemeName)
1092 {
1093     if (_appearanceUi->colorSchemeList->model() == nullptr) {
1094         _appearanceUi->colorSchemeList->setModel(new QStandardItemModel(this));
1095     }
1096 
1097     std::shared_ptr<const ColorScheme> selectedColorScheme = ColorSchemeManager::instance()->findColorScheme(selectedColorSchemeName);
1098 
1099     auto *model = qobject_cast<QStandardItemModel *>(_appearanceUi->colorSchemeList->model());
1100 
1101     Q_ASSERT(model);
1102 
1103     model->clear();
1104 
1105     QStandardItem *selectedItem = nullptr;
1106 
1107     const QList<std::shared_ptr<const ColorScheme>> schemeList = ColorSchemeManager::instance()->allColorSchemes();
1108 
1109     for (const std::shared_ptr<const ColorScheme> &scheme : schemeList) {
1110         QStandardItem *item = new QStandardItem(scheme->description());
1111         item->setData(QVariant::fromValue(scheme), Qt::UserRole + 1);
1112         item->setData(QVariant::fromValue(_profile->font()), Qt::UserRole + 2);
1113         item->setFlags(item->flags());
1114 
1115         // if selectedColorSchemeName is not empty then select that scheme
1116         // after saving the changes in the colorScheme editor
1117         if (selectedColorScheme == scheme) {
1118             selectedItem = item;
1119         }
1120 
1121         model->appendRow(item);
1122     }
1123 
1124     model->sort(0);
1125 
1126     if (selectedItem != nullptr) {
1127         _appearanceUi->colorSchemeList->updateGeometry();
1128         _appearanceUi->colorSchemeList->selectionModel()->setCurrentIndex(selectedItem->index(), QItemSelectionModel::Select);
1129 
1130         // update transparency warning label
1131         updateTransparencyWarning();
1132     }
1133 }
1134 
1135 void EditProfileDialog::updateKeyBindingsList(const QString &selectKeyBindingsName)
1136 {
1137     if (_keyboardUi->keyBindingList->model() == nullptr) {
1138         _keyboardUi->keyBindingList->setModel(new QStandardItemModel(this));
1139     }
1140 
1141     auto *model = qobject_cast<QStandardItemModel *>(_keyboardUi->keyBindingList->model());
1142 
1143     Q_ASSERT(model);
1144 
1145     model->clear();
1146 
1147     QStandardItem *selectedItem = nullptr;
1148 
1149     const QStringList &translatorNames = _keyManager->allTranslators();
1150     for (const QString &translatorName : translatorNames) {
1151         const KeyboardTranslator *translator = _keyManager->findTranslator(translatorName);
1152         if (translator == nullptr) {
1153             continue;
1154         }
1155 
1156         QStandardItem *item = new QStandardItem(translator->description());
1157         item->setEditable(false);
1158         item->setData(QVariant::fromValue(translator), Qt::UserRole + 1);
1159         item->setData(QVariant::fromValue(_keyManager->findTranslatorPath(translatorName)), Qt::ToolTipRole);
1160         item->setData(QVariant::fromValue(_profile->font()), Qt::UserRole + 2);
1161         item->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-keyboard")));
1162 
1163         if (selectKeyBindingsName == translatorName) {
1164             selectedItem = item;
1165         }
1166 
1167         model->appendRow(item);
1168     }
1169 
1170     model->sort(0);
1171 
1172     if (selectedItem != nullptr) {
1173         _keyboardUi->keyBindingList->selectionModel()->setCurrentIndex(selectedItem->index(), QItemSelectionModel::Select);
1174     }
1175 }
1176 
1177 QSize EditProfileDialog::sizeHint() const
1178 {
1179     QFontMetrics fm(font());
1180     const int ch = fm.boundingRect(QLatin1Char('0')).width();
1181 
1182     // By default minimum size is used. Increase it to make text inputs
1183     // on "tabs" page wider and to add some whitespace on right side
1184     // of other pages. The window will not be wider than 2/3 of
1185     // the screen width (unless necessary to fit everything)
1186     return QDialog::sizeHint() + QSize(10 * ch, 0);
1187 }
1188 
1189 void EditProfileDialog::unpreviewAll()
1190 {
1191     // undo any preview changes
1192     if (!_previewedProperties.empty()) {
1193         ProfileManager::instance()->changeProfile(_profile, _previewedProperties, false);
1194     }
1195 }
1196 
1197 void EditProfileDialog::unpreview(Profile::Property prop)
1198 {
1199     auto node = _previewedProperties.extract(prop);
1200     if (!node) {
1201         return;
1202     }
1203 
1204     Profile::PropertyMap map;
1205     map.insert(std::move(node));
1206     ProfileManager::instance()->changeProfile(_profile, map, false);
1207 }
1208 
1209 void EditProfileDialog::preview(Profile::Property prop, const QVariant &value)
1210 {
1211     const Profile::Ptr original = _profile;
1212 
1213     // skip previews for profile groups if the profiles in the group
1214     // have conflicting original values for the property
1215     //
1216     // TODO - Save the original values for each profile and use to unpreview properties
1217     ProfileGroup::Ptr group = original->asGroup();
1218     if (group && group->profiles().count() > 1 && original->property<QVariant>(prop).isNull()) {
1219         return;
1220     }
1221 
1222     _previewedProperties.insert({prop, original->property<QVariant>(prop)});
1223 
1224     // temporary change to color scheme
1225     ProfileManager::instance()->changeProfile(_profile, {{prop, value}}, false);
1226 }
1227 
1228 void EditProfileDialog::previewColorScheme(const QModelIndex &index)
1229 {
1230     const QString &name = index.data(Qt::UserRole + 1).value<std::shared_ptr<const ColorScheme>>()->name();
1231     preview(Profile::ColorScheme, name);
1232 }
1233 
1234 void EditProfileDialog::showFontDialog()
1235 {
1236     if (_fontDialog == nullptr) {
1237         _fontDialog = new FontDialog(this);
1238         _fontDialog->setModal(true);
1239         connect(_fontDialog, &FontDialog::fontChanged, this, [this](const QFont &font) {
1240             preview(Profile::Font, font);
1241             updateFontPreview(font);
1242         });
1243         connect(_fontDialog, &FontDialog::accepted, this, [this]() {
1244             const QFont font = _fontDialog->font();
1245             preview(Profile::Font, font);
1246             updateTempProfileProperty(Profile::Font, font);
1247             updateFontPreview(font);
1248         });
1249         connect(_fontDialog, &FontDialog::rejected, this, [this]() {
1250             unpreview(Profile::Font);
1251             updateFontPreview(_profile->font());
1252         });
1253     }
1254     _fontDialog->setFont(_profile->font());
1255     _fontDialog->show();
1256 }
1257 
1258 void EditProfileDialog::showEmojiFontDialog()
1259 {
1260     if (_emojiFontDialog == nullptr) {
1261         _emojiFontDialog = new FontDialog(this, true, _profile->emojiFont());
1262         _emojiFontDialog->setModal(true);
1263         connect(_emojiFontDialog, &FontDialog::fontChanged, this, [this](const QFont &font) {
1264             preview(Profile::EmojiFont, font);
1265             updateEmojiFontPreview(font);
1266         });
1267         connect(_emojiFontDialog, &FontDialog::accepted, this, [this]() {
1268             const QFont font = _emojiFontDialog->font();
1269             preview(Profile::EmojiFont, font);
1270             updateTempProfileProperty(Profile::EmojiFont, font);
1271             updateEmojiFontPreview(font);
1272         });
1273         connect(_emojiFontDialog, &FontDialog::rejected, this, [this]() {
1274             unpreview(Profile::EmojiFont);
1275             updateEmojiFontPreview(_profile->emojiFont());
1276         });
1277     }
1278     _emojiFontDialog->show();
1279 }
1280 
1281 void EditProfileDialog::updateFontPreview(QFont font)
1282 {
1283     bool aa = _profile->antiAliasFonts();
1284     font.setStyleStrategy(aa ? QFont::PreferAntialias : QFont::NoAntialias);
1285 
1286     _appearanceUi->fontPreview->setFont(font);
1287     _appearanceUi->fontPreview->setText(QStringLiteral("%1 %2pt").arg(font.family()).arg(font.pointSize()));
1288 }
1289 
1290 void EditProfileDialog::updateEmojiFontPreview(QFont font)
1291 {
1292     bool aa = _profile->antiAliasFonts();
1293     font.setStyleStrategy(aa ? QFont::PreferAntialias : QFont::NoAntialias);
1294     QFont emojiFont = _profile->emojiFont();
1295     _appearanceUi->emojiFontPreview->setFont(_profile->font());
1296     _appearanceUi->emojiFontPreview->setText(QStringLiteral("%1 %2pt").arg(emojiFont.family()).arg(emojiFont.pointSize()));
1297 }
1298 
1299 void EditProfileDialog::removeColorScheme()
1300 {
1301     const QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes();
1302     if (selected.isEmpty()) {
1303         return;
1304     }
1305     const QString &name = selected.first().data(Qt::UserRole + 1).value<std::shared_ptr<const ColorScheme>>()->name();
1306     Q_ASSERT(!name.isEmpty());
1307     if (ColorSchemeManager::instance()->deleteColorScheme(name)) {
1308         _appearanceUi->colorSchemeList->model()->removeRow(selected.first().row());
1309     }
1310 }
1311 
1312 void EditProfileDialog::gotNewColorSchemes(const QList<KNSCore::Entry> &changedEntries)
1313 {
1314     int failures = 0;
1315     for (auto &entry : std::as_const(changedEntries)) {
1316         switch (entry.status()) {
1317         case KNSCore::Entry::Installed:
1318             for (const QString &file : entry.installedFiles()) {
1319                 if (ColorSchemeManager::instance()->loadColorScheme(file)) {
1320                     continue;
1321                 }
1322                 qWarning() << "Failed to load file" << file;
1323                 ++failures;
1324             }
1325             if (failures == entry.installedFiles().size()) {
1326                 _appearanceUi->colorSchemeMessageWidget->setText(xi18nc("@info", "Scheme <resource>%1</resource> failed to load.", entry.name()));
1327                 _appearanceUi->colorSchemeMessageWidget->animatedShow();
1328                 QTimer::singleShot(8000, _appearanceUi->colorSchemeMessageWidget, &KMessageWidget::animatedHide);
1329             }
1330             break;
1331         case KNSCore::Entry::Deleted:
1332             for (const auto &file : entry.uninstalledFiles()) {
1333                 if (ColorSchemeManager::instance()->unloadColorScheme(file)) {
1334                     continue;
1335                 }
1336                 qWarning() << "Failed to unload file" << file;
1337                 // If unloading fails we do not care. If the scheme failed here
1338                 // it either wasn't loaded or was invalid to begin with.
1339             }
1340             break;
1341         case KNSCore::Entry::Invalid:
1342         case KNSCore::Entry::Installing:
1343         case KNSCore::Entry::Downloadable:
1344         case KNSCore::Entry::Updateable:
1345         case KNSCore::Entry::Updating:
1346             // Not interesting.
1347             break;
1348         }
1349     }
1350     updateColorSchemeList(currentColorSchemeName());
1351 }
1352 
1353 void EditProfileDialog::resetColorScheme()
1354 {
1355     QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes();
1356 
1357     if (!selected.isEmpty()) {
1358         const QString &name = selected.first().data(Qt::UserRole + 1).value<std::shared_ptr<const ColorScheme>>()->name();
1359 
1360         ColorSchemeManager::instance()->deleteColorScheme(name);
1361 
1362         // select the colorScheme used in the current profile
1363         updateColorSchemeList(currentColorSchemeName());
1364     }
1365 }
1366 
1367 void EditProfileDialog::showColorSchemeEditor(bool isNewScheme)
1368 {
1369     // Finding selected ColorScheme
1370     QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes();
1371     QAbstractItemModel *model = _appearanceUi->colorSchemeList->model();
1372     std::shared_ptr<const ColorScheme> colors;
1373     if (!selected.isEmpty()) {
1374         colors = model->data(selected.first(), Qt::UserRole + 1).value<std::shared_ptr<const ColorScheme>>();
1375     } else {
1376         colors = ColorSchemeManager::instance()->defaultColorScheme();
1377     }
1378 
1379     Q_ASSERT(colors);
1380 
1381     // Setting up ColorSchemeEditor ui
1382     // close any running ColorSchemeEditor
1383     if (_colorDialog != nullptr) {
1384         closeColorSchemeEditor();
1385     }
1386     _colorDialog = new ColorSchemeEditor(WindowSystemInfo::compositingActive(), this);
1387 
1388     connect(_colorDialog, &Konsole::ColorSchemeEditor::colorSchemeSaveRequested, this, &Konsole::EditProfileDialog::saveColorScheme);
1389     _colorDialog->setup(colors, isNewScheme);
1390 
1391     _colorDialog->show();
1392 }
1393 
1394 void EditProfileDialog::closeColorSchemeEditor()
1395 {
1396     if (_colorDialog != nullptr) {
1397         _colorDialog->close();
1398         delete _colorDialog;
1399     }
1400 }
1401 
1402 void EditProfileDialog::newColorScheme()
1403 {
1404     showColorSchemeEditor(true);
1405 }
1406 
1407 void EditProfileDialog::editColorScheme()
1408 {
1409     showColorSchemeEditor(false);
1410 }
1411 
1412 void EditProfileDialog::saveColorScheme(const ColorScheme &scheme, bool isNewScheme)
1413 {
1414     std::shared_ptr<ColorScheme> newScheme = std::make_shared<ColorScheme>(scheme);
1415 
1416     // if this is a new color scheme, pick a name based on the description
1417     if (isNewScheme) {
1418         newScheme->setName(newScheme->description());
1419     }
1420 
1421     ColorSchemeManager::instance()->addColorScheme(newScheme);
1422 
1423     const QString &selectedColorSchemeName = newScheme->name();
1424 
1425     // select the edited or the new colorScheme after saving the changes
1426     updateColorSchemeList(selectedColorSchemeName);
1427 
1428     preview(Profile::ColorScheme, newScheme->name());
1429 }
1430 
1431 void EditProfileDialog::colorSchemeSelected()
1432 {
1433     QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes();
1434 
1435     if (!selected.isEmpty()) {
1436         QAbstractItemModel *model = _appearanceUi->colorSchemeList->model();
1437         std::shared_ptr<const ColorScheme> colors = model->data(selected.first(), Qt::UserRole + 1).value<std::shared_ptr<const ColorScheme>>();
1438         if (colors != nullptr) {
1439             updateTempProfileProperty(Profile::ColorScheme, colors->name());
1440             previewColorScheme(selected.first());
1441 
1442             updateTransparencyWarning();
1443         }
1444     }
1445 
1446     updateColorSchemeButtons();
1447 }
1448 
1449 void EditProfileDialog::updateColorSchemeButtons()
1450 {
1451     enableIfNonEmptySelection(_appearanceUi->editColorSchemeButton, _appearanceUi->colorSchemeList->selectionModel());
1452 
1453     QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes();
1454 
1455     if (!selected.isEmpty()) {
1456         const QString &name = selected.first().data(Qt::UserRole + 1).value<std::shared_ptr<const ColorScheme>>()->name();
1457 
1458         bool isResettable = ColorSchemeManager::instance()->canResetColorScheme(name);
1459         _appearanceUi->resetColorSchemeButton->setEnabled(isResettable);
1460 
1461         bool isDeletable = ColorSchemeManager::instance()->isColorSchemeDeletable(name);
1462         // if a colorScheme can be restored then it can't be deleted
1463         _appearanceUi->removeColorSchemeButton->setEnabled(isDeletable && !isResettable);
1464     } else {
1465         _appearanceUi->removeColorSchemeButton->setEnabled(false);
1466         _appearanceUi->resetColorSchemeButton->setEnabled(false);
1467     }
1468 }
1469 
1470 void EditProfileDialog::updateKeyBindingsButtons()
1471 {
1472     QModelIndexList selected = _keyboardUi->keyBindingList->selectionModel()->selectedIndexes();
1473 
1474     if (!selected.isEmpty()) {
1475         _keyboardUi->editKeyBindingsButton->setEnabled(true);
1476 
1477         const QString &name = selected.first().data(Qt::UserRole + 1).value<const KeyboardTranslator *>()->name();
1478 
1479         bool isResettable = _keyManager->isTranslatorResettable(name);
1480         _keyboardUi->resetKeyBindingsButton->setEnabled(isResettable);
1481 
1482         bool isDeletable = _keyManager->isTranslatorDeletable(name);
1483 
1484         // if a key bindings scheme can be reset then it can't be deleted
1485         _keyboardUi->removeKeyBindingsButton->setEnabled(isDeletable && !isResettable);
1486     }
1487 }
1488 
1489 void EditProfileDialog::enableIfNonEmptySelection(QWidget *widget, QItemSelectionModel *selectionModel)
1490 {
1491     widget->setEnabled(selectionModel->hasSelection());
1492 }
1493 
1494 void EditProfileDialog::updateTransparencyWarning()
1495 {
1496     // zero or one indexes can be selected
1497     const QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes();
1498     for (const QModelIndex &index : selected) {
1499         bool needTransparency = index.data(Qt::UserRole + 1).value<std::shared_ptr<const ColorScheme>>()->opacity() < 1.0;
1500 
1501         if (!needTransparency) {
1502             _appearanceUi->transparencyWarningWidget->setHidden(true);
1503         } else if (!WindowSystemInfo::compositingActive()) {
1504             _appearanceUi->transparencyWarningWidget->setText(
1505                 i18n("This color scheme uses a transparent background"
1506                      " which does not appear to be supported on your"
1507                      " desktop"));
1508             _appearanceUi->transparencyWarningWidget->setHidden(false);
1509         } else if (!WindowSystemInfo::HAVE_TRANSPARENCY) {
1510             _appearanceUi->transparencyWarningWidget->setText(
1511                 i18n("Konsole was started before desktop effects were enabled."
1512                      " You need to restart Konsole to see transparent background."));
1513             _appearanceUi->transparencyWarningWidget->setHidden(false);
1514         }
1515     }
1516 }
1517 
1518 void EditProfileDialog::resetTempProfile()
1519 {
1520     _tempProfile->clear();
1521     _tempProfile->setHidden(true);
1522 }
1523 
1524 void EditProfileDialog::updateTempProfileProperty(Profile::Property property, const QVariant &value)
1525 {
1526     _tempProfile->setProperty(property, value);
1527     updateButtonApply();
1528 }
1529 
1530 void EditProfileDialog::updateButtonApply()
1531 {
1532     auto filterFunc = [this](const auto &keyValue) {
1533         const auto &[property, value] = keyValue;
1534         // for previewed property
1535         auto it = _previewedProperties.find(property);
1536         if (it != _previewedProperties.cend()) {
1537             if (value != it->second) {
1538                 return true;
1539             }
1540             // for not-previewed property
1541             //
1542             // for the Profile::KeyBindings property, if it's set in the _tempProfile
1543             // then the user opened the edit key bindings dialog and clicked
1544             // OK, and could have add/removed a key bindings rule
1545         } else if (property == Profile::KeyBindings //
1546                    || value != _profile->property<QVariant>(property)) {
1547             return true;
1548         }
1549 
1550         return false;
1551     };
1552 
1553     const Profile::PropertyMap &map = _tempProfile->properties();
1554     bool userModified = std::any_of(map.cbegin(), map.cend(), filterFunc);
1555 
1556     if (_generalUi->setAsDefaultButton->isChecked() != _isDefault) {
1557         userModified = true;
1558     }
1559 
1560     _buttonBox->button(QDialogButtonBox::Apply)->setEnabled(userModified);
1561 }
1562 
1563 void EditProfileDialog::setupKeyboardPage(const Profile::Ptr & /* profile */)
1564 {
1565     // setup translator list
1566     updateKeyBindingsList(_profile->keyBindings());
1567 
1568     connect(_keyboardUi->keyBindingList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Konsole::EditProfileDialog::keyBindingSelected);
1569     connect(_keyboardUi->newKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::newKeyBinding);
1570 
1571     _keyboardUi->editKeyBindingsButton->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
1572     _keyboardUi->removeKeyBindingsButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
1573     _keyboardUi->newKeyBindingsButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
1574     _keyboardUi->resetKeyBindingsButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
1575 
1576     _keyboardUi->editKeyBindingsButton->setEnabled(false);
1577     _keyboardUi->removeKeyBindingsButton->setEnabled(false);
1578     _keyboardUi->resetKeyBindingsButton->setEnabled(false);
1579 
1580     updateKeyBindingsButtons();
1581 
1582     connect(_keyboardUi->editKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::editKeyBinding);
1583     connect(_keyboardUi->removeKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::removeKeyBinding);
1584     connect(_keyboardUi->resetKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::resetKeyBindings);
1585 }
1586 
1587 void EditProfileDialog::keyBindingSelected()
1588 {
1589     QModelIndexList selected = _keyboardUi->keyBindingList->selectionModel()->selectedIndexes();
1590 
1591     if (!selected.isEmpty()) {
1592         QAbstractItemModel *model = _keyboardUi->keyBindingList->model();
1593         const auto *translator = model->data(selected.first(), Qt::UserRole + 1).value<const KeyboardTranslator *>();
1594         if (translator != nullptr) {
1595             updateTempProfileProperty(Profile::KeyBindings, translator->name());
1596         }
1597     }
1598 
1599     updateKeyBindingsButtons();
1600 }
1601 
1602 void EditProfileDialog::removeKeyBinding()
1603 {
1604     QModelIndexList selected = _keyboardUi->keyBindingList->selectionModel()->selectedIndexes();
1605 
1606     if (!selected.isEmpty()) {
1607         const QString &name = selected.first().data(Qt::UserRole + 1).value<const KeyboardTranslator *>()->name();
1608         if (KeyboardTranslatorManager::instance()->deleteTranslator(name)) {
1609             _keyboardUi->keyBindingList->model()->removeRow(selected.first().row());
1610         }
1611     }
1612 }
1613 
1614 void EditProfileDialog::showKeyBindingEditor(bool isNewTranslator)
1615 {
1616     QModelIndexList selected = _keyboardUi->keyBindingList->selectionModel()->selectedIndexes();
1617     QAbstractItemModel *model = _keyboardUi->keyBindingList->model();
1618 
1619     const KeyboardTranslator *translator = nullptr;
1620     if (!selected.isEmpty()) {
1621         translator = model->data(selected.first(), Qt::UserRole + 1).value<const KeyboardTranslator *>();
1622     } else {
1623         translator = _keyManager->defaultTranslator();
1624     }
1625 
1626     auto *editor = new KeyBindingEditor(this);
1627     editor->setAttribute(Qt::WA_DeleteOnClose);
1628     editor->setModal(true);
1629 
1630     if (translator != nullptr) {
1631         editor->setup(translator, _profile->keyBindings(), isNewTranslator);
1632     }
1633 
1634     connect(editor, &Konsole::KeyBindingEditor::updateKeyBindingsListRequest, this, &Konsole::EditProfileDialog::updateKeyBindingsList);
1635     connect(editor, &Konsole::KeyBindingEditor::updateTempProfileKeyBindingsRequest, this, &Konsole::EditProfileDialog::updateTempProfileProperty);
1636 
1637     editor->show();
1638 }
1639 
1640 void EditProfileDialog::newKeyBinding()
1641 {
1642     showKeyBindingEditor(true);
1643 }
1644 
1645 void EditProfileDialog::editKeyBinding()
1646 {
1647     showKeyBindingEditor(false);
1648 }
1649 
1650 void EditProfileDialog::resetKeyBindings()
1651 {
1652     QModelIndexList selected = _keyboardUi->keyBindingList->selectionModel()->selectedIndexes();
1653 
1654     if (!selected.isEmpty()) {
1655         const QString &name = selected.first().data(Qt::UserRole + 1).value<const KeyboardTranslator *>()->name();
1656 
1657         _keyManager->deleteTranslator(name);
1658         // find and load the translator
1659         _keyManager->findTranslator(name);
1660 
1661         updateKeyBindingsList(name);
1662     }
1663 }
1664 
1665 void EditProfileDialog::setupButtonGroup(const ButtonGroupOptions &options, const Profile::Ptr &profile)
1666 {
1667     auto currentValue = profile->property<int>(options.profileProperty);
1668 
1669     for (auto option : options.buttons) {
1670         options.group->setId(option.button, option.value);
1671     }
1672 
1673     Q_ASSERT(options.buttons.count() > 0);
1674     auto *activeButton = options.group->button(currentValue);
1675     if (activeButton == nullptr) {
1676         activeButton = options.buttons[0].button;
1677     }
1678     activeButton->setChecked(true);
1679     connect(options.group, &QButtonGroup::idClicked, this, [this, options](int value) {
1680         if (options.preview) {
1681             preview(options.profileProperty, value);
1682         }
1683         updateTempProfileProperty(options.profileProperty, value);
1684     });
1685 }
1686 
1687 void EditProfileDialog::setupScrollingPage(const Profile::Ptr &profile)
1688 {
1689     // setup scrollbar radio
1690     const ButtonGroupOptions scrollBarPositionOptions = {
1691         _scrollingUi->scrollBarPosition, // group
1692         Profile::ScrollBarPosition, // profileProperty
1693         false, // preview
1694         {
1695             // buttons
1696             {_scrollingUi->scrollBarRightButton, Enum::ScrollBarRight},
1697             {_scrollingUi->scrollBarLeftButton, Enum::ScrollBarLeft},
1698             {_scrollingUi->scrollBarHiddenButton, Enum::ScrollBarHidden},
1699         },
1700     };
1701     setupButtonGroup(scrollBarPositionOptions, profile);
1702 
1703     // setup scrollback type radio
1704     auto scrollBackType = profile->property<int>(Profile::HistoryMode);
1705     _scrollingUi->historySizeWidget->setMode(Enum::HistoryModeEnum(scrollBackType));
1706     connect(_scrollingUi->historySizeWidget, &Konsole::HistorySizeWidget::historyModeChanged, this, &Konsole::EditProfileDialog::historyModeChanged);
1707 
1708     // setup scrollback line count spinner
1709     const int historySize = profile->historySize();
1710     _scrollingUi->historySizeWidget->setLineCount(historySize);
1711 
1712     // setup scrollpageamount type radio
1713     auto scrollFullPage = profile->property<int>(Profile::ScrollFullPage);
1714 
1715     _scrollingUi->scrollHalfPage->setChecked(Enum::ScrollPageHalf == scrollFullPage);
1716     connect(_scrollingUi->scrollHalfPage, &QPushButton::clicked, this, &EditProfileDialog::scrollHalfPage);
1717 
1718     _scrollingUi->scrollFullPage->setChecked(Enum::ScrollPageFull == scrollFullPage);
1719     connect(_scrollingUi->scrollFullPage, &QPushButton::clicked, this, &EditProfileDialog::scrollFullPage);
1720 
1721     _scrollingUi->highlightScrolledLinesButton->setChecked(profile->property<bool>(Profile::HighlightScrolledLines));
1722     connect(_scrollingUi->highlightScrolledLinesButton, &QPushButton::clicked, this, &EditProfileDialog::toggleHighlightScrolledLines);
1723 
1724     _scrollingUi->reflowLinesButton->setChecked(profile->property<bool>(Profile::ReflowLines));
1725     connect(_scrollingUi->reflowLinesButton, &QPushButton::clicked, this, &EditProfileDialog::toggleReflowLines);
1726 
1727     // signals and slots
1728     connect(_scrollingUi->historySizeWidget, &Konsole::HistorySizeWidget::historySizeChanged, this, &Konsole::EditProfileDialog::historySizeChanged);
1729 }
1730 
1731 void EditProfileDialog::historySizeChanged(int lineCount)
1732 {
1733     updateTempProfileProperty(Profile::HistorySize, lineCount);
1734 }
1735 
1736 void EditProfileDialog::historyModeChanged(Enum::HistoryModeEnum mode)
1737 {
1738     updateTempProfileProperty(Profile::HistoryMode, mode);
1739 }
1740 
1741 void EditProfileDialog::scrollFullPage()
1742 {
1743     updateTempProfileProperty(Profile::ScrollFullPage, Enum::ScrollPageFull);
1744 }
1745 
1746 void EditProfileDialog::scrollHalfPage()
1747 {
1748     updateTempProfileProperty(Profile::ScrollFullPage, Enum::ScrollPageHalf);
1749 }
1750 
1751 void EditProfileDialog::toggleHighlightScrolledLines(bool enable)
1752 {
1753     updateTempProfileProperty(Profile::HighlightScrolledLines, enable);
1754 }
1755 
1756 void EditProfileDialog::toggleReflowLines(bool enable)
1757 {
1758     updateTempProfileProperty(Profile::ReflowLines, enable);
1759 }
1760 
1761 void EditProfileDialog::setupMousePage(const Profile::Ptr &profile)
1762 {
1763     _mouseUi->underlineLinksButton->setChecked(profile->property<bool>(Profile::UnderlineLinksEnabled));
1764     connect(_mouseUi->underlineLinksButton, &QPushButton::toggled, this, &EditProfileDialog::toggleUnderlineLinks);
1765     _mouseUi->underlineFilesButton->setChecked(profile->property<bool>(Profile::UnderlineFilesEnabled));
1766     connect(_mouseUi->underlineFilesButton, &QPushButton::toggled, this, &EditProfileDialog::toggleUnderlineFiles);
1767     _mouseUi->ctrlRequiredForDragButton->setChecked(profile->property<bool>(Profile::CtrlRequiredForDrag));
1768     connect(_mouseUi->ctrlRequiredForDragButton, &QPushButton::toggled, this, &EditProfileDialog::toggleCtrlRequiredForDrag);
1769     _mouseUi->copyTextAsHTMLButton->setChecked(profile->property<bool>(Profile::CopyTextAsHTML));
1770     connect(_mouseUi->copyTextAsHTMLButton, &QPushButton::toggled, this, &EditProfileDialog::toggleCopyTextAsHTML);
1771     _mouseUi->copyTextToClipboardButton->setChecked(profile->property<bool>(Profile::AutoCopySelectedText));
1772     connect(_mouseUi->copyTextToClipboardButton, &QPushButton::toggled, this, &EditProfileDialog::toggleCopyTextToClipboard);
1773     _mouseUi->trimLeadingSpacesButton->setChecked(profile->property<bool>(Profile::TrimLeadingSpacesInSelectedText));
1774     connect(_mouseUi->trimLeadingSpacesButton, &QPushButton::toggled, this, &EditProfileDialog::toggleTrimLeadingSpacesInSelectedText);
1775     _mouseUi->trimTrailingSpacesButton->setChecked(profile->property<bool>(Profile::TrimTrailingSpacesInSelectedText));
1776     connect(_mouseUi->trimTrailingSpacesButton, &QPushButton::toggled, this, &EditProfileDialog::toggleTrimTrailingSpacesInSelectedText);
1777     _mouseUi->openLinksByDirectClickButton->setChecked(profile->property<bool>(Profile::OpenLinksByDirectClickEnabled));
1778     connect(_mouseUi->openLinksByDirectClickButton, &QPushButton::toggled, this, &EditProfileDialog::toggleOpenLinksByDirectClick);
1779     _mouseUi->dropUrlsAsText->setChecked(profile->property<bool>(Profile::DropUrlsAsText));
1780     connect(_mouseUi->dropUrlsAsText, &QPushButton::toggled, this, &EditProfileDialog::toggleDropUrlsAsText);
1781     _mouseUi->enableAlternateScrollingButton->setChecked(profile->property<bool>(Profile::AlternateScrolling));
1782     connect(_mouseUi->enableAlternateScrollingButton, &QPushButton::toggled, this, &EditProfileDialog::toggleAlternateScrolling);
1783     _mouseUi->allowColorFilters->setChecked(profile->property<bool>(Profile::ColorFilterEnabled));
1784     connect(_mouseUi->allowColorFilters, &QPushButton::toggled, this, &EditProfileDialog::toggleAllowColorFilter);
1785     _mouseUi->allowMouseTrackingButton->setChecked(profile->property<bool>(Profile::AllowMouseTracking));
1786     connect(_mouseUi->allowMouseTrackingButton, &QPushButton::toggled, this, &EditProfileDialog::toggleAllowMouseTracking);
1787 
1788     // setup middle click paste mode
1789     const auto middleClickPasteMode = profile->property<int>(Profile::MiddleClickPasteMode);
1790     _mouseUi->pasteFromX11SelectionButton->setChecked(Enum::PasteFromX11Selection == middleClickPasteMode);
1791     connect(_mouseUi->pasteFromX11SelectionButton, &QPushButton::clicked, this, &EditProfileDialog::pasteFromX11Selection);
1792     _mouseUi->pasteFromClipboardButton->setChecked(Enum::PasteFromClipboard == middleClickPasteMode);
1793     connect(_mouseUi->pasteFromClipboardButton, &QPushButton::clicked, this, &EditProfileDialog::pasteFromClipboard);
1794 
1795     _mouseUi->textEditorCustomBtn->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
1796 
1797     // interaction options
1798     _mouseUi->wordCharacterEdit->setText(profile->wordCharacters());
1799 
1800     connect(_mouseUi->wordCharacterEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::wordCharactersChanged);
1801 
1802     const ButtonGroupOptions tripleClickModeOptions = {
1803         _mouseUi->tripleClickMode, // group
1804         Profile::TripleClickMode, // profileProperty
1805         false, // preview
1806         {
1807             // buttons
1808             {_mouseUi->tripleClickSelectsTheWholeLine, Enum::SelectWholeLine},
1809             {_mouseUi->tripleClickSelectsFromMousePosition, Enum::SelectForwardsFromCursor},
1810         },
1811     };
1812     setupButtonGroup(tripleClickModeOptions, profile);
1813 
1814     _mouseUi->openLinksByDirectClickButton->setEnabled(_mouseUi->underlineLinksButton->isChecked() || _mouseUi->underlineFilesButton->isChecked());
1815 
1816     _mouseUi->enableMouseWheelZoomButton->setChecked(profile->mouseWheelZoomEnabled());
1817     connect(_mouseUi->enableMouseWheelZoomButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::toggleMouseWheelZoom);
1818 
1819     _mouseUi->allowLinkEscapeSequenceButton->setChecked(profile->allowEscapedLinks());
1820     connect(_mouseUi->allowLinkEscapeSequenceButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::toggleAllowLinkEscapeSequence);
1821 
1822     _mouseUi->linkEscapeSequenceTexts->setEnabled(profile->allowEscapedLinks());
1823     _mouseUi->linkEscapeSequenceTexts->setText(profile->escapedLinksSchema().join(QLatin1Char(';')));
1824     connect(_mouseUi->linkEscapeSequenceTexts, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::linkEscapeSequenceTextsChanged);
1825 
1826     setTextEditorCombo(profile);
1827 }
1828 
1829 void EditProfileDialog::setTextEditorCombo(const Profile::Ptr &profile)
1830 {
1831     static const Enum::TextEditorCmd editorsList[] =
1832         {Enum::Kate, Enum::KWrite, Enum::KDevelop, Enum::QtCreator, Enum::Gedit, Enum::gVim, Enum::CustomTextEditor};
1833 
1834     auto *editorCombo = _mouseUi->textEditorCombo;
1835 
1836     QStandardItemModel *model = static_cast<QStandardItemModel *>(editorCombo->model());
1837     Q_ASSERT(model);
1838 
1839     for (auto editor : editorsList) {
1840         QString exec;
1841         QString displayName;
1842         QIcon icon;
1843         switch (editor) {
1844         case Enum::Kate:
1845             exec = QStringLiteral("kate");
1846             displayName = QStringLiteral("Kate");
1847             icon = QIcon::fromTheme(exec);
1848             break;
1849         case Enum::KWrite:
1850             exec = QStringLiteral("kwrite");
1851             displayName = QStringLiteral("KWrite");
1852             icon = QIcon::fromTheme(exec);
1853             break;
1854         case Enum::KDevelop:
1855             exec = QStringLiteral("kdevelop");
1856             displayName = QStringLiteral("KDevelop");
1857             icon = QIcon::fromTheme(exec);
1858             break;
1859         case Enum::QtCreator:
1860             exec = QStringLiteral("qtcreator");
1861             displayName = QStringLiteral("Qt Creator");
1862             icon = QIcon::fromTheme(exec);
1863             break;
1864         case Enum::Gedit:
1865             exec = QStringLiteral("gedit");
1866             displayName = QStringLiteral("Gedit");
1867             QIcon::fromTheme(QStringLiteral("org.gnome.gedit"));
1868             break;
1869         case Enum::gVim:
1870             exec = QStringLiteral("gvim");
1871             displayName = QStringLiteral("gVim");
1872             icon = QIcon::fromTheme(exec);
1873             break;
1874         case Enum::CustomTextEditor:
1875             displayName = QStringLiteral("Custom");
1876             icon = QIcon::fromTheme(QStringLiteral("system-run"));
1877             break;
1878         default:
1879             break;
1880         }
1881 
1882         editorCombo->addItem(icon, displayName);
1883 
1884         // For "CustomTextEditor" we don't check if the binary exists
1885         const bool isAvailable = editor == Enum::CustomTextEditor || !QStandardPaths::findExecutable(exec).isEmpty();
1886         // Make un-available editors look disabled in the combobox
1887         model->item(static_cast<int>(editor))->setEnabled(isAvailable);
1888     }
1889 
1890     const auto currentEditor = profile->property<int>(Profile::TextEditorCmd);
1891     editorCombo->setCurrentIndex(currentEditor);
1892 
1893     connect(editorCombo, &QComboBox::currentIndexChanged, this, [this](const int index) {
1894         updateTempProfileProperty(Profile::TextEditorCmd, index);
1895         _mouseUi->textEditorCustomBtn->setEnabled(index == Enum::CustomTextEditor);
1896     });
1897 
1898     _mouseUi->textEditorCustomBtn->setEnabled(currentEditor == Enum::CustomTextEditor);
1899     connect(_mouseUi->textEditorCustomBtn, &QAbstractButton::clicked, this, [this, profile]() {
1900         auto *dlg = new QInputDialog(static_cast<QWidget *>(this));
1901         dlg->setLabelText(
1902             i18n("The format is e.g. 'editorExec PATH:LINE:COLUMN'\n\n"
1903                  "PATH    will be replaced by the path to the text file\n"
1904                  "LINE    will be replaced by the line number\n"
1905                  "COLUMN  (optional) will be replaced by the column number\n"
1906                  "Note: you will need to replace 'PATH:LINE:COLUMN' by the actual\n"
1907                  "syntax the editor you want to use supports; e.g.:\n"
1908                  "gedit +LINE:COLUMN PATH\n\n"
1909                  "If PATH or LINE aren't present in the command, this setting\n"
1910                  "will be ignored and the file will be opened by the default text\n"
1911                  "editor."));
1912         const QString cmd = profile->customTextEditorCmd();
1913         dlg->setTextValue(cmd);
1914         dlg->setAttribute(Qt::WA_DeleteOnClose);
1915         dlg->setWindowTitle(i18n("Text Editor Custom Command"));
1916 
1917         QFontMetrics fm(font());
1918         const int width = qMin(fm.averageCharWidth() * cmd.size(), this->width());
1919         dlg->resize(width, dlg->height());
1920 
1921         connect(dlg, &QDialog::accepted, this, [this, dlg]() {
1922             updateTempProfileProperty(Profile::TextEditorCmdCustom, dlg->textValue());
1923         });
1924 
1925         dlg->show();
1926     });
1927 }
1928 
1929 void EditProfileDialog::setupAdvancedPage(const Profile::Ptr &profile)
1930 {
1931     _advancedUi->enableBlinkingTextButton->setChecked(profile->property<bool>(Profile::BlinkingTextEnabled));
1932     connect(_advancedUi->enableBlinkingTextButton, &QPushButton::toggled, this, &EditProfileDialog::toggleBlinkingText);
1933     _advancedUi->enableFlowControlButton->setChecked(profile->property<bool>(Profile::FlowControlEnabled));
1934     connect(_advancedUi->enableFlowControlButton, &QPushButton::toggled, this, &EditProfileDialog::toggleFlowControl);
1935     _appearanceUi->enableBlinkingCursorButton->setChecked(profile->property<bool>(Profile::BlinkingCursorEnabled));
1936     connect(_appearanceUi->enableBlinkingCursorButton, &QPushButton::toggled, this, &EditProfileDialog::toggleBlinkingCursor);
1937     _advancedUi->enableReverseUrlHints->setChecked(profile->property<bool>(Profile::ReverseUrlHints));
1938     connect(_advancedUi->enableReverseUrlHints, &QPushButton::toggled, this, &EditProfileDialog::toggleReverseUrlHints);
1939 
1940     // Setup the URL hints modifier checkboxes
1941     {
1942         auto modifiers = profile->property<int>(Profile::UrlHintsModifiers);
1943         _advancedUi->urlHintsModifierShift->setChecked((modifiers & Qt::ShiftModifier) != 0U);
1944         _advancedUi->urlHintsModifierCtrl->setChecked((modifiers & Qt::ControlModifier) != 0U);
1945         _advancedUi->urlHintsModifierAlt->setChecked((modifiers & Qt::AltModifier) != 0U);
1946         _advancedUi->urlHintsModifierMeta->setChecked((modifiers & Qt::MetaModifier) != 0U);
1947         connect(_advancedUi->urlHintsModifierShift, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier);
1948         connect(_advancedUi->urlHintsModifierCtrl, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier);
1949         connect(_advancedUi->urlHintsModifierAlt, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier);
1950         connect(_advancedUi->urlHintsModifierMeta, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier);
1951     }
1952 
1953     // encoding options
1954     auto codecAction = new KCodecAction(this);
1955     codecAction->setCurrentCodec(profile->defaultEncoding());
1956     _advancedUi->selectEncodingButton->setMenu(codecAction->menu());
1957 
1958     connect(codecAction, &KCodecAction::codecNameTriggered, this, [this](const QByteArray &codecName) {
1959         setDefaultCodec(QTextCodec::codecForName(codecName));
1960     });
1961 
1962     connect(codecAction, &KCodecAction::defaultItemTriggered, this, [this] {
1963         setDefaultCodec(QTextCodec::codecForLocale());
1964     });
1965 
1966     _advancedUi->selectEncodingButton->setText(profile->defaultEncoding());
1967 
1968     _advancedUi->peekPrimaryWidget->setKeySequence(profile->peekPrimaryKeySequence());
1969     connect(_advancedUi->peekPrimaryWidget, &QKeySequenceEdit::editingFinished, this, &EditProfileDialog::peekPrimaryKeySequenceChanged);
1970 }
1971 
1972 int EditProfileDialog::maxSpinBoxWidth(const KPluralHandlingSpinBox *spinBox, const KLocalizedString &suffix)
1973 {
1974     static const int cursorWidth = 2;
1975 
1976     const QFontMetrics fm(spinBox->fontMetrics());
1977     const QString plural = suffix.subs(2).toString();
1978     const QString singular = suffix.subs(1).toString();
1979     const QString min = QString::number(spinBox->minimum());
1980     const QString max = QString::number(spinBox->maximum());
1981     const int pluralWidth = fm.boundingRect(plural).width();
1982     const int singularWidth = fm.boundingRect(singular).width();
1983     const int minWidth = fm.boundingRect(min).width();
1984     const int maxWidth = fm.boundingRect(max).width();
1985     const int width = qMax(pluralWidth, singularWidth) + qMax(minWidth, maxWidth) + cursorWidth;
1986 
1987     // Based on QAbstractSpinBox::initStyleOption() from Qt
1988     QStyleOptionSpinBox opt;
1989     opt.initFrom(spinBox);
1990     opt.activeSubControls = QStyle::SC_None;
1991     opt.buttonSymbols = spinBox->buttonSymbols();
1992     // Assume all spinboxes have buttons
1993     opt.subControls = QStyle::SC_SpinBoxFrame | QStyle::SC_SpinBoxEditField | QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown;
1994     opt.frame = spinBox->hasFrame();
1995 
1996     const QSize hint(width, spinBox->sizeHint().height());
1997     const QSize spinBoxSize = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, spinBox);
1998     return spinBoxSize.width();
1999 }
2000 
2001 void EditProfileDialog::setDefaultCodec(QTextCodec *codec)
2002 {
2003     QString name = QString::fromLocal8Bit(codec->name());
2004 
2005     updateTempProfileProperty(Profile::DefaultEncoding, name);
2006     _advancedUi->selectEncodingButton->setText(name);
2007 }
2008 
2009 void EditProfileDialog::wordCharactersChanged(const QString &text)
2010 {
2011     updateTempProfileProperty(Profile::WordCharacters, text);
2012 }
2013 
2014 void EditProfileDialog::togglebidiRendering(bool enable)
2015 {
2016     updateTempProfileProperty(Profile::BidiRenderingEnabled, enable);
2017     _appearanceUi->enableBidiTableDirOverrideButton->setEnabled(enable);
2018     _appearanceUi->bidiLineLTR->setEnabled(enable);
2019 }
2020 
2021 void EditProfileDialog::togglebidiTableDirOverride(bool enable)
2022 {
2023     updateTempProfileProperty(Profile::BidiTableDirOverride, enable);
2024 }
2025 
2026 void EditProfileDialog::togglebidiLineLTR(bool enable)
2027 {
2028     updateTempProfileProperty(Profile::BidiLineLTR, enable);
2029 }
2030 
2031 void EditProfileDialog::toggleUnderlineLinks(bool enable)
2032 {
2033     updateTempProfileProperty(Profile::UnderlineLinksEnabled, enable);
2034 
2035     bool enableClick = _mouseUi->underlineFilesButton->isChecked() || enable;
2036     _mouseUi->openLinksByDirectClickButton->setEnabled(enableClick);
2037 }
2038 
2039 void EditProfileDialog::toggleUnderlineFiles(bool enable)
2040 {
2041     updateTempProfileProperty(Profile::UnderlineFilesEnabled, enable);
2042 
2043     bool enableClick = _mouseUi->underlineLinksButton->isChecked() || enable;
2044     _mouseUi->openLinksByDirectClickButton->setEnabled(enableClick);
2045 }
2046 
2047 void EditProfileDialog::textEditorCmdEditLineChanged(const QString &text)
2048 {
2049     updateTempProfileProperty(Profile::TextEditorCmd, text);
2050 }
2051 
2052 void EditProfileDialog::toggleCtrlRequiredForDrag(bool enable)
2053 {
2054     updateTempProfileProperty(Profile::CtrlRequiredForDrag, enable);
2055 }
2056 
2057 void EditProfileDialog::toggleDropUrlsAsText(bool enable)
2058 {
2059     updateTempProfileProperty(Profile::DropUrlsAsText, enable);
2060 }
2061 
2062 void EditProfileDialog::toggleOpenLinksByDirectClick(bool enable)
2063 {
2064     updateTempProfileProperty(Profile::OpenLinksByDirectClickEnabled, enable);
2065 }
2066 
2067 void EditProfileDialog::toggleCopyTextAsHTML(bool enable)
2068 {
2069     updateTempProfileProperty(Profile::CopyTextAsHTML, enable);
2070 }
2071 
2072 void EditProfileDialog::toggleCopyTextToClipboard(bool enable)
2073 {
2074     updateTempProfileProperty(Profile::AutoCopySelectedText, enable);
2075 }
2076 
2077 void EditProfileDialog::toggleTrimLeadingSpacesInSelectedText(bool enable)
2078 {
2079     updateTempProfileProperty(Profile::TrimLeadingSpacesInSelectedText, enable);
2080 }
2081 
2082 void EditProfileDialog::toggleTrimTrailingSpacesInSelectedText(bool enable)
2083 {
2084     updateTempProfileProperty(Profile::TrimTrailingSpacesInSelectedText, enable);
2085 }
2086 
2087 void EditProfileDialog::pasteFromX11Selection()
2088 {
2089     updateTempProfileProperty(Profile::MiddleClickPasteMode, Enum::PasteFromX11Selection);
2090 }
2091 
2092 void EditProfileDialog::pasteFromClipboard()
2093 {
2094     updateTempProfileProperty(Profile::MiddleClickPasteMode, Enum::PasteFromClipboard);
2095 }
2096 
2097 void EditProfileDialog::TripleClickModeChanged(int newValue)
2098 {
2099     updateTempProfileProperty(Profile::TripleClickMode, newValue);
2100 }
2101 
2102 void EditProfileDialog::updateUrlHintsModifier(bool)
2103 {
2104     Qt::KeyboardModifiers modifiers;
2105     if (_advancedUi->urlHintsModifierShift->isChecked()) {
2106         modifiers |= Qt::ShiftModifier;
2107     }
2108     if (_advancedUi->urlHintsModifierCtrl->isChecked()) {
2109         modifiers |= Qt::ControlModifier;
2110     }
2111     if (_advancedUi->urlHintsModifierAlt->isChecked()) {
2112         modifiers |= Qt::AltModifier;
2113     }
2114     if (_advancedUi->urlHintsModifierMeta->isChecked()) {
2115         modifiers |= Qt::MetaModifier;
2116     }
2117     updateTempProfileProperty(Profile::UrlHintsModifiers, int(modifiers));
2118 }
2119 
2120 void EditProfileDialog::toggleReverseUrlHints(bool enable)
2121 {
2122     updateTempProfileProperty(Profile::ReverseUrlHints, enable);
2123 }
2124 
2125 void EditProfileDialog::toggleBlinkingText(bool enable)
2126 {
2127     updateTempProfileProperty(Profile::BlinkingTextEnabled, enable);
2128 }
2129 
2130 void EditProfileDialog::toggleFlowControl(bool enable)
2131 {
2132     updateTempProfileProperty(Profile::FlowControlEnabled, enable);
2133 }
2134 
2135 void EditProfileDialog::peekPrimaryKeySequenceChanged()
2136 {
2137     updateTempProfileProperty(Profile::PeekPrimaryKeySequence, _advancedUi->peekPrimaryWidget->keySequence().toString());
2138 }
2139 
2140 void EditProfileDialog::toggleWordMode(bool mode)
2141 {
2142     updateTempProfileProperty(Profile::WordMode, mode);
2143     _appearanceUi->wordModeAttr->setEnabled(mode);
2144     _appearanceUi->wordModeAscii->setEnabled(mode);
2145     _appearanceUi->wordModeBrahmic->setEnabled(mode);
2146 }
2147 
2148 void EditProfileDialog::toggleWordModeAttr(bool mode)
2149 {
2150     updateTempProfileProperty(Profile::WordModeAttr, mode);
2151 }
2152 
2153 void EditProfileDialog::toggleWordModeAscii(bool mode)
2154 {
2155     updateTempProfileProperty(Profile::WordModeAscii, mode);
2156 }
2157 
2158 void EditProfileDialog::toggleWordModeBrahmic(bool mode)
2159 {
2160     updateTempProfileProperty(Profile::WordModeBrahmic, mode);
2161 }
2162 
2163 void EditProfileDialog::toggleIgnoreWcWidth(bool ignore)
2164 {
2165     updateTempProfileProperty(Profile::IgnoreWcWidth, ignore);
2166 }
2167 
2168 #include "moc_EditProfileDialog.cpp"