/* SPDX-FileCopyrightText: 2007-2008 Robert Knight SPDX-FileCopyrightText: 2018 Harald Sitter SPDX-License-Identifier: GPL-2.0-or-later */ // Own #include "EditProfileDialog.h" // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE #include #include #include #include #include #include // Konsole #include "ui_EditProfileAdvancedPage.h" #include "ui_EditProfileAppearancePage.h" #include "ui_EditProfileGeneralPage.h" #include "ui_EditProfileKeyboardPage.h" #include "ui_EditProfileMousePage.h" #include "ui_EditProfileScrollingPage.h" #include "ui_EditProfileTabsPage.h" #include "colorscheme/ColorSchemeManager.h" #include "keyboardtranslator/KeyboardTranslator.h" #include "KeyBindingEditor.h" #include "ShellCommand.h" #include "WindowSystemInfo.h" #include "profile/ProfileManager.h" // Others #if defined(Q_OS_LINUX) | defined(Q_OS_FREEBSD) | defined(Q_OS_OPENBSD) | defined(Q_OS_SOLARIS) | defined(Q_OS_MACOS) #include #endif using namespace Konsole; EditProfileDialog::EditProfileDialog(QWidget *parent) : KPageDialog(parent) , _tempProfile(new Profile{}) { _tempProfile->setHidden(true); setWindowTitle(i18n("Edit Profile")); setFaceType(KPageDialog::List); _buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply); setButtonBox(_buttonBox); _buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); auto *applyButton = _buttonBox->button(QDialogButtonBox::Apply); // Disable it, since no modifications have been made yet applyButton->setEnabled(false); connect(applyButton, &QPushButton::clicked, this, [this]() { if (isProfileNameValid()) { save(); } }); // Set a fallback icon for non-plasma desktops as this dialog looks // terrible without all the icons on the left sidebar. On GTK related // desktops, this dialog look good enough without installing // oxygen-icon-theme, qt5ct and setting export QT_QPA_PLATFORMTHEME=qt5ct // Plain Xorg desktops still look terrible as there are no icons // visible. const auto defaultIcon = QIcon::fromTheme(QStringLiteral("utilities-terminal")); // General page const QString generalPageName = i18nc("@title:tab Generic, common options", "General"); auto *generalPageWidget = new QWidget(this); _generalUi = new Ui::EditProfileGeneralPage(); _generalUi->setupUi(generalPageWidget); _generalPageItem = addPage(generalPageWidget, generalPageName); _generalPageItem->setHeader(generalPageName); _generalPageItem->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); _pages[_generalPageItem] = Page(&EditProfileDialog::setupGeneralPage); // Tabs page const QString tabsPageName = i18n("Tabs"); auto *tabsPageWidget = new QWidget(this); _tabsUi = new Ui::EditProfileTabsPage(); _tabsUi->setupUi(tabsPageWidget); auto *tabsPageItem = addPage(tabsPageWidget, tabsPageName); tabsPageItem->setHeader(tabsPageName); tabsPageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-tabs"), defaultIcon)); _pages[tabsPageItem] = Page(&EditProfileDialog::setupTabsPage); LabelsAligner tabsAligner(tabsPageWidget); tabsAligner.addLayout(dynamic_cast(_tabsUi->tabMonitoringGroup->layout())); tabsAligner.addLayout(dynamic_cast(_tabsUi->renameTabWidget->layout())); tabsAligner.updateLayouts(); tabsAligner.align(); // Appearance page const QString appearancePageName = i18n("Appearance"); auto *appearancePageWidget = new QWidget(this); _appearanceUi = new Ui::EditProfileAppearancePage(); _appearanceUi->setupUi(appearancePageWidget); auto *appearancePageItem = addPage(appearancePageWidget, appearancePageName); appearancePageItem->setHeader(appearancePageName); appearancePageItem->setIcon(QIcon::fromTheme(QStringLiteral("kcolorchooser"), defaultIcon)); _pages[appearancePageItem] = Page(&EditProfileDialog::setupAppearancePage); LabelsAligner appearanceAligner(appearancePageWidget); appearanceAligner.addLayout(dynamic_cast(_appearanceUi->contentsGroup->layout())); appearanceAligner.updateLayouts(); appearanceAligner.align(); // Scrolling page const QString scrollingPageName = i18n("Scrolling"); auto *scrollingPageWidget = new QWidget(this); _scrollingUi = new Ui::EditProfileScrollingPage(); _scrollingUi->setupUi(scrollingPageWidget); auto *scrollingPageItem = addPage(scrollingPageWidget, scrollingPageName); scrollingPageItem->setHeader(scrollingPageName); scrollingPageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-scroll"), defaultIcon)); _pages[scrollingPageItem] = Page(&EditProfileDialog::setupScrollingPage); // adjust "history size" label height to match history size widget's first radio button _scrollingUi->historySizeLabel->setFixedHeight(_scrollingUi->historySizeWidget->preferredLabelHeight()); // Keyboard page const QString keyboardPageName = i18n("Keyboard"); const QString keyboardPageTitle = i18n("Key bindings"); auto *keyboardPageWidget = new QWidget(this); _keyboardUi = new Ui::EditProfileKeyboardPage(); _keyboardUi->setupUi(keyboardPageWidget); auto *keyboardPageItem = addPage(keyboardPageWidget, keyboardPageName); keyboardPageItem->setHeader(keyboardPageTitle); keyboardPageItem->setIcon(QIcon::fromTheme(QStringLiteral("input-keyboard"), defaultIcon)); _pages[keyboardPageItem] = Page(&EditProfileDialog::setupKeyboardPage); // Mouse page const QString mousePageName = i18n("Mouse"); auto *mousePageWidget = new QWidget(this); _mouseUi = new Ui::EditProfileMousePage(); _mouseUi->setupUi(mousePageWidget); const auto regExp = QRegularExpression(QStringLiteral(R"(([a-z]*:\/\/;)*([A-Za-z*]:\/\/))")); auto validator = new QRegularExpressionValidator(regExp, this); _mouseUi->linkEscapeSequenceTexts->setValidator(validator); auto *mousePageItem = addPage(mousePageWidget, mousePageName); mousePageItem->setHeader(mousePageName); mousePageItem->setIcon(QIcon::fromTheme(QStringLiteral("input-mouse"), defaultIcon)); _pages[mousePageItem] = Page(&EditProfileDialog::setupMousePage); // Advanced page const QString advancedPageName = i18nc("@title:tab Complex options", "Advanced"); auto *advancedPageWidget = new QWidget(this); _advancedUi = new Ui::EditProfileAdvancedPage(); _advancedUi->setupUi(advancedPageWidget); auto *advancedPageItem = addPage(advancedPageWidget, advancedPageName); advancedPageItem->setHeader(advancedPageName); advancedPageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other"), defaultIcon)); _pages[advancedPageItem] = Page(&EditProfileDialog::setupAdvancedPage); // there are various setupXYZPage() methods to load the items // for each page and update their states to match the profile // being edited. // // these are only called when needed ( ie. when the user clicks // the tab to move to that page ). // // the _pageNeedsUpdate vector keeps track of the pages that have // not been updated since the last profile change and will need // to be refreshed when the user switches to them connect(this, &KPageDialog::currentPageChanged, this, &Konsole::EditProfileDialog::preparePage); } EditProfileDialog::~EditProfileDialog() { delete _generalUi; delete _tabsUi; delete _appearanceUi; delete _scrollingUi; delete _keyboardUi; delete _mouseUi; delete _advancedUi; } void EditProfileDialog::save() { const bool isNewProfile = _profileState == EditProfileDialog::NewProfile; if (isNewProfile) { ProfileManager::instance()->addProfile(_profile); // The profile exists now, no longer treat it as initial creation _profileState = EditProfileDialog::ExistingProfile; } bool defaultChanged = _isDefault != _generalUi->setAsDefaultButton->isChecked(); if (_tempProfile->isEmpty() && !defaultChanged) { if (isNewProfile) { // New profile, we need to save it to disk, even if no settings // were changed and _tempProfile is empty ProfileManager::instance()->changeProfile(_profile, _profile->properties()); } // no changes since last save return; } ProfileManager::instance()->changeProfile(_profile, _tempProfile->properties()); // ensure that these settings are not undone by a call // to unpreview() _previewedProperties.clear(); // Update the default profile if needed if (defaultChanged) { Q_ASSERT(_profile != ProfileManager::instance()->builtinProfile()); bool defaultChecked = _generalUi->setAsDefaultButton->isChecked(); Profile::Ptr newDefault = defaultChecked ? _profile : ProfileManager::instance()->builtinProfile(); ProfileManager::instance()->setDefaultProfile(newDefault); _isDefault = defaultChecked; } resetTempProfile(); _buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); } void EditProfileDialog::reject() { unpreviewAll(); QDialog::reject(); } void EditProfileDialog::accept() { if (isProfileNameValid()) { save(); unpreviewAll(); QDialog::accept(); } } void EditProfileDialog::setMessageGeneralPage(const QString &msg) { _generalUi->generalPageMessageWidget->setText(msg); _generalUi->generalPageMessageWidget->setMessageType(KMessageWidget::Error); setCurrentPage(_generalPageItem); _generalUi->generalPageMessageWidget->animatedShow(); } bool EditProfileDialog::isProfileNameValid() { Q_ASSERT(_profile); Q_ASSERT(_tempProfile); // check whether the user has enough permissions to save the profile QFileInfo fileInfo(_profile->path()); if (fileInfo.exists() && !fileInfo.isWritable() && (!_tempProfile->isPropertySet(Profile::Name) || _tempProfile->name() == _profile->name())) { setMessageGeneralPage(xi18nc("@info", "Insufficient permissions to save settings to: %1." "Either change the permissions of that file or set a different name to save " "the settings to a new profile.", _profile->path())); return false; } const QList existingProfiles = ProfileManager::instance()->allProfiles(); QStringList otherExistingProfileNames; for (const Profile::Ptr &existingProfile : existingProfiles) { if (existingProfile->name() != _profile->name()) { otherExistingProfileNames.append(existingProfile->name()); } } if ((_tempProfile->isPropertySet(Profile::Name) && _tempProfile->name().isEmpty()) || (_profile->name().isEmpty() && _tempProfile->name().isEmpty())) { setMessageGeneralPage(i18nc("@info", "Profile Name was empty; please set a name to be able to save settings.")); // Revert the name in the dialog _generalUi->profileNameEdit->setText(_profile->name()); selectProfileName(); return false; } if (!_tempProfile->name().isEmpty() && otherExistingProfileNames.contains(_tempProfile->name())) { setMessageGeneralPage(i18nc("@info", "A profile with the name \"%1\" already exists.", _generalUi->profileNameEdit->text())); // Revert the name in the dialog _generalUi->profileNameEdit->setText(_profile->name()); selectProfileName(); return false; } const QString profileStoragePath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + QStringLiteral("konsole")); int nameLenMax = 256; #if defined(Q_OS_LINUX) | defined(Q_OS_OPENBSD) | defined(Q_OS_FREEBSD) | defined(Q_OS_MACOS) | defined(Q_OS_SOLARIS) const int maxPath = pathconf(profileStoragePath.toLocal8Bit().constData(), _PC_PATH_MAX); const int nameLenMaxPath = maxPath - _profile->path().lastIndexOf(QLatin1Char('/')) + 1; const int nameLenMaxName = pathconf(profileStoragePath.toLocal8Bit().constData(), _PC_NAME_MAX); nameLenMax = qMin(nameLenMaxPath, nameLenMaxName); #elif defined(Q_OS_WIN) // If MAX_PATH not found, use default (260 - 1) const int maxPath = qMax(259, qEnvironmentVariable("MAX_PATH").toInt()); nameLenMax = maxPath - _profile->path().lastIndexOf(QLatin1Char('/')) + 1; #endif nameLenMax -= QStringLiteral(".profile").size(); // doesn't work on macOS and maybe other systems so make sure we have a sane value always // See https://mail.kde.org/pipermail/kwrite-devel/2024-April/007139.html if (nameLenMax < 0) { nameLenMax = 100; } if (_tempProfile->name().size() > nameLenMax) { setMessageGeneralPage(i18nc("@info", "Profile name exceeded maximum allowed length (%1).", nameLenMax)); // Revert the name in the dialog _generalUi->profileNameEdit->setText(_profile->name()); selectProfileName(); return false; } if (!_tempProfile->name().isEmpty()) { static const QString illegalCharacters(QStringLiteral("#%&{}/\\\"<>*?$!':@`+=|")); static const QString colorTagOpen(QStringLiteral("")); static const QString colorTagClose(QStringLiteral("")); const QString name(_tempProfile->name()); bool hasIllegal = false; bool unclosedTag = false; QString highlightedName; for (int i = 0; i < name.size(); ++i) { const auto currChar = name.at(i); if (illegalCharacters.contains(currChar)) { if (!unclosedTag) { highlightedName += colorTagOpen; } hasIllegal = true; unclosedTag = true; } else if (unclosedTag) { unclosedTag = false; highlightedName += colorTagClose; } highlightedName += currChar; if (i + 1 == name.size() && unclosedTag) { highlightedName += colorTagClose; } } if (hasIllegal) { setMessageGeneralPage(i18nc("@info", "The highlighted characters are invalid in profile names: %1.", highlightedName)); // Revert the name in the dialog _generalUi->profileNameEdit->setText(_profile->name()); selectProfileName(); return false; } } // Valid name return true; } QString EditProfileDialog::groupProfileNames(const ProfileGroup::Ptr &group, int maxLength) { QString caption; int count = group->profiles().count(); for (int i = 0; i < count; i++) { caption += group->profiles()[i]->name(); if (i < (count - 1)) { caption += QLatin1Char(','); // limit caption length to prevent very long window titles if (maxLength > 0 && caption.length() > maxLength) { caption += QLatin1String("..."); break; } } } return caption; } void EditProfileDialog::updateCaption(const Profile::Ptr &profile) { const int MAX_GROUP_CAPTION_LENGTH = 25; ProfileGroup::Ptr group = profile->asGroup(); if (group && group->profiles().count() > 1) { QString caption = groupProfileNames(group, MAX_GROUP_CAPTION_LENGTH); setWindowTitle(i18np("Editing profile: %2", "Editing %1 profiles: %2", group->profiles().count(), caption)); updateButtonApply(); } else { if (_profileState == EditProfileDialog::NewProfile) { setWindowTitle(i18n("Create New Profile")); _buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); } else { setWindowTitle(i18n("Edit Profile \"%1\"", profile->name())); updateButtonApply(); } } } void EditProfileDialog::setProfile(const Konsole::Profile::Ptr &profile, EditProfileDialog::InitialProfileState state) { Q_ASSERT(profile); _profile = profile; _profileState = state; // update caption updateCaption(profile); // mark each page of the dialog as out of date // and force an update of the currently visible page // // the other pages will be updated as necessary for (Page &page : _pages) { page.needsUpdate = true; } preparePage(currentPage()); resetTempProfile(); } const QString EditProfileDialog::currentColorSchemeName() const { const QString ¤tColorSchemeName = _profile->colorScheme(); return currentColorSchemeName; } void EditProfileDialog::preparePage(KPageWidgetItem *current, KPageWidgetItem *before) { Q_UNUSED(before) Q_ASSERT(current); Q_ASSERT(_pages.contains(current)); const Profile::Ptr profile = _profile; auto setupPage = _pages[current].setupPage; Q_ASSERT(profile); Q_ASSERT(setupPage); if (_pages[current].needsUpdate) { (*this.*setupPage)(profile); _pages[current].needsUpdate = false; } } void Konsole::EditProfileDialog::selectProfileName() { _generalUi->profileNameEdit->setFocus(); _generalUi->profileNameEdit->selectAll(); } void EditProfileDialog::setupGeneralPage(const Profile::Ptr &profile) { _generalUi->generalPageMessageWidget->setVisible(false); _generalUi->generalPageMessageWidget->setWordWrap(true); _generalUi->generalPageMessageWidget->setCloseButtonVisible(true); // basic profile options { ProfileGroup::Ptr group = profile->asGroup(); if (!group || group->profiles().count() < 2) { _generalUi->profileNameEdit->setText(profile->name()); _generalUi->profileNameEdit->setClearButtonEnabled(true); } else { _generalUi->profileNameEdit->setText(groupProfileNames(group, -1)); _generalUi->profileNameEdit->setEnabled(false); } } ShellCommand command(profile->command(), profile->arguments()); _generalUi->commandEdit->setText(command.fullCommand()); // If a "completion" is requested, consider changing this to KLineEdit // and using KCompletion. _generalUi->initialDirEdit->setText(profile->defaultWorkingDirectory()); _generalUi->initialDirEdit->setClearButtonEnabled(true); _generalUi->initialDirEdit->setPlaceholderText(QStandardPaths::standardLocations(QStandardPaths::HomeLocation).value(0)); _generalUi->dirSelectButton->setIcon(QIcon::fromTheme(QStringLiteral("folder-open"))); _generalUi->iconSelectButton->setIcon(QIcon::fromTheme(profile->icon())); _generalUi->environmentEditButton->setIcon(QIcon::fromTheme(QStringLiteral("document-edit"))); _generalUi->startInSameDirButton->setChecked(profile->startInCurrentSessionDir()); // initial terminal size const auto colsSuffix = ki18ncp("Suffix of the number of columns (N columns). The leading space is needed to separate it from the number value.", " column", " columns"); const auto rowsSuffix = ki18ncp("Suffix of the number of rows (N rows). The leading space is needed to separate it from the number value.", " row", " rows"); _generalUi->terminalColumnsEntry->setValue(profile->terminalColumns()); _generalUi->terminalRowsEntry->setValue(profile->terminalRows()); _generalUi->terminalColumnsEntry->setSuffix(colsSuffix); _generalUi->terminalRowsEntry->setSuffix(rowsSuffix); // make width of initial terminal size spinboxes equal const int sizeEntryWidth = qMax(maxSpinBoxWidth(_generalUi->terminalColumnsEntry, colsSuffix), maxSpinBoxWidth(_generalUi->terminalRowsEntry, rowsSuffix)); _generalUi->terminalColumnsEntry->setFixedWidth(sizeEntryWidth); _generalUi->terminalRowsEntry->setFixedWidth(sizeEntryWidth); auto *bellModeModel = new QStringListModel({i18n("System Bell"), i18n("System Notifications"), i18n("Visual Bell"), i18n("Ignore Bell Events")}, this); _generalUi->terminalBellCombo->setModel(bellModeModel); _generalUi->terminalBellCombo->setCurrentIndex(profile->property(Profile::BellMode)); _isDefault = profile == ProfileManager::instance()->defaultProfile(); _generalUi->setAsDefaultButton->setChecked(_isDefault); QString appName = QCoreApplication::applicationName(); if (!appName.isEmpty() && appName != QLatin1String("konsole")) { appName[0] = appName.at(0).toUpper(); _generalUi->setAsDefaultButton->setText(i18n("Default profile for new terminal sessions in %1", appName)); } else { _generalUi->setAsDefaultButton->setText(i18n("Default profile")); } _generalUi->semanticUpDown->setChecked(profile->semanticUpDown()); _generalUi->semanticInputClick->setChecked(profile->semanticInputClick()); // signals and slots connect(_generalUi->dirSelectButton, &QToolButton::clicked, this, &Konsole::EditProfileDialog::selectInitialDir); connect(_generalUi->iconSelectButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::selectIcon); connect(_generalUi->startInSameDirButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::startInSameDir); connect(_generalUi->profileNameEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::profileNameChanged); connect(_generalUi->initialDirEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::initialDirChanged); connect(_generalUi->commandEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::commandChanged); connect(_generalUi->environmentEditButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::showEnvironmentEditor); connect(_generalUi->semanticUpDown, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::semanticUpDown); connect(_generalUi->semanticInputClick, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::semanticInputClick); connect(_generalUi->terminalColumnsEntry, &QSpinBox::valueChanged, this, &Konsole::EditProfileDialog::terminalColumnsEntryChanged); connect(_generalUi->terminalRowsEntry, &QSpinBox::valueChanged, this, &Konsole::EditProfileDialog::terminalRowsEntryChanged); connect(_generalUi->terminalBellCombo, &QComboBox::currentIndexChanged, this, [this](const int index) { updateTempProfileProperty(Profile::BellMode, index); }); connect(_generalUi->setAsDefaultButton, &QAbstractButton::toggled, this, &Konsole::EditProfileDialog::updateButtonApply); const ButtonGroupOptions semanticHints = { _generalUi->semanticHints, // group Profile::SemanticHints, // profileProperty false, // preview { // buttons {_generalUi->semanticHintsNever, Enum::HintsNever}, {_generalUi->semanticHintsURL, Enum::HintsURL}, {_generalUi->semanticHintsAlways, Enum::HintsAlways}, }, }; setupButtonGroup(semanticHints, profile); const ButtonGroupOptions errorBars = { _generalUi->errorBars, // group Profile::ErrorBars, // profileProperty false, // preview { // buttons {_generalUi->errorBarsNever, Enum::HintsNever}, {_generalUi->errorBarsURL, Enum::HintsURL}, {_generalUi->errorBarsAlways, Enum::HintsAlways}, }, }; setupButtonGroup(errorBars, profile); const ButtonGroupOptions errorBackground = { _generalUi->errorBackground, // group Profile::ErrorBackground, // profileProperty false, // preview { // buttons {_generalUi->errorBackgroundNever, Enum::HintsNever}, {_generalUi->errorBackgroundURL, Enum::HintsURL}, {_generalUi->errorBackgroundAlways, Enum::HintsAlways}, }, }; setupButtonGroup(errorBackground, profile); const ButtonGroupOptions alternatingBars = { _generalUi->alternatingBars, // group Profile::AlternatingBars, // profileProperty false, // preview { // buttons {_generalUi->alternatingBarsNever, Enum::HintsNever}, {_generalUi->alternatingBarsURL, Enum::HintsURL}, {_generalUi->alternatingBarsAlways, Enum::HintsAlways}, }, }; setupButtonGroup(alternatingBars, profile); const ButtonGroupOptions alternatingBackground = { _generalUi->alternatingBackground, // group Profile::AlternatingBackground, // profileProperty false, // preview { // buttons {_generalUi->alternatingBackgroundNever, Enum::HintsNever}, {_generalUi->alternatingBackgroundURL, Enum::HintsURL}, {_generalUi->alternatingBackgroundAlways, Enum::HintsAlways}, }, }; setupButtonGroup(alternatingBackground, profile); } void EditProfileDialog::showEnvironmentEditor() { bool ok; const Profile::Ptr profile = _profile; QStringList currentEnvironment; // The user could re-open the environment editor before clicking // OK/Apply in the parent edit profile dialog, so we make sure // to show the new environment vars if (_tempProfile->isPropertySet(Profile::Environment)) { currentEnvironment = _tempProfile->environment(); } else { currentEnvironment = profile->environment(); } QString text = QInputDialog::getMultiLineText(this, i18n("Edit Environment"), i18n("One environment variable per line"), currentEnvironment.join(QStringLiteral("\n")), &ok); QStringList newEnvironment; if (ok) { if (!text.isEmpty()) { newEnvironment = text.split(QLatin1Char('\n')); updateTempProfileProperty(Profile::Environment, newEnvironment); } else { // the user could have removed all entries so we return an empty list updateTempProfileProperty(Profile::Environment, newEnvironment); } } } void EditProfileDialog::setupTabsPage(const Profile::Ptr &profile) { // tab title format _tabsUi->renameTabWidget->setTabTitleText(profile->localTabTitleFormat()); _tabsUi->renameTabWidget->setRemoteTabTitleText(profile->remoteTabTitleFormat()); _tabsUi->renameTabWidget->setColor(profile->tabColor()); connect(_tabsUi->renameTabWidget, &Konsole::RenameTabWidget::tabTitleFormatChanged, this, &Konsole::EditProfileDialog::tabTitleFormatChanged); connect(_tabsUi->renameTabWidget, &Konsole::RenameTabWidget::remoteTabTitleFormatChanged, this, &Konsole::EditProfileDialog::remoteTabTitleFormatChanged); connect(_tabsUi->renameTabWidget, &Konsole::RenameTabWidget::tabColorChanged, this, &Konsole::EditProfileDialog::tabColorChanged); // tab monitoring const int silenceSeconds = profile->silenceSeconds(); _tabsUi->silenceSecondsSpinner->setValue(silenceSeconds); auto suffix = ki18ncp("Unit of time", " second", " seconds"); _tabsUi->silenceSecondsSpinner->setSuffix(suffix); int silenceCheckBoxWidth = maxSpinBoxWidth(_generalUi->terminalColumnsEntry, suffix); _tabsUi->silenceSecondsSpinner->setFixedWidth(silenceCheckBoxWidth); connect(_tabsUi->silenceSecondsSpinner, &QSpinBox::valueChanged, this, &Konsole::EditProfileDialog::silenceSecondsChanged); } void EditProfileDialog::terminalColumnsEntryChanged(int value) { updateTempProfileProperty(Profile::TerminalColumns, value); } void EditProfileDialog::terminalRowsEntryChanged(int value) { updateTempProfileProperty(Profile::TerminalRows, value); } void EditProfileDialog::showTerminalSizeHint(bool value) { updateTempProfileProperty(Profile::ShowTerminalSizeHint, value); } void EditProfileDialog::setDimWhenInactive(bool value) { updateTempProfileProperty(Profile::DimWhenInactive, value); } void EditProfileDialog::setDimValue(int value) { updateTempProfileProperty(Profile::DimValue, value); } void EditProfileDialog::setBorderWhenActive(bool value) { updateTempProfileProperty(Profile::BorderWhenActive, value); } void EditProfileDialog::setFocusBorderColor(const QColor &color) { updateTempProfileProperty(Profile::FocusBorderColor, color); } void EditProfileDialog::tabTitleFormatChanged(const QString &format) { updateTempProfileProperty(Profile::LocalTabTitleFormat, format); } void EditProfileDialog::remoteTabTitleFormatChanged(const QString &format) { updateTempProfileProperty(Profile::RemoteTabTitleFormat, format); } void EditProfileDialog::tabColorChanged(const QColor &color) { updateTempProfileProperty(Profile::TabColor, color); } void EditProfileDialog::focusBorderColorChanged(const QColor &color) { updateTempProfileProperty(Profile::FocusBorderColor, color); } void EditProfileDialog::silenceSecondsChanged(int seconds) { updateTempProfileProperty(Profile::SilenceSeconds, seconds); } void EditProfileDialog::selectIcon() { const QString &icon = KIconDialog::getIcon(KIconLoader::Desktop, KIconLoader::Application, false, 0, false, this); if (!icon.isEmpty()) { _generalUi->iconSelectButton->setIcon(QIcon::fromTheme(icon)); updateTempProfileProperty(Profile::Icon, icon); } } void EditProfileDialog::profileNameChanged(const QString &name) { updateTempProfileProperty(Profile::Name, name); updateTempProfileProperty(Profile::UntranslatedName, name); updateCaption(_tempProfile); } void EditProfileDialog::startInSameDir(bool sameDir) { updateTempProfileProperty(Profile::StartInCurrentSessionDir, sameDir); } void EditProfileDialog::semanticUpDown(bool enable) { updateTempProfileProperty(Profile::SemanticUpDown, enable); } void EditProfileDialog::semanticInputClick(bool enable) { updateTempProfileProperty(Profile::SemanticInputClick, enable); } void EditProfileDialog::initialDirChanged(const QString &dir) { updateTempProfileProperty(Profile::Directory, dir); } void EditProfileDialog::commandChanged(const QString &command) { ShellCommand shellCommand(command); updateTempProfileProperty(Profile::Command, shellCommand.command()); updateTempProfileProperty(Profile::Arguments, shellCommand.arguments()); } void EditProfileDialog::selectInitialDir() { const QUrl url = QFileDialog::getExistingDirectoryUrl(this, i18n("Select Initial Directory"), QUrl::fromUserInput(_generalUi->initialDirEdit->text())); if (!url.isEmpty()) { _generalUi->initialDirEdit->setText(url.path()); } } void EditProfileDialog::setupAppearancePage(const Profile::Ptr &profile) { auto compositingActiveHelper = [] { return WindowSystemInfo::compositingActive(); }; auto delegate = new ColorSchemeViewDelegate(compositingActiveHelper, this); _appearanceUi->colorSchemeList->setItemDelegate(delegate); _appearanceUi->transparencyWarningWidget->setVisible(false); _appearanceUi->transparencyWarningWidget->setWordWrap(true); _appearanceUi->transparencyWarningWidget->setCloseButtonVisible(false); _appearanceUi->transparencyWarningWidget->setMessageType(KMessageWidget::Warning); _appearanceUi->colorSchemeMessageWidget->setVisible(false); _appearanceUi->colorSchemeMessageWidget->setWordWrap(true); _appearanceUi->colorSchemeMessageWidget->setCloseButtonVisible(false); _appearanceUi->colorSchemeMessageWidget->setMessageType(KMessageWidget::Warning); _appearanceUi->editColorSchemeButton->setIcon(QIcon::fromTheme(QStringLiteral("document-edit"))); _appearanceUi->removeColorSchemeButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); _appearanceUi->newColorSchemeButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); _appearanceUi->chooseFontButton->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font"))); _appearanceUi->resetColorSchemeButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo"))); _appearanceUi->editColorSchemeButton->setEnabled(false); _appearanceUi->removeColorSchemeButton->setEnabled(false); _appearanceUi->resetColorSchemeButton->setEnabled(false); // setup color list // select the colorScheme used in the current profile updateColorSchemeList(currentColorSchemeName()); _appearanceUi->colorSchemeList->setMouseTracking(true); _appearanceUi->colorSchemeList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); connect(_appearanceUi->colorSchemeList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Konsole::EditProfileDialog::colorSchemeSelected); connect(_appearanceUi->colorSchemeList, &QListView::activated, this, &Konsole::EditProfileDialog::previewColorScheme); updateColorSchemeButtons(); connect(_appearanceUi->editColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::editColorScheme); connect(_appearanceUi->removeColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::removeColorScheme); connect(_appearanceUi->newColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::newColorScheme); connect(_appearanceUi->resetColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::resetColorScheme); connect(_appearanceUi->chooseFontButton, &QAbstractButton::clicked, this, &EditProfileDialog::showFontDialog); connect(_appearanceUi->emojiFontPreview, &QAbstractButton::clicked, this, &EditProfileDialog::showEmojiFontDialog); // setup font preview const bool antialias = profile->antiAliasFonts(); QFont profileFont = profile->font(); profileFont.setStyleStrategy(antialias ? QFont::PreferAntialias : QFont::NoAntialias); _appearanceUi->fontPreview->setFont(profileFont); _appearanceUi->fontPreview->setText(QStringLiteral("%1 %2pt").arg(profileFont.family()).arg(profileFont.pointSize())); QFont emojiFont = profile->emojiFont(); _appearanceUi->emojiFontPreview->setFont(profileFont); _appearanceUi->emojiFontPreview->setText(QStringLiteral("%1 %2pt").arg(emojiFont.family()).arg(emojiFont.pointSize())); // setup font smoothing _appearanceUi->antialiasTextButton->setChecked(antialias); connect(_appearanceUi->antialiasTextButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setAntialiasText); _appearanceUi->boldIntenseButton->setChecked(profile->boldIntense()); connect(_appearanceUi->boldIntenseButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setBoldIntense); _appearanceUi->useFontLineCharactersButton->setChecked(profile->useFontLineCharacters()); connect(_appearanceUi->useFontLineCharactersButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::useFontLineCharacters); _appearanceUi->useFontBrailleCharactersButton->setEnabled(!profile->property(Profile::UseFontLineCharacters)); _appearanceUi->useFontBrailleCharactersButton->setChecked(profile->property(Profile::UseFontBrailleCharacters)); connect(_appearanceUi->useFontBrailleCharactersButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::useFontBrailleCharacters); _mouseUi->enableMouseWheelZoomButton->setChecked(profile->mouseWheelZoomEnabled()); connect(_mouseUi->enableMouseWheelZoomButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::toggleMouseWheelZoom); // cursor options _appearanceUi->enableBlinkingCursorButton->setChecked(profile->property(Profile::BlinkingCursorEnabled)); connect(_appearanceUi->enableBlinkingCursorButton, &QToolButton::toggled, this, &EditProfileDialog::toggleBlinkingCursor); _appearanceUi->enableAnimatingCursorButton->setChecked(profile->property(Profile::AnimatingCursorEnabled)); connect(_appearanceUi->enableAnimatingCursorButton, &QToolButton::toggled, this, &EditProfileDialog::toggleAnimatingCursor); if (profile->useCustomCursorColor()) { _appearanceUi->customCursorColorButton->setChecked(true); } else { _appearanceUi->autoCursorColorButton->setChecked(true); } _appearanceUi->customColorSelectButton->setColor(profile->customCursorColor()); _appearanceUi->customTextColorSelectButton->setColor(profile->customCursorTextColor()); connect(_appearanceUi->customCursorColorButton, &QRadioButton::clicked, this, &Konsole::EditProfileDialog::customCursorColor); connect(_appearanceUi->autoCursorColorButton, &QRadioButton::clicked, this, &Konsole::EditProfileDialog::autoCursorColor); connect(_appearanceUi->customColorSelectButton, &KColorButton::changed, this, &Konsole::EditProfileDialog::customCursorColorChanged); connect(_appearanceUi->customTextColorSelectButton, &KColorButton::changed, this, &Konsole::EditProfileDialog::customCursorTextColorChanged); const ButtonGroupOptions cursorShapeOptions = { _appearanceUi->cursorShape, // group Profile::CursorShape, // profileProperty true, // preview { // buttons {_appearanceUi->cursorShapeBlock, Enum::BlockCursor}, {_appearanceUi->cursorShapeIBeam, Enum::IBeamCursor}, {_appearanceUi->cursorShapeUnderline, Enum::UnderlineCursor}, }, }; setupButtonGroup(cursorShapeOptions, profile); _appearanceUi->marginsSpinner->setValue(profile->terminalMargin()); connect(_appearanceUi->marginsSpinner, &QSpinBox::valueChanged, this, &Konsole::EditProfileDialog::terminalMarginChanged); _appearanceUi->lineSpacingSpinner->setValue(profile->lineSpacing()); connect(_appearanceUi->lineSpacingSpinner, &QSpinBox::valueChanged, this, &Konsole::EditProfileDialog::lineSpacingChanged); _appearanceUi->alignToCenterButton->setChecked(profile->terminalCenter()); connect(_appearanceUi->alignToCenterButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setTerminalCenter); _appearanceUi->showTerminalSizeHintButton->setChecked(profile->showTerminalSizeHint()); connect(_appearanceUi->showTerminalSizeHintButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::showTerminalSizeHint); _appearanceUi->dimWhenInactiveCheckbox->setChecked(profile->dimWhenInactive()); connect(_appearanceUi->dimWhenInactiveCheckbox, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setDimWhenInactive); _appearanceUi->dimValue->setValue(profile->dimValue()); _appearanceUi->dimValue->setEnabled(profile->dimWhenInactive()); _appearanceUi->dimLabel->setEnabled(profile->dimWhenInactive()); connect(_appearanceUi->dimValue, &QSlider::valueChanged, this, &Konsole::EditProfileDialog::setDimValue); _appearanceUi->borderWhenActiveCheckbox->setChecked(profile->borderWhenActive()); connect(_appearanceUi->borderWhenActiveCheckbox, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setBorderWhenActive); _appearanceUi->focusBorderColor->setColor(profile->focusBorderColor()); _appearanceUi->focusBorderColor->setEnabled(profile->borderWhenActive()); _appearanceUi->borderLabel->setEnabled(profile->borderWhenActive()); connect(_appearanceUi->focusBorderColor, &KColorButton::changed, this, &Konsole::EditProfileDialog::focusBorderColorChanged); _appearanceUi->invertSelectionColorsCheckbox->setChecked(profile->property(Profile::InvertSelectionColors)); connect(_appearanceUi->invertSelectionColorsCheckbox, &QCheckBox::toggled, this, [this](bool checked) { updateTempProfileProperty(Profile::InvertSelectionColors, checked); }); _appearanceUi->displayVerticalLine->setChecked(profile->verticalLine()); connect(_appearanceUi->displayVerticalLine, &QCheckBox::toggled, this, &EditProfileDialog::setVerticalLine); _appearanceUi->displayVerticalLineAtColumn->setValue(profile->verticalLineAtChar()); connect(_appearanceUi->displayVerticalLineAtColumn, &QSpinBox::valueChanged, this, &EditProfileDialog::setVerticalLineColumn); auto *getNewButton = new KNSWidgets::Button(this); connect(getNewButton, &KNSWidgets::Button::dialogFinished, this, &Konsole::EditProfileDialog::gotNewColorSchemes); getNewButton->setText(i18n("Get New...")); getNewButton->setConfigFile(QStringLiteral(":/konsole/konsole.knsrc")); _appearanceUi->colorSchemesBtnLayout->addWidget(getNewButton); _appearanceUi->enableBidiRenderingButton->setChecked(profile->bidiRenderingEnabled()); connect(_appearanceUi->enableBidiRenderingButton, &QPushButton::toggled, this, &EditProfileDialog::togglebidiRendering); _appearanceUi->enableBidiTableDirOverrideButton->setChecked(profile->property(Profile::BidiTableDirOverride)); connect(_appearanceUi->enableBidiTableDirOverrideButton, &QPushButton::toggled, this, &EditProfileDialog::togglebidiTableDirOverride); _appearanceUi->enableBidiTableDirOverrideButton->setEnabled(profile->bidiRenderingEnabled()); _appearanceUi->bidiLineLTR->setChecked(profile->property(Profile::BidiLineLTR)); connect(_appearanceUi->bidiLineLTR, &QPushButton::toggled, this, &EditProfileDialog::togglebidiLineLTR); _appearanceUi->bidiLineLTR->setEnabled(profile->bidiRenderingEnabled()); _appearanceUi->wordMode->setChecked(profile->property(Profile::WordMode)); connect(_appearanceUi->wordMode, &QPushButton::toggled, this, &EditProfileDialog::toggleWordMode); _appearanceUi->wordModeAttr->setEnabled(profile->property(Profile::WordMode)); _appearanceUi->wordModeAscii->setEnabled(profile->property(Profile::WordMode)); _appearanceUi->wordModeBrahmic->setEnabled(profile->property(Profile::WordMode)); _appearanceUi->wordModeAttr->setChecked(profile->property(Profile::WordModeAttr)); connect(_appearanceUi->wordModeAttr, &QPushButton::toggled, this, &EditProfileDialog::toggleWordModeAttr); _appearanceUi->wordModeAscii->setChecked(profile->property(Profile::WordModeAscii)); connect(_appearanceUi->wordModeAscii, &QPushButton::toggled, this, &EditProfileDialog::toggleWordModeAscii); _appearanceUi->wordModeBrahmic->setChecked(profile->property(Profile::WordModeBrahmic)); connect(_appearanceUi->wordModeBrahmic, &QPushButton::toggled, this, &EditProfileDialog::toggleWordModeBrahmic); _appearanceUi->ignoreWcWidth->setChecked(profile->property(Profile::IgnoreWcWidth)); connect(_appearanceUi->ignoreWcWidth, &QPushButton::toggled, this, &EditProfileDialog::toggleIgnoreWcWidth); } void EditProfileDialog::setAntialiasText(bool enable) { preview(Profile::AntiAliasFonts, enable); updateTempProfileProperty(Profile::AntiAliasFonts, enable); const QFont font = _profile->font(); updateFontPreview(font); } void EditProfileDialog::toggleAllowLinkEscapeSequence(bool enable) { updateTempProfileProperty(Profile::AllowEscapedLinks, enable); } void EditProfileDialog::linkEscapeSequenceTextsChanged() { updateTempProfileProperty(Profile::EscapedLinksSchema, _mouseUi->linkEscapeSequenceTexts->text()); } void EditProfileDialog::setVerticalLine(bool value) { updateTempProfileProperty(Profile::VerticalLine, value); } void EditProfileDialog::setVerticalLineColumn(int value) { updateTempProfileProperty(Profile::VerticalLineAtChar, value); } void EditProfileDialog::setBoldIntense(bool enable) { preview(Profile::BoldIntense, enable); updateTempProfileProperty(Profile::BoldIntense, enable); } void EditProfileDialog::useFontLineCharacters(bool enable) { _appearanceUi->useFontBrailleCharactersButton->setEnabled(!enable); preview(Profile::UseFontLineCharacters, enable); updateTempProfileProperty(Profile::UseFontLineCharacters, enable); } void EditProfileDialog::useFontBrailleCharacters(bool enable) { preview(Profile::UseFontBrailleCharacters, enable); updateTempProfileProperty(Profile::UseFontBrailleCharacters, enable); } void EditProfileDialog::toggleBlinkingCursor(bool enable) { preview(Profile::BlinkingCursorEnabled, enable); updateTempProfileProperty(Profile::BlinkingCursorEnabled, enable); } void EditProfileDialog::toggleAnimatingCursor(bool enable) { preview(Profile::AnimatingCursorEnabled, enable); updateTempProfileProperty(Profile::AnimatingCursorEnabled, enable); } void EditProfileDialog::setCursorShape(int index) { preview(Profile::CursorShape, index); updateTempProfileProperty(Profile::CursorShape, index); } void EditProfileDialog::autoCursorColor() { preview(Profile::UseCustomCursorColor, false); updateTempProfileProperty(Profile::UseCustomCursorColor, false); } void EditProfileDialog::customCursorColor() { preview(Profile::UseCustomCursorColor, true); updateTempProfileProperty(Profile::UseCustomCursorColor, true); } void EditProfileDialog::customCursorColorChanged(const QColor &color) { preview(Profile::CustomCursorColor, color); updateTempProfileProperty(Profile::CustomCursorColor, color); // ensure that custom cursor colors are enabled _appearanceUi->customCursorColorButton->click(); } void EditProfileDialog::customCursorTextColorChanged(const QColor &color) { preview(Profile::CustomCursorTextColor, color); updateTempProfileProperty(Profile::CustomCursorTextColor, color); // ensure that custom cursor colors are enabled _appearanceUi->customCursorColorButton->click(); } void EditProfileDialog::terminalMarginChanged(int margin) { preview(Profile::TerminalMargin, margin); updateTempProfileProperty(Profile::TerminalMargin, margin); } void EditProfileDialog::lineSpacingChanged(int spacing) { preview(Profile::LineSpacing, spacing); updateTempProfileProperty(Profile::LineSpacing, spacing); } void EditProfileDialog::setTerminalCenter(bool enable) { preview(Profile::TerminalCenter, enable); updateTempProfileProperty(Profile::TerminalCenter, enable); } void EditProfileDialog::toggleMouseWheelZoom(bool enable) { updateTempProfileProperty(Profile::MouseWheelZoomEnabled, enable); } void EditProfileDialog::toggleAlternateScrolling(bool enable) { updateTempProfileProperty(Profile::AlternateScrolling, enable); } void EditProfileDialog::toggleAllowColorFilter(bool enable) { updateTempProfileProperty(Profile::ColorFilterEnabled, enable); } void EditProfileDialog::toggleAllowMouseTracking(bool allow) { updateTempProfileProperty(Profile::AllowMouseTracking, allow); } void EditProfileDialog::updateColorSchemeList(const QString &selectedColorSchemeName) { if (_appearanceUi->colorSchemeList->model() == nullptr) { _appearanceUi->colorSchemeList->setModel(new QStandardItemModel(this)); } std::shared_ptr selectedColorScheme = ColorSchemeManager::instance()->findColorScheme(selectedColorSchemeName); auto *model = qobject_cast(_appearanceUi->colorSchemeList->model()); Q_ASSERT(model); model->clear(); QStandardItem *selectedItem = nullptr; const QList> schemeList = ColorSchemeManager::instance()->allColorSchemes(); for (const std::shared_ptr &scheme : schemeList) { QStandardItem *item = new QStandardItem(scheme->description()); item->setData(QVariant::fromValue(scheme), Qt::UserRole + 1); item->setData(QVariant::fromValue(_profile->font()), Qt::UserRole + 2); item->setFlags(item->flags()); // if selectedColorSchemeName is not empty then select that scheme // after saving the changes in the colorScheme editor if (selectedColorScheme == scheme) { selectedItem = item; } model->appendRow(item); } model->sort(0); if (selectedItem != nullptr) { _appearanceUi->colorSchemeList->updateGeometry(); _appearanceUi->colorSchemeList->selectionModel()->setCurrentIndex(selectedItem->index(), QItemSelectionModel::Select); // update transparency warning label updateTransparencyWarning(); } } void EditProfileDialog::updateKeyBindingsList(const QString &selectKeyBindingsName) { if (_keyboardUi->keyBindingList->model() == nullptr) { _keyboardUi->keyBindingList->setModel(new QStandardItemModel(this)); } auto *model = qobject_cast(_keyboardUi->keyBindingList->model()); Q_ASSERT(model); model->clear(); QStandardItem *selectedItem = nullptr; const QStringList &translatorNames = _keyManager->allTranslators(); for (const QString &translatorName : translatorNames) { const KeyboardTranslator *translator = _keyManager->findTranslator(translatorName); if (translator == nullptr) { continue; } QStandardItem *item = new QStandardItem(translator->description()); item->setEditable(false); item->setData(QVariant::fromValue(translator), Qt::UserRole + 1); item->setData(QVariant::fromValue(_keyManager->findTranslatorPath(translatorName)), Qt::ToolTipRole); item->setData(QVariant::fromValue(_profile->font()), Qt::UserRole + 2); item->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-keyboard"))); if (selectKeyBindingsName == translatorName) { selectedItem = item; } model->appendRow(item); } model->sort(0); if (selectedItem != nullptr) { _keyboardUi->keyBindingList->selectionModel()->setCurrentIndex(selectedItem->index(), QItemSelectionModel::Select); } } QSize EditProfileDialog::sizeHint() const { QFontMetrics fm(font()); const int ch = fm.boundingRect(QLatin1Char('0')).width(); // By default minimum size is used. Increase it to make text inputs // on "tabs" page wider and to add some whitespace on right side // of other pages. The window will not be wider than 2/3 of // the screen width (unless necessary to fit everything) return QDialog::sizeHint() + QSize(10 * ch, 0); } void EditProfileDialog::unpreviewAll() { // undo any preview changes if (!_previewedProperties.empty()) { ProfileManager::instance()->changeProfile(_profile, _previewedProperties, false); } } void EditProfileDialog::unpreview(Profile::Property prop) { auto node = _previewedProperties.extract(prop); if (!node) { return; } Profile::PropertyMap map; map.insert(std::move(node)); ProfileManager::instance()->changeProfile(_profile, map, false); } void EditProfileDialog::preview(Profile::Property prop, const QVariant &value) { const Profile::Ptr original = _profile; // skip previews for profile groups if the profiles in the group // have conflicting original values for the property // // TODO - Save the original values for each profile and use to unpreview properties ProfileGroup::Ptr group = original->asGroup(); if (group && group->profiles().count() > 1 && original->property(prop).isNull()) { return; } _previewedProperties.insert({prop, original->property(prop)}); // temporary change to color scheme ProfileManager::instance()->changeProfile(_profile, {{prop, value}}, false); } void EditProfileDialog::previewColorScheme(const QModelIndex &index) { const QString &name = index.data(Qt::UserRole + 1).value>()->name(); preview(Profile::ColorScheme, name); } void EditProfileDialog::showFontDialog() { if (_fontDialog == nullptr) { _fontDialog = new FontDialog(this); _fontDialog->setModal(true); connect(_fontDialog, &FontDialog::fontChanged, this, [this](const QFont &font) { preview(Profile::Font, font); updateFontPreview(font); }); connect(_fontDialog, &FontDialog::accepted, this, [this]() { const QFont font = _fontDialog->font(); preview(Profile::Font, font); updateTempProfileProperty(Profile::Font, font); updateFontPreview(font); }); connect(_fontDialog, &FontDialog::rejected, this, [this]() { unpreview(Profile::Font); updateFontPreview(_profile->font()); }); } _fontDialog->setFont(_profile->font()); _fontDialog->show(); } void EditProfileDialog::showEmojiFontDialog() { if (_emojiFontDialog == nullptr) { _emojiFontDialog = new FontDialog(this, true, _profile->emojiFont()); _emojiFontDialog->setModal(true); connect(_emojiFontDialog, &FontDialog::fontChanged, this, [this](const QFont &font) { preview(Profile::EmojiFont, font); updateEmojiFontPreview(font); }); connect(_emojiFontDialog, &FontDialog::accepted, this, [this]() { const QFont font = _emojiFontDialog->font(); preview(Profile::EmojiFont, font); updateTempProfileProperty(Profile::EmojiFont, font); updateEmojiFontPreview(font); }); connect(_emojiFontDialog, &FontDialog::rejected, this, [this]() { unpreview(Profile::EmojiFont); updateEmojiFontPreview(_profile->emojiFont()); }); } _emojiFontDialog->show(); } void EditProfileDialog::updateFontPreview(QFont font) { bool aa = _profile->antiAliasFonts(); font.setStyleStrategy(aa ? QFont::PreferAntialias : QFont::NoAntialias); _appearanceUi->fontPreview->setFont(font); _appearanceUi->fontPreview->setText(QStringLiteral("%1 %2pt").arg(font.family()).arg(font.pointSize())); } void EditProfileDialog::updateEmojiFontPreview(QFont font) { bool aa = _profile->antiAliasFonts(); font.setStyleStrategy(aa ? QFont::PreferAntialias : QFont::NoAntialias); QFont emojiFont = _profile->emojiFont(); _appearanceUi->emojiFontPreview->setFont(_profile->font()); _appearanceUi->emojiFontPreview->setText(QStringLiteral("%1 %2pt").arg(emojiFont.family()).arg(emojiFont.pointSize())); } void EditProfileDialog::removeColorScheme() { const QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes(); if (selected.isEmpty()) { return; } const QString &name = selected.first().data(Qt::UserRole + 1).value>()->name(); Q_ASSERT(!name.isEmpty()); if (ColorSchemeManager::instance()->deleteColorScheme(name)) { _appearanceUi->colorSchemeList->model()->removeRow(selected.first().row()); } } void EditProfileDialog::gotNewColorSchemes(const QList &changedEntries) { int failures = 0; for (auto &entry : std::as_const(changedEntries)) { switch (entry.status()) { case KNSCore::Entry::Installed: { const auto installedFiles = entry.installedFiles(); for (const QString &file : installedFiles) { if (ColorSchemeManager::instance()->loadColorScheme(file)) { continue; } qWarning() << "Failed to load file" << file; ++failures; } if (failures == entry.installedFiles().size()) { _appearanceUi->colorSchemeMessageWidget->setText(xi18nc("@info", "Scheme %1 failed to load.", entry.name())); _appearanceUi->colorSchemeMessageWidget->animatedShow(); QTimer::singleShot(8000, _appearanceUi->colorSchemeMessageWidget, &KMessageWidget::animatedHide); } break; } case KNSCore::Entry::Deleted: { const auto uninstalledFiles = entry.uninstalledFiles(); for (const auto &file : uninstalledFiles) { if (ColorSchemeManager::instance()->unloadColorScheme(file)) { continue; } qWarning() << "Failed to unload file" << file; // If unloading fails we do not care. If the scheme failed here // it either wasn't loaded or was invalid to begin with. } break; } case KNSCore::Entry::Invalid: case KNSCore::Entry::Installing: case KNSCore::Entry::Downloadable: case KNSCore::Entry::Updateable: case KNSCore::Entry::Updating: // Not interesting. break; } } updateColorSchemeList(currentColorSchemeName()); } void EditProfileDialog::resetColorScheme() { QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes(); if (!selected.isEmpty()) { const QString &name = selected.first().data(Qt::UserRole + 1).value>()->name(); ColorSchemeManager::instance()->deleteColorScheme(name); // select the colorScheme used in the current profile updateColorSchemeList(currentColorSchemeName()); } } void EditProfileDialog::showColorSchemeEditor(bool isNewScheme) { // Finding selected ColorScheme QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes(); QAbstractItemModel *model = _appearanceUi->colorSchemeList->model(); std::shared_ptr colors; if (!selected.isEmpty()) { colors = model->data(selected.first(), Qt::UserRole + 1).value>(); } else { colors = ColorSchemeManager::instance()->defaultColorScheme(); } Q_ASSERT(colors); // Setting up ColorSchemeEditor ui // close any running ColorSchemeEditor if (_colorDialog != nullptr) { closeColorSchemeEditor(); } _colorDialog = new ColorSchemeEditor(WindowSystemInfo::compositingActive(), this); connect(_colorDialog, &Konsole::ColorSchemeEditor::colorSchemeSaveRequested, this, &Konsole::EditProfileDialog::saveColorScheme); _colorDialog->setup(colors, isNewScheme); _colorDialog->show(); } void EditProfileDialog::closeColorSchemeEditor() { if (_colorDialog != nullptr) { _colorDialog->close(); delete _colorDialog; } } void EditProfileDialog::newColorScheme() { showColorSchemeEditor(true); } void EditProfileDialog::editColorScheme() { showColorSchemeEditor(false); } void EditProfileDialog::saveColorScheme(const ColorScheme &scheme, bool isNewScheme) { std::shared_ptr newScheme = std::make_shared(scheme); // if this is a new color scheme, pick a name based on the description if (isNewScheme) { newScheme->setName(newScheme->description()); } ColorSchemeManager::instance()->addColorScheme(newScheme); const QString &selectedColorSchemeName = newScheme->name(); // select the edited or the new colorScheme after saving the changes updateColorSchemeList(selectedColorSchemeName); preview(Profile::ColorScheme, newScheme->name()); } void EditProfileDialog::colorSchemeSelected() { QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes(); if (!selected.isEmpty()) { QAbstractItemModel *model = _appearanceUi->colorSchemeList->model(); std::shared_ptr colors = model->data(selected.first(), Qt::UserRole + 1).value>(); if (colors != nullptr) { updateTempProfileProperty(Profile::ColorScheme, colors->name()); previewColorScheme(selected.first()); updateTransparencyWarning(); } } updateColorSchemeButtons(); } void EditProfileDialog::updateColorSchemeButtons() { enableIfNonEmptySelection(_appearanceUi->editColorSchemeButton, _appearanceUi->colorSchemeList->selectionModel()); QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes(); if (!selected.isEmpty()) { const QString &name = selected.first().data(Qt::UserRole + 1).value>()->name(); bool isResettable = ColorSchemeManager::instance()->canResetColorScheme(name); _appearanceUi->resetColorSchemeButton->setEnabled(isResettable); bool isDeletable = ColorSchemeManager::instance()->isColorSchemeDeletable(name); // if a colorScheme can be restored then it can't be deleted _appearanceUi->removeColorSchemeButton->setEnabled(isDeletable && !isResettable); } else { _appearanceUi->removeColorSchemeButton->setEnabled(false); _appearanceUi->resetColorSchemeButton->setEnabled(false); } } void EditProfileDialog::updateKeyBindingsButtons() { QModelIndexList selected = _keyboardUi->keyBindingList->selectionModel()->selectedIndexes(); if (!selected.isEmpty()) { _keyboardUi->editKeyBindingsButton->setEnabled(true); const QString &name = selected.first().data(Qt::UserRole + 1).value()->name(); bool isResettable = _keyManager->isTranslatorResettable(name); _keyboardUi->resetKeyBindingsButton->setEnabled(isResettable); bool isDeletable = _keyManager->isTranslatorDeletable(name); // if a key bindings scheme can be reset then it can't be deleted _keyboardUi->removeKeyBindingsButton->setEnabled(isDeletable && !isResettable); } } void EditProfileDialog::enableIfNonEmptySelection(QWidget *widget, QItemSelectionModel *selectionModel) { widget->setEnabled(selectionModel->hasSelection()); } void EditProfileDialog::updateTransparencyWarning() { // zero or one indexes can be selected const QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes(); for (const QModelIndex &index : selected) { bool needTransparency = index.data(Qt::UserRole + 1).value>()->opacity() < 1.0; if (!needTransparency) { _appearanceUi->transparencyWarningWidget->setHidden(true); } else if (!WindowSystemInfo::compositingActive()) { _appearanceUi->transparencyWarningWidget->setText( i18n("This color scheme uses a transparent background" " which does not appear to be supported on your" " desktop")); _appearanceUi->transparencyWarningWidget->setHidden(false); } else if (!WindowSystemInfo::HAVE_TRANSPARENCY) { _appearanceUi->transparencyWarningWidget->setText( i18n("Konsole was started before desktop effects were enabled." " You need to restart Konsole to see transparent background.")); _appearanceUi->transparencyWarningWidget->setHidden(false); } } } void EditProfileDialog::resetTempProfile() { _tempProfile->clear(); _tempProfile->setHidden(true); } void EditProfileDialog::updateTempProfileProperty(Profile::Property property, const QVariant &value) { _tempProfile->setProperty(property, value); updateButtonApply(); } void EditProfileDialog::updateButtonApply() { auto filterFunc = [this](const auto &keyValue) { const auto &[property, value] = keyValue; // for previewed property auto it = _previewedProperties.find(property); if (it != _previewedProperties.cend()) { if (value != it->second) { return true; } // for not-previewed property // // for the Profile::KeyBindings property, if it's set in the _tempProfile // then the user opened the edit key bindings dialog and clicked // OK, and could have add/removed a key bindings rule } else if (property == Profile::KeyBindings // || value != _profile->property(property)) { return true; } return false; }; const Profile::PropertyMap &map = _tempProfile->properties(); bool userModified = std::any_of(map.cbegin(), map.cend(), filterFunc); if (_generalUi->setAsDefaultButton->isChecked() != _isDefault) { userModified = true; } _buttonBox->button(QDialogButtonBox::Apply)->setEnabled(userModified); } void EditProfileDialog::setupKeyboardPage(const Profile::Ptr & /* profile */) { // setup translator list updateKeyBindingsList(_profile->keyBindings()); connect(_keyboardUi->keyBindingList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Konsole::EditProfileDialog::keyBindingSelected); connect(_keyboardUi->newKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::newKeyBinding); _keyboardUi->editKeyBindingsButton->setIcon(QIcon::fromTheme(QStringLiteral("document-edit"))); _keyboardUi->removeKeyBindingsButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); _keyboardUi->newKeyBindingsButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); _keyboardUi->resetKeyBindingsButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo"))); _keyboardUi->editKeyBindingsButton->setEnabled(false); _keyboardUi->removeKeyBindingsButton->setEnabled(false); _keyboardUi->resetKeyBindingsButton->setEnabled(false); updateKeyBindingsButtons(); connect(_keyboardUi->editKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::editKeyBinding); connect(_keyboardUi->removeKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::removeKeyBinding); connect(_keyboardUi->resetKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::resetKeyBindings); } void EditProfileDialog::keyBindingSelected() { QModelIndexList selected = _keyboardUi->keyBindingList->selectionModel()->selectedIndexes(); if (!selected.isEmpty()) { QAbstractItemModel *model = _keyboardUi->keyBindingList->model(); const auto *translator = model->data(selected.first(), Qt::UserRole + 1).value(); if (translator != nullptr) { updateTempProfileProperty(Profile::KeyBindings, translator->name()); } } updateKeyBindingsButtons(); } void EditProfileDialog::removeKeyBinding() { QModelIndexList selected = _keyboardUi->keyBindingList->selectionModel()->selectedIndexes(); if (!selected.isEmpty()) { const QString &name = selected.first().data(Qt::UserRole + 1).value()->name(); if (KeyboardTranslatorManager::instance()->deleteTranslator(name)) { _keyboardUi->keyBindingList->model()->removeRow(selected.first().row()); } } } void EditProfileDialog::showKeyBindingEditor(bool isNewTranslator) { QModelIndexList selected = _keyboardUi->keyBindingList->selectionModel()->selectedIndexes(); QAbstractItemModel *model = _keyboardUi->keyBindingList->model(); const KeyboardTranslator *translator = nullptr; if (!selected.isEmpty()) { translator = model->data(selected.first(), Qt::UserRole + 1).value(); } else { translator = _keyManager->defaultTranslator(); } auto *editor = new KeyBindingEditor(this); editor->setAttribute(Qt::WA_DeleteOnClose); editor->setModal(true); if (translator != nullptr) { editor->setup(translator, _profile->keyBindings(), isNewTranslator); } connect(editor, &Konsole::KeyBindingEditor::updateKeyBindingsListRequest, this, &Konsole::EditProfileDialog::updateKeyBindingsList); connect(editor, &Konsole::KeyBindingEditor::updateTempProfileKeyBindingsRequest, this, &Konsole::EditProfileDialog::updateTempProfileProperty); editor->show(); } void EditProfileDialog::newKeyBinding() { showKeyBindingEditor(true); } void EditProfileDialog::editKeyBinding() { showKeyBindingEditor(false); } void EditProfileDialog::resetKeyBindings() { QModelIndexList selected = _keyboardUi->keyBindingList->selectionModel()->selectedIndexes(); if (!selected.isEmpty()) { const QString &name = selected.first().data(Qt::UserRole + 1).value()->name(); _keyManager->deleteTranslator(name); // find and load the translator _keyManager->findTranslator(name); updateKeyBindingsList(name); } } void EditProfileDialog::setupButtonGroup(const ButtonGroupOptions &options, const Profile::Ptr &profile) { auto currentValue = profile->property(options.profileProperty); for (auto option : options.buttons) { options.group->setId(option.button, option.value); } Q_ASSERT(options.buttons.count() > 0); auto *activeButton = options.group->button(currentValue); if (activeButton == nullptr) { activeButton = options.buttons[0].button; } activeButton->setChecked(true); connect(options.group, &QButtonGroup::idClicked, this, [this, options](int value) { if (options.preview) { preview(options.profileProperty, value); } updateTempProfileProperty(options.profileProperty, value); }); } void EditProfileDialog::setupScrollingPage(const Profile::Ptr &profile) { // setup scrollbar radio const ButtonGroupOptions scrollBarPositionOptions = { _scrollingUi->scrollBarPosition, // group Profile::ScrollBarPosition, // profileProperty false, // preview { // buttons {_scrollingUi->scrollBarRightButton, Enum::ScrollBarRight}, {_scrollingUi->scrollBarLeftButton, Enum::ScrollBarLeft}, {_scrollingUi->scrollBarHiddenButton, Enum::ScrollBarHidden}, }, }; setupButtonGroup(scrollBarPositionOptions, profile); // setup scrollback type radio auto scrollBackType = profile->property(Profile::HistoryMode); _scrollingUi->historySizeWidget->setMode(Enum::HistoryModeEnum(scrollBackType)); connect(_scrollingUi->historySizeWidget, &Konsole::HistorySizeWidget::historyModeChanged, this, &Konsole::EditProfileDialog::historyModeChanged); // setup scrollback line count spinner const int historySize = profile->historySize(); _scrollingUi->historySizeWidget->setLineCount(historySize); // setup scrollpageamount type radio auto scrollFullPage = profile->property(Profile::ScrollFullPage); _scrollingUi->scrollHalfPage->setChecked(Enum::ScrollPageHalf == scrollFullPage); connect(_scrollingUi->scrollHalfPage, &QPushButton::clicked, this, &EditProfileDialog::scrollHalfPage); _scrollingUi->scrollFullPage->setChecked(Enum::ScrollPageFull == scrollFullPage); connect(_scrollingUi->scrollFullPage, &QPushButton::clicked, this, &EditProfileDialog::scrollFullPage); _scrollingUi->highlightScrolledLinesButton->setChecked(profile->property(Profile::HighlightScrolledLines)); connect(_scrollingUi->highlightScrolledLinesButton, &QPushButton::clicked, this, &EditProfileDialog::toggleHighlightScrolledLines); _scrollingUi->reflowLinesButton->setChecked(profile->property(Profile::ReflowLines)); connect(_scrollingUi->reflowLinesButton, &QPushButton::clicked, this, &EditProfileDialog::toggleReflowLines); // setup marker color button _scrollingUi->markerColorButton->setColor(profile->property(Profile::MarkerColor)); connect(_scrollingUi->markerColorButton, &KColorButton::changed, this, &Konsole::EditProfileDialog::toggleScrollbarMarkerColor); // setup marker size widget _scrollingUi->markerSizeWidget->setValue(profile->property(Profile::MarkerSize)); connect(_scrollingUi->markerSizeWidget, &QDoubleSpinBox::valueChanged, this, &Konsole::EditProfileDialog::toggleScrollbarMarkerSize); // setup search highlighter color button _scrollingUi->searchHighligheterColorButton->setColor(profile->property(Profile::SearchLineColor)); connect(_scrollingUi->searchHighligheterColorButton, &KColorButton::changed, this, &Konsole::EditProfileDialog::toggleScrollbarSearchLineColor); // setup search highlighter opacity widget _scrollingUi->labelSearchHighlighterOpacitySlider->setValue(profile->property(Profile::SearchLineOpacity)); connect(_scrollingUi->labelSearchHighlighterOpacitySlider, &QSlider::valueChanged, this, &Konsole::EditProfileDialog::toggleScrollbarSearchLineOpacity); // signals and slots connect(_scrollingUi->historySizeWidget, &Konsole::HistorySizeWidget::historySizeChanged, this, &Konsole::EditProfileDialog::historySizeChanged); } void EditProfileDialog::historySizeChanged(int lineCount) { updateTempProfileProperty(Profile::HistorySize, lineCount); } void EditProfileDialog::historyModeChanged(Enum::HistoryModeEnum mode) { updateTempProfileProperty(Profile::HistoryMode, mode); } void EditProfileDialog::scrollFullPage() { updateTempProfileProperty(Profile::ScrollFullPage, Enum::ScrollPageFull); } void EditProfileDialog::scrollHalfPage() { updateTempProfileProperty(Profile::ScrollFullPage, Enum::ScrollPageHalf); } void EditProfileDialog::toggleHighlightScrolledLines(bool enable) { updateTempProfileProperty(Profile::HighlightScrolledLines, enable); } void EditProfileDialog::toggleReflowLines(bool enable) { updateTempProfileProperty(Profile::ReflowLines, enable); } void EditProfileDialog::toggleScrollbarMarkerColor(QColor color) { updateTempProfileProperty(Profile::MarkerColor, color); } void EditProfileDialog::toggleScrollbarMarkerSize(double pSize) { updateTempProfileProperty(Profile::MarkerSize, pSize); } void EditProfileDialog::toggleScrollbarSearchLineColor(QColor color) { updateTempProfileProperty(Profile::SearchLineColor, color); } void EditProfileDialog::toggleScrollbarSearchLineOpacity(int opacity) { updateTempProfileProperty(Profile::SearchLineOpacity, opacity); } void EditProfileDialog::setupMousePage(const Profile::Ptr &profile) { _mouseUi->underlineLinksButton->setChecked(profile->property(Profile::UnderlineLinksEnabled)); connect(_mouseUi->underlineLinksButton, &QPushButton::toggled, this, &EditProfileDialog::toggleUnderlineLinks); _mouseUi->underlineFilesButton->setChecked(profile->property(Profile::UnderlineFilesEnabled)); connect(_mouseUi->underlineFilesButton, &QPushButton::toggled, this, &EditProfileDialog::toggleUnderlineFiles); _mouseUi->ctrlRequiredForDragButton->setChecked(profile->property(Profile::CtrlRequiredForDrag)); connect(_mouseUi->ctrlRequiredForDragButton, &QPushButton::toggled, this, &EditProfileDialog::toggleCtrlRequiredForDrag); _mouseUi->copyTextAsHTMLButton->setChecked(profile->property(Profile::CopyTextAsHTML)); connect(_mouseUi->copyTextAsHTMLButton, &QPushButton::toggled, this, &EditProfileDialog::toggleCopyTextAsHTML); _mouseUi->copyTextToClipboardButton->setChecked(profile->property(Profile::AutoCopySelectedText)); connect(_mouseUi->copyTextToClipboardButton, &QPushButton::toggled, this, &EditProfileDialog::toggleCopyTextToClipboard); _mouseUi->trimLeadingSpacesButton->setChecked(profile->property(Profile::TrimLeadingSpacesInSelectedText)); connect(_mouseUi->trimLeadingSpacesButton, &QPushButton::toggled, this, &EditProfileDialog::toggleTrimLeadingSpacesInSelectedText); _mouseUi->trimTrailingSpacesButton->setChecked(profile->property(Profile::TrimTrailingSpacesInSelectedText)); connect(_mouseUi->trimTrailingSpacesButton, &QPushButton::toggled, this, &EditProfileDialog::toggleTrimTrailingSpacesInSelectedText); _mouseUi->openLinksByDirectClickButton->setChecked(profile->property(Profile::OpenLinksByDirectClickEnabled)); connect(_mouseUi->openLinksByDirectClickButton, &QPushButton::toggled, this, &EditProfileDialog::toggleOpenLinksByDirectClick); _mouseUi->dropUrlsAsText->setChecked(profile->property(Profile::DropUrlsAsText)); connect(_mouseUi->dropUrlsAsText, &QPushButton::toggled, this, &EditProfileDialog::toggleDropUrlsAsText); _mouseUi->enableAlternateScrollingButton->setChecked(profile->property(Profile::AlternateScrolling)); connect(_mouseUi->enableAlternateScrollingButton, &QPushButton::toggled, this, &EditProfileDialog::toggleAlternateScrolling); _mouseUi->allowColorFilters->setChecked(profile->property(Profile::ColorFilterEnabled)); connect(_mouseUi->allowColorFilters, &QPushButton::toggled, this, &EditProfileDialog::toggleAllowColorFilter); _mouseUi->allowMouseTrackingButton->setChecked(profile->property(Profile::AllowMouseTracking)); connect(_mouseUi->allowMouseTrackingButton, &QPushButton::toggled, this, &EditProfileDialog::toggleAllowMouseTracking); // setup middle click paste mode const auto middleClickPasteMode = profile->property(Profile::MiddleClickPasteMode); _mouseUi->pasteFromX11SelectionButton->setChecked(Enum::PasteFromX11Selection == middleClickPasteMode); connect(_mouseUi->pasteFromX11SelectionButton, &QPushButton::clicked, this, &EditProfileDialog::pasteFromX11Selection); _mouseUi->pasteFromClipboardButton->setChecked(Enum::PasteFromClipboard == middleClickPasteMode); connect(_mouseUi->pasteFromClipboardButton, &QPushButton::clicked, this, &EditProfileDialog::pasteFromClipboard); _mouseUi->textEditorCustomBtn->setIcon(QIcon::fromTheme(QStringLiteral("document-edit"))); // interaction options _mouseUi->wordCharacterEdit->setText(profile->wordCharacters()); connect(_mouseUi->wordCharacterEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::wordCharactersChanged); const ButtonGroupOptions tripleClickModeOptions = { _mouseUi->tripleClickMode, // group Profile::TripleClickMode, // profileProperty false, // preview { // buttons {_mouseUi->tripleClickSelectsTheWholeLine, Enum::SelectWholeLine}, {_mouseUi->tripleClickSelectsFromMousePosition, Enum::SelectForwardsFromCursor}, }, }; setupButtonGroup(tripleClickModeOptions, profile); _mouseUi->openLinksByDirectClickButton->setEnabled(_mouseUi->underlineLinksButton->isChecked() || _mouseUi->underlineFilesButton->isChecked()); _mouseUi->enableMouseWheelZoomButton->setChecked(profile->mouseWheelZoomEnabled()); connect(_mouseUi->enableMouseWheelZoomButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::toggleMouseWheelZoom); _mouseUi->allowLinkEscapeSequenceButton->setChecked(profile->allowEscapedLinks()); connect(_mouseUi->allowLinkEscapeSequenceButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::toggleAllowLinkEscapeSequence); _mouseUi->linkEscapeSequenceTexts->setEnabled(profile->allowEscapedLinks()); _mouseUi->linkEscapeSequenceTexts->setText(profile->escapedLinksSchema().join(QLatin1Char(';'))); connect(_mouseUi->linkEscapeSequenceTexts, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::linkEscapeSequenceTextsChanged); setTextEditorCombo(profile); } void EditProfileDialog::setTextEditorCombo(const Profile::Ptr &profile) { static const Enum::TextEditorCmd editorsList[] = {Enum::Kate, Enum::KWrite, Enum::KDevelop, Enum::QtCreator, Enum::Gedit, Enum::gVim, Enum::CustomTextEditor, Enum::Default}; auto *editorCombo = _mouseUi->textEditorCombo; QStandardItemModel *model = static_cast(editorCombo->model()); Q_ASSERT(model); for (auto editor : editorsList) { QString exec; QString displayName; QIcon icon; switch (editor) { case Enum::Kate: exec = QStringLiteral("kate"); displayName = QStringLiteral("Kate"); icon = QIcon::fromTheme(exec); break; case Enum::KWrite: exec = QStringLiteral("kwrite"); displayName = QStringLiteral("KWrite"); icon = QIcon::fromTheme(exec); break; case Enum::KDevelop: exec = QStringLiteral("kdevelop"); displayName = QStringLiteral("KDevelop"); icon = QIcon::fromTheme(exec); break; case Enum::QtCreator: exec = QStringLiteral("qtcreator"); displayName = QStringLiteral("Qt Creator"); icon = QIcon::fromTheme(exec); break; case Enum::Gedit: exec = QStringLiteral("gedit"); displayName = QStringLiteral("Gedit"); QIcon::fromTheme(QStringLiteral("org.gnome.gedit")); break; case Enum::gVim: exec = QStringLiteral("gvim"); displayName = QStringLiteral("gVim"); icon = QIcon::fromTheme(exec); break; case Enum::CustomTextEditor: displayName = QStringLiteral("Custom"); icon = QIcon::fromTheme(QStringLiteral("system-run")); break; case Enum::Default: displayName = i18n("Default for file type"); icon = QIcon::fromTheme(QStringLiteral("preferences-desktop-default-applications")); default: break; } editorCombo->addItem(icon, displayName); // For "CustomTextEditor" we don't check if the binary exists const bool isAvailable = editor == Enum::CustomTextEditor || editor == Enum::Default || !QStandardPaths::findExecutable(exec).isEmpty(); // Make un-available editors look disabled in the combobox model->item(static_cast(editor))->setEnabled(isAvailable); } const auto currentEditor = profile->property(Profile::TextEditorCmd); editorCombo->setCurrentIndex(currentEditor); connect(editorCombo, &QComboBox::currentIndexChanged, this, [this](const int index) { updateTempProfileProperty(Profile::TextEditorCmd, index); _mouseUi->textEditorCustomBtn->setEnabled(index == Enum::CustomTextEditor); }); _mouseUi->textEditorCustomBtn->setEnabled(currentEditor == Enum::CustomTextEditor); connect(_mouseUi->textEditorCustomBtn, &QAbstractButton::clicked, this, [this, profile]() { auto *dlg = new QInputDialog(static_cast(this)); dlg->setLabelText( i18n("The format is e.g. 'editorExec PATH:LINE:COLUMN'\n\n" "PATH will be replaced by the path to the text file\n" "LINE will be replaced by the line number\n" "COLUMN (optional) will be replaced by the column number\n" "Note: you will need to replace 'PATH:LINE:COLUMN' by the actual\n" "syntax the editor you want to use supports; e.g.:\n" "gedit +LINE:COLUMN PATH\n\n" "If PATH or LINE aren't present in the command, this setting\n" "will be ignored and the file will be opened by the default text\n" "editor.")); const QString cmd = profile->customTextEditorCmd(); dlg->setTextValue(cmd); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setWindowTitle(i18n("Text Editor Custom Command")); QFontMetrics fm(font()); const int width = qMin(fm.averageCharWidth() * cmd.size(), this->width()); dlg->resize(width, dlg->height()); connect(dlg, &QDialog::accepted, this, [this, dlg]() { updateTempProfileProperty(Profile::TextEditorCmdCustom, dlg->textValue()); }); dlg->show(); }); } void EditProfileDialog::setupAdvancedPage(const Profile::Ptr &profile) { _advancedUi->enableBlinkingTextButton->setChecked(profile->property(Profile::BlinkingTextEnabled)); connect(_advancedUi->enableBlinkingTextButton, &QPushButton::toggled, this, &EditProfileDialog::toggleBlinkingText); _advancedUi->enableFlowControlButton->setChecked(profile->property(Profile::FlowControlEnabled)); connect(_advancedUi->enableFlowControlButton, &QPushButton::toggled, this, &EditProfileDialog::toggleFlowControl); _appearanceUi->enableBlinkingCursorButton->setChecked(profile->property(Profile::BlinkingCursorEnabled)); connect(_appearanceUi->enableBlinkingCursorButton, &QPushButton::toggled, this, &EditProfileDialog::toggleBlinkingCursor); _advancedUi->enableReverseUrlHints->setChecked(profile->property(Profile::ReverseUrlHints)); connect(_advancedUi->enableReverseUrlHints, &QPushButton::toggled, this, &EditProfileDialog::toggleReverseUrlHints); // Setup the URL hints modifier checkboxes { auto modifiers = profile->property(Profile::UrlHintsModifiers); _advancedUi->urlHintsModifierShift->setChecked((modifiers & Qt::ShiftModifier) != 0U); _advancedUi->urlHintsModifierCtrl->setChecked((modifiers & Qt::ControlModifier) != 0U); _advancedUi->urlHintsModifierAlt->setChecked((modifiers & Qt::AltModifier) != 0U); _advancedUi->urlHintsModifierMeta->setChecked((modifiers & Qt::MetaModifier) != 0U); connect(_advancedUi->urlHintsModifierShift, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier); connect(_advancedUi->urlHintsModifierCtrl, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier); connect(_advancedUi->urlHintsModifierAlt, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier); connect(_advancedUi->urlHintsModifierMeta, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier); } _advancedUi->autoSaveIntervalWidget->setValue(profile->property(Profile::AutoSaveInterval)); connect(_advancedUi->autoSaveIntervalWidget, &QSpinBox::valueChanged, this, &EditProfileDialog::setAutoSaveInterval); // encoding options auto codecAction = new KCodecAction(this); codecAction->setCurrentCodec(profile->defaultEncoding()); _advancedUi->selectEncodingButton->setMenu(codecAction->menu()); connect(codecAction, &KCodecAction::codecNameTriggered, this, [this](const QByteArray &codecName) { setDefaultCodec(codecName); }); connect(codecAction, &KCodecAction::defaultItemTriggered, this, [this] { setDefaultCodec(QStringConverter::nameForEncoding(QStringConverter::System)); }); _advancedUi->selectEncodingButton->setText(profile->defaultEncoding()); _advancedUi->peekPrimaryWidget->setKeySequence(profile->peekPrimaryKeySequence()); connect(_advancedUi->peekPrimaryWidget, &QKeySequenceEdit::editingFinished, this, &EditProfileDialog::peekPrimaryKeySequenceChanged); const ButtonGroupOptions lineNums = { _advancedUi->lineNums, // group Profile::LineNumbers, // profileProperty false, // preview { // buttons {_advancedUi->lineNumsNever, Enum::HintsNever}, {_advancedUi->lineNumsURL, Enum::HintsURL}, {_advancedUi->lineNumsAlways, Enum::HintsAlways}, }, }; setupButtonGroup(lineNums, profile); } int EditProfileDialog::maxSpinBoxWidth(const KPluralHandlingSpinBox *spinBox, const KLocalizedString &suffix) { static const int cursorWidth = 2; const QFontMetrics fm(spinBox->fontMetrics()); const QString plural = suffix.subs(2).toString(); const QString singular = suffix.subs(1).toString(); const QString min = QString::number(spinBox->minimum()); const QString max = QString::number(spinBox->maximum()); const int pluralWidth = fm.boundingRect(plural).width(); const int singularWidth = fm.boundingRect(singular).width(); const int minWidth = fm.boundingRect(min).width(); const int maxWidth = fm.boundingRect(max).width(); const int width = qMax(pluralWidth, singularWidth) + qMax(minWidth, maxWidth) + cursorWidth; // Based on QAbstractSpinBox::initStyleOption() from Qt QStyleOptionSpinBox opt; opt.initFrom(spinBox); opt.activeSubControls = QStyle::SC_None; opt.buttonSymbols = spinBox->buttonSymbols(); // Assume all spinboxes have buttons opt.subControls = QStyle::SC_SpinBoxFrame | QStyle::SC_SpinBoxEditField | QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown; opt.frame = spinBox->hasFrame(); const QSize hint(width, spinBox->sizeHint().height()); const QSize spinBoxSize = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, spinBox); return spinBoxSize.width(); } void EditProfileDialog::setDefaultCodec(const QByteArray &codec) { QString name = QString::fromLocal8Bit(codec); updateTempProfileProperty(Profile::DefaultEncoding, name); _advancedUi->selectEncodingButton->setText(name); } void EditProfileDialog::wordCharactersChanged(const QString &text) { updateTempProfileProperty(Profile::WordCharacters, text); } void EditProfileDialog::togglebidiRendering(bool enable) { updateTempProfileProperty(Profile::BidiRenderingEnabled, enable); _appearanceUi->enableBidiTableDirOverrideButton->setEnabled(enable); _appearanceUi->bidiLineLTR->setEnabled(enable); } void EditProfileDialog::togglebidiTableDirOverride(bool enable) { updateTempProfileProperty(Profile::BidiTableDirOverride, enable); } void EditProfileDialog::togglebidiLineLTR(bool enable) { updateTempProfileProperty(Profile::BidiLineLTR, enable); } void EditProfileDialog::toggleUnderlineLinks(bool enable) { updateTempProfileProperty(Profile::UnderlineLinksEnabled, enable); bool enableClick = _mouseUi->underlineFilesButton->isChecked() || enable; _mouseUi->openLinksByDirectClickButton->setEnabled(enableClick); } void EditProfileDialog::toggleUnderlineFiles(bool enable) { updateTempProfileProperty(Profile::UnderlineFilesEnabled, enable); bool enableClick = _mouseUi->underlineLinksButton->isChecked() || enable; _mouseUi->openLinksByDirectClickButton->setEnabled(enableClick); } void EditProfileDialog::textEditorCmdEditLineChanged(const QString &text) { updateTempProfileProperty(Profile::TextEditorCmd, text); } void EditProfileDialog::toggleCtrlRequiredForDrag(bool enable) { updateTempProfileProperty(Profile::CtrlRequiredForDrag, enable); } void EditProfileDialog::toggleDropUrlsAsText(bool enable) { updateTempProfileProperty(Profile::DropUrlsAsText, enable); } void EditProfileDialog::toggleOpenLinksByDirectClick(bool enable) { updateTempProfileProperty(Profile::OpenLinksByDirectClickEnabled, enable); } void EditProfileDialog::toggleCopyTextAsHTML(bool enable) { updateTempProfileProperty(Profile::CopyTextAsHTML, enable); } void EditProfileDialog::toggleCopyTextToClipboard(bool enable) { updateTempProfileProperty(Profile::AutoCopySelectedText, enable); } void EditProfileDialog::toggleTrimLeadingSpacesInSelectedText(bool enable) { updateTempProfileProperty(Profile::TrimLeadingSpacesInSelectedText, enable); } void EditProfileDialog::toggleTrimTrailingSpacesInSelectedText(bool enable) { updateTempProfileProperty(Profile::TrimTrailingSpacesInSelectedText, enable); } void EditProfileDialog::pasteFromX11Selection() { updateTempProfileProperty(Profile::MiddleClickPasteMode, Enum::PasteFromX11Selection); } void EditProfileDialog::pasteFromClipboard() { updateTempProfileProperty(Profile::MiddleClickPasteMode, Enum::PasteFromClipboard); } void EditProfileDialog::TripleClickModeChanged(int newValue) { updateTempProfileProperty(Profile::TripleClickMode, newValue); } void EditProfileDialog::updateUrlHintsModifier(bool) { Qt::KeyboardModifiers modifiers; if (_advancedUi->urlHintsModifierShift->isChecked()) { modifiers |= Qt::ShiftModifier; } if (_advancedUi->urlHintsModifierCtrl->isChecked()) { modifiers |= Qt::ControlModifier; } if (_advancedUi->urlHintsModifierAlt->isChecked()) { modifiers |= Qt::AltModifier; } if (_advancedUi->urlHintsModifierMeta->isChecked()) { modifiers |= Qt::MetaModifier; } updateTempProfileProperty(Profile::UrlHintsModifiers, int(modifiers)); } void EditProfileDialog::toggleReverseUrlHints(bool enable) { updateTempProfileProperty(Profile::ReverseUrlHints, enable); } void EditProfileDialog::toggleBlinkingText(bool enable) { updateTempProfileProperty(Profile::BlinkingTextEnabled, enable); } void EditProfileDialog::toggleFlowControl(bool enable) { updateTempProfileProperty(Profile::FlowControlEnabled, enable); } void EditProfileDialog::setAutoSaveInterval(int newVal) { updateTempProfileProperty(Profile::AutoSaveInterval, newVal); } void EditProfileDialog::peekPrimaryKeySequenceChanged() { updateTempProfileProperty(Profile::PeekPrimaryKeySequence, _advancedUi->peekPrimaryWidget->keySequence().toString()); } void EditProfileDialog::toggleWordMode(bool mode) { updateTempProfileProperty(Profile::WordMode, mode); _appearanceUi->wordModeAttr->setEnabled(mode); _appearanceUi->wordModeAscii->setEnabled(mode); _appearanceUi->wordModeBrahmic->setEnabled(mode); } void EditProfileDialog::toggleWordModeAttr(bool mode) { updateTempProfileProperty(Profile::WordModeAttr, mode); } void EditProfileDialog::toggleWordModeAscii(bool mode) { updateTempProfileProperty(Profile::WordModeAscii, mode); } void EditProfileDialog::toggleWordModeBrahmic(bool mode) { updateTempProfileProperty(Profile::WordModeBrahmic, mode); } void EditProfileDialog::toggleIgnoreWcWidth(bool ignore) { updateTempProfileProperty(Profile::IgnoreWcWidth, ignore); } #include "moc_EditProfileDialog.cpp"