iter(_tempProfile->setProperties());
while (iter.hasNext()) {
iter.next();
_previewedProperties.remove(iter.key());
}
createTempProfile();
_buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
}
void EditProfileDialog::reject()
{
unpreviewAll();
QDialog::reject();
}
void EditProfileDialog::accept()
{
// if the Apply button is disabled then no settings were changed
// or the changes have already been saved by apply()
if (_buttonBox->button(QDialogButtonBox::Apply)->isEnabled()) {
if (!isValidProfileName()) {
return;
}
save();
}
unpreviewAll();
QDialog::accept();
}
void EditProfileDialog::apply()
{
if (!isValidProfileName()) {
return;
}
save();
}
bool EditProfileDialog::isValidProfileName()
{
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()) {
if (!_tempProfile->isPropertySet(Profile::Name)
|| (_tempProfile->name() == _profile->name())) {
KMessageBox::sorry(this,
i18n("Konsole does not have permission to save this profile to:
\"%1\"
"
"To be able to save settings you can either change the permissions "
"of the profile configuration file or change the profile 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())) {
KMessageBox::sorry(this,
i18n("Each profile must have a name before it can be saved "
"into disk.
"));
// revert the name in the dialog
_generalUi->profileNameEdit->setText(_profile->name());
selectProfileName();
return false;
} else if (!_tempProfile->name().isEmpty() && otherExistingProfileNames.contains(_tempProfile->name())) {
KMessageBox::sorry(this,
i18n("A profile with this name already exists.
"));
// revert the name in the dialog
_generalUi->profileNameEdit->setText(_profile->name());
selectProfileName();
return false;
} else {
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));
} else {
setWindowTitle(i18n("Edit Profile \"%1\"", profile->name()));
}
}
void EditProfileDialog::setProfile(const Konsole::Profile::Ptr &profile)
{
Q_ASSERT(profile);
_profile = profile;
// 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());
if (_tempProfile) {
createTempProfile();
}
}
const Profile::Ptr EditProfileDialog::lookupProfile() const
{
return _profile;
}
const QString EditProfileDialog::currentColorSchemeName() const
{
const QString ¤tColorSchemeName = lookupProfile()->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 = lookupProfile();
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)
{
// 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->startInSameDirButton->setChecked(profile->startInCurrentSessionDir());
// initial terminal size
const auto colsSuffix = ki18ncp("Suffix of the number of columns (N columns)", " column", " columns");
const auto rowsSuffix = ki18ncp("Suffix of the number of rows (N rows)", " 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);
// 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->terminalColumnsEntry,
QOverload::of(&QSpinBox::valueChanged), this,
&Konsole::EditProfileDialog::terminalColumnsEntryChanged);
connect(_generalUi->terminalRowsEntry,
QOverload::of(&QSpinBox::valueChanged), this,
&Konsole::EditProfileDialog::terminalRowsEntryChanged);
}
void EditProfileDialog::showEnvironmentEditor()
{
bool ok;
const Profile::Ptr profile = lookupProfile();
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());
connect(_tabsUi->renameTabWidget, &Konsole::RenameTabWidget::tabTitleFormatChanged, this,
&Konsole::EditProfileDialog::tabTitleFormatChanged);
connect(_tabsUi->renameTabWidget, &Konsole::RenameTabWidget::remoteTabTitleFormatChanged, this,
&Konsole::EditProfileDialog::remoteTabTitleFormatChanged);
// 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,
QOverload::of(&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::tabTitleFormatChanged(const QString &format)
{
updateTempProfileProperty(Profile::LocalTabTitleFormat, format);
}
void EditProfileDialog::remoteTabTitleFormatChanged(const QString &format)
{
updateTempProfileProperty(Profile::RemoteTabTitleFormat, format);
}
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::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 delegate = new ColorSchemeViewDelegate(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->setEnabled(false);
_appearanceUi->removeColorSchemeButton->setEnabled(false);
_appearanceUi->resetColorSchemeButton->setEnabled(false);
_appearanceUi->downloadColorSchemeButton->setConfigFile(QStringLiteral("konsole.knsrc"));
// setup color list
// select the colorScheme used in the current profile
updateColorSchemeList(currentColorSchemeName());
_appearanceUi->colorSchemeList->setMouseTracking(true);
_appearanceUi->colorSchemeList->installEventFilter(this);
_appearanceUi->colorSchemeList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
connect(_appearanceUi->colorSchemeList->selectionModel(),
&QItemSelectionModel::selectionChanged, this,
&Konsole::EditProfileDialog::colorSchemeSelected);
connect(_appearanceUi->colorSchemeList, &QListView::entered, 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->downloadColorSchemeButton, &KNS3::Button::dialogFinished, this,
&Konsole::EditProfileDialog::gotNewColorSchemes);
connect(_appearanceUi->resetColorSchemeButton, &QPushButton::clicked, this,
&Konsole::EditProfileDialog::resetColorScheme);
connect(_appearanceUi->chooseFontButton, &QAbstractButton::clicked, this,
&EditProfileDialog::showFontDialog);
// 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()));
// 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);
_mouseUi->enableMouseWheelZoomButton->setChecked(profile->mouseWheelZoomEnabled());
connect(_mouseUi->enableMouseWheelZoomButton, &QCheckBox::toggled, this,
&Konsole::EditProfileDialog::toggleMouseWheelZoom);
// cursor options
const auto options = QVector{
{
_appearanceUi->enableBlinkingCursorButton, Profile::BlinkingCursorEnabled,
SLOT(toggleBlinkingCursor(bool))
},
};
setupCheckBoxes(options, profile);
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);
-
- // Make radio buttons height equal
- int cursorColorRadioHeight = qMax(_appearanceUi->autoCursorColorButton->minimumSizeHint().height(),
- _appearanceUi->customColorSelectButton->minimumSizeHint().height());
- _appearanceUi->autoCursorColorButton->setMinimumHeight(cursorColorRadioHeight);
- _appearanceUi->customCursorColorButton->setMinimumHeight(cursorColorRadioHeight);
+ 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,
QOverload::of(&QSpinBox::valueChanged), this,
&Konsole::EditProfileDialog::terminalMarginChanged);
_appearanceUi->lineSpacingSpinner->setValue(profile->lineSpacing());
connect(_appearanceUi->lineSpacingSpinner,
QOverload::of(&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);
}
void EditProfileDialog::setAntialiasText(bool enable)
{
preview(Profile::AntiAliasFonts, enable);
updateTempProfileProperty(Profile::AntiAliasFonts, enable);
const QFont font = _profile->font();
updateFontPreview(font);
}
void EditProfileDialog::setBoldIntense(bool enable)
{
preview(Profile::BoldIntense, enable);
updateTempProfileProperty(Profile::BoldIntense, enable);
}
void EditProfileDialog::useFontLineCharacters(bool enable)
{
preview(Profile::UseFontLineCharacters, enable);
updateTempProfileProperty(Profile::UseFontLineCharacters, enable);
}
void EditProfileDialog::toggleBlinkingCursor(bool enable)
{
preview(Profile::BlinkingCursorEnabled, enable);
updateTempProfileProperty(Profile::BlinkingCursorEnabled, 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::updateColorSchemeList(const QString &selectedColorSchemeName)
{
if (_appearanceUi->colorSchemeList->model() == nullptr) {
_appearanceUi->colorSchemeList->setModel(new QStandardItemModel(this));
}
const ColorScheme *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 ColorScheme *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);
}
}
bool EditProfileDialog::eventFilter(QObject *watched, QEvent *event)
{
if (watched == _appearanceUi->colorSchemeList && event->type() == QEvent::Leave) {
if (_tempProfile->isPropertySet(Profile::ColorScheme)) {
preview(Profile::ColorScheme, _tempProfile->colorScheme());
} else {
unpreview(Profile::ColorScheme);
}
}
return QDialog::eventFilter(watched, event);
}
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()
{
_delayedPreviewTimer->stop();
_delayedPreviewProperties.clear();
QHash map;
QHashIterator iter(_previewedProperties);
while (iter.hasNext()) {
iter.next();
map.insert(static_cast(iter.key()), iter.value());
}
// undo any preview changes
if (!map.isEmpty()) {
ProfileManager::instance()->changeProfile(_profile, map, false);
}
}
void EditProfileDialog::unpreview(int property)
{
_delayedPreviewProperties.remove(property);
if (!_previewedProperties.contains(property)) {
return;
}
QHash map;
map.insert(static_cast(property), _previewedProperties[property]);
ProfileManager::instance()->changeProfile(_profile, map, false);
_previewedProperties.remove(property);
}
void EditProfileDialog::delayedPreview(int property, const QVariant &value)
{
_delayedPreviewProperties.insert(property, value);
_delayedPreviewTimer->stop();
_delayedPreviewTimer->start(300);
}
void EditProfileDialog::delayedPreviewActivate()
{
Q_ASSERT(qobject_cast(sender()));
QMutableHashIterator iter(_delayedPreviewProperties);
if (iter.hasNext()) {
iter.next();
preview(iter.key(), iter.value());
}
}
void EditProfileDialog::preview(int property, const QVariant &value)
{
QHash map;
map.insert(static_cast(property), value);
_delayedPreviewProperties.remove(property);
const Profile::Ptr original = lookupProfile();
// 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(static_cast(property)).isNull()) {
return;
}
if (!_previewedProperties.contains(property)) {
_previewedProperties.insert(property,
original->property(static_cast(property)));
}
// temporary change to color scheme
ProfileManager::instance()->changeProfile(_profile, map, false);
}
void EditProfileDialog::previewColorScheme(const QModelIndex &index)
{
const QString &name = index.data(Qt::UserRole + 1).value()->name();
delayedPreview(Profile::ColorScheme, name);
}
void EditProfileDialog::showFontDialog()
{
if (_fontDialog == nullptr) {
_fontDialog = new FontDialog(this);
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);
const QFont font = _profile->font();
updateFontPreview(font);
});
}
const QFont font = _profile->font();
_fontDialog->setFont(font);
_fontDialog->exec();
}
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::removeColorScheme()
{
QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes();
if (selected.isEmpty()) {
return;
}
// The actual delete runs async because we need to on-demand query
// files managed by KNS. Deleting files managed by KNS screws up the
// KNS states (entry gets shown as installed when in fact we deleted it).
auto *manager = new KNSCore::DownloadManager(QStringLiteral("konsole.knsrc"), this);
connect(manager, &KNSCore::DownloadManager::searchResult,
this, [=](const KNSCore::EntryInternal::List &entries) {
const QString &name = selected.first().data(Qt::UserRole + 1).value()->name();
Q_ASSERT(!name.isEmpty());
bool uninstalled = false;
// Check if the theme was installed by KNS, if so uninstall it through
// there and unload it.
for (auto &entry : entries) {
for (const auto &file : entry.installedFiles()) {
if (ColorSchemeManager::colorSchemeNameFromPath(file) != name) {
continue;
}
// Make sure the manager can unload it before uninstalling it.
if (ColorSchemeManager::instance()->unloadColorScheme(file)) {
manager->uninstallEntry(entry);
uninstalled = true;
}
}
if (uninstalled) {
break;
}
}
// If KNS wasn't able to remove it is a custom theme and we'll drop
// it manually.
if (!uninstalled) {
uninstalled = ColorSchemeManager::instance()->deleteColorScheme(name);
}
if (uninstalled) {
_appearanceUi->colorSchemeList->model()->removeRow(selected.first().row());
}
manager->deleteLater();
});
manager->checkForInstalled();
}
void EditProfileDialog::gotNewColorSchemes(const KNS3::Entry::List &changedEntries)
{
int failures = 0;
for (auto &entry : qAsConst(changedEntries)) {
switch (entry.status()) {
case KNS3::Entry::Installed:
for (const auto &file : entry.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 KNS3::Entry::Deleted:
for (const auto &file : entry.uninstalledFiles()) {
if (ColorSchemeManager::instance()->unloadColorScheme(file)) {
continue;
}
qWarning() << "Failed to unload file" << file;
// If unloading fails we do not care. Iff the scheme failed here
// it either wasn't loaded or was invalid to begin with.
}
break;
case KNS3::Entry::Invalid:
case KNS3::Entry::Installing:
case KNS3::Entry::Downloadable:
case KNS3::Entry::Updateable:
case KNS3::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();
const ColorScheme *colors = nullptr;
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(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)
{
auto newScheme = new ColorScheme(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();
const auto *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 (!KWindowSystem::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::createTempProfile()
{
_tempProfile = Profile::Ptr(new Profile);
_tempProfile->setHidden(true);
}
void EditProfileDialog::updateTempProfileProperty(Profile::Property property, const QVariant &value)
{
_tempProfile->setProperty(property, value);
updateButtonApply();
}
void EditProfileDialog::updateButtonApply()
{
bool userModified = false;
QHashIterator iter(_tempProfile->setProperties());
while (iter.hasNext()) {
iter.next();
Profile::Property property = iter.key();
QVariant value = iter.value();
// for previewed property
if (_previewedProperties.contains(static_cast(property))) {
if (value != _previewedProperties.value(static_cast(property))) {
userModified = true;
break;
}
// 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))) {
userModified = true;
break;
}
}
_buttonBox->button(QDialogButtonBox::Apply)->setEnabled(userModified);
}
void EditProfileDialog::setupKeyboardPage(const Profile::Ptr &/* profile */)
{
// setup translator list
updateKeyBindingsList(lookupProfile()->keyBindings());
connect(_keyboardUi->keyBindingList->selectionModel(),
&QItemSelectionModel::selectionChanged, this,
&Konsole::EditProfileDialog::keyBindingSelected);
connect(_keyboardUi->newKeyBindingsButton, &QPushButton::clicked, this,
&Konsole::EditProfileDialog::newKeyBinding);
_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();
}
Q_ASSERT(translator);
auto editor = new KeyBindingEditor(this);
if (translator != nullptr) {
editor->setup(translator, lookupProfile()->keyBindings(), isNewTranslator);
}
connect(editor, &Konsole::KeyBindingEditor::updateKeyBindingsListRequest,
this, &Konsole::EditProfileDialog::updateKeyBindingsList);
connect(editor, &Konsole::KeyBindingEditor::updateTempProfileKeyBindingsRequest,
this, &Konsole::EditProfileDialog::updateTempProfileProperty);
editor->exec();
}
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::setupCheckBoxes(const QVector& options, const Profile::Ptr &profile)
{
for(const auto& option : options) {
option.button->setChecked(profile->property(option.property));
connect(option.button, SIGNAL(toggled(bool)), this, option.slot);
}
}
void EditProfileDialog::setupRadio(const QVector& possibilities, int actual)
{
for(const auto& possibility : possibilities) {
possibility.button->setChecked(possibility.value == actual);
connect(possibility.button, SIGNAL(clicked()), this, possibility.slot);
}
}
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, QOverload::of(&QButtonGroup::buttonClicked),
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);
const auto pageamounts = QVector{
{_scrollingUi->scrollHalfPage, Enum::ScrollPageHalf, SLOT(scrollHalfPage())},
{_scrollingUi->scrollFullPage, Enum::ScrollPageFull, SLOT(scrollFullPage())}
};
setupRadio(pageamounts, scrollFullPage);
// 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::setupMousePage(const Profile::Ptr &profile)
{
const auto options = QVector{
{
_mouseUi->underlineLinksButton, Profile::UnderlineLinksEnabled,
SLOT(toggleUnderlineLinks(bool))
},
{
_mouseUi->underlineFilesButton, Profile::UnderlineFilesEnabled,
SLOT(toggleUnderlineFiles(bool))
},
{
_mouseUi->ctrlRequiredForDragButton, Profile::CtrlRequiredForDrag,
SLOT(toggleCtrlRequiredForDrag(bool))
},
{
_mouseUi->copyTextAsHTMLButton, Profile::CopyTextAsHTML,
SLOT(toggleCopyTextAsHTML(bool))
},
{
_mouseUi->copyTextToClipboardButton, Profile::AutoCopySelectedText,
SLOT(toggleCopyTextToClipboard(bool))
},
{
_mouseUi->trimLeadingSpacesButton, Profile::TrimLeadingSpacesInSelectedText,
SLOT(toggleTrimLeadingSpacesInSelectedText(bool))
},
{
_mouseUi->trimTrailingSpacesButton, Profile::TrimTrailingSpacesInSelectedText,
SLOT(toggleTrimTrailingSpacesInSelectedText(bool))
},
{
_mouseUi->openLinksByDirectClickButton, Profile::OpenLinksByDirectClickEnabled,
SLOT(toggleOpenLinksByDirectClick(bool))
},
{
_mouseUi->dropUrlsAsText, Profile::DropUrlsAsText,
SLOT(toggleDropUrlsAsText(bool))
},
{
_mouseUi->enableAlternateScrollingButton, Profile::AlternateScrolling,
SLOT(toggleAlternateScrolling(bool))
}
};
setupCheckBoxes(options, profile);
// setup middle click paste mode
const auto middleClickPasteMode = profile->property(Profile::MiddleClickPasteMode);
const auto pasteModes = QVector {
{_mouseUi->pasteFromX11SelectionButton, Enum::PasteFromX11Selection, SLOT(pasteFromX11Selection())},
{_mouseUi->pasteFromClipboardButton, Enum::PasteFromClipboard, SLOT(pasteFromClipboard())} };
setupRadio(pasteModes, middleClickPasteMode);
// 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);
}
void EditProfileDialog::setupAdvancedPage(const Profile::Ptr &profile)
{
const auto options = QVector{
{
_advancedUi->enableBlinkingTextButton, Profile::BlinkingTextEnabled,
SLOT(toggleBlinkingText(bool))
},
{
_advancedUi->enableFlowControlButton, Profile::FlowControlEnabled,
SLOT(toggleFlowControl(bool))
},
{
_appearanceUi->enableBlinkingCursorButton, Profile::BlinkingCursorEnabled,
SLOT(toggleBlinkingCursor(bool))
},
{
_advancedUi->enableBidiRenderingButton, Profile::BidiRenderingEnabled,
SLOT(togglebidiRendering(bool))
},
{
_advancedUi->enableReverseUrlHints, Profile::ReverseUrlHints,
SLOT(toggleReverseUrlHints(bool))
}
};
setupCheckBoxes(options, profile);
// 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);
}
// encoding options
auto codecAction = new KCodecAction(this);
_advancedUi->selectEncodingButton->setMenu(codecAction->menu());
connect(codecAction,
QOverload::of(&KCodecAction::triggered), this,
&Konsole::EditProfileDialog::setDefaultCodec);
_advancedUi->selectEncodingButton->setText(profile->defaultEncoding());
}
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)
.expandedTo(QApplication::globalStrut());
return spinBoxSize.width();
}
void EditProfileDialog::setDefaultCodec(QTextCodec *codec)
{
QString name = QString::fromLocal8Bit(codec->name());
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);
}
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::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);
}
ColorSchemeViewDelegate::ColorSchemeViewDelegate(QObject *parent) :
QAbstractItemDelegate(parent)
{
}
void ColorSchemeViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
const auto *scheme = index.data(Qt::UserRole + 1).value();
QFont profileFont = index.data(Qt::UserRole + 2).value();
Q_ASSERT(scheme);
if (scheme == nullptr) {
return;
}
painter->setRenderHint(QPainter::Antialiasing);
// Draw background
QStyle *style = option.widget != nullptr ? option.widget->style() : QApplication::style();
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, option.widget);
// Draw name
QPalette::ColorRole textColor = ((option.state & QStyle::State_Selected) != 0)
? QPalette::HighlightedText : QPalette::Text;
painter->setPen(option.palette.color(textColor));
painter->setFont(option.font);
// Determine width of sample text using profile's font
const QString sampleText = i18n("AaZz09...");
QFontMetrics profileFontMetrics(profileFont);
const int sampleTextWidth = profileFontMetrics.boundingRect(sampleText).width();
painter->drawText(option.rect.adjusted(sampleTextWidth + 15, 0, 0, 0),
Qt::AlignLeft | Qt::AlignVCenter,
index.data(Qt::DisplayRole).toString());
// Draw the preview
const int x = option.rect.left();
const int y = option.rect.top();
QRect previewRect(x + 4, y + 4, sampleTextWidth + 8, option.rect.height() - 8);
bool transparencyAvailable = KWindowSystem::compositingActive();
if (transparencyAvailable) {
painter->save();
QColor color = scheme->backgroundColor();
color.setAlphaF(scheme->opacity());
painter->setPen(Qt::NoPen);
painter->setCompositionMode(QPainter::CompositionMode_Source);
painter->setBrush(color);
painter->drawRect(previewRect);
painter->restore();
} else {
painter->setPen(Qt::NoPen);
painter->setBrush(scheme->backgroundColor());
painter->drawRect(previewRect);
}
// draw color scheme name using scheme's foreground color
QPen pen(scheme->foregroundColor());
painter->setPen(pen);
// TODO: respect antialias setting
painter->setFont(profileFont);
painter->drawText(previewRect, Qt::AlignCenter, sampleText);
}
QSize ColorSchemeViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex & /*index*/) const
{
const int width = 200;
const int margin = 5;
const int colorWidth = width / TABLE_COLORS;
const int heightForWidth = (colorWidth * 2) + option.fontMetrics.height() + margin;
// temporary
return {width, heightForWidth};
}
diff --git a/src/EditProfileDialog.h b/src/EditProfileDialog.h
index f20727f8..959ddb77 100644
--- a/src/EditProfileDialog.h
+++ b/src/EditProfileDialog.h
@@ -1,471 +1,472 @@
/*
Copyright 2007-2008 by Robert Knight
Copyright 2018 by Harald Sitter
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
*/
#ifndef EDITPROFILEDIALOG_H
#define EDITPROFILEDIALOG_H
// Qt
#include
#include
#include
#include
#include
// KDE
#include
#include
// Konsole
#include "Profile.h"
#include "Enumeration.h"
#include "ColorScheme.h"
#include "ColorSchemeEditor.h"
#include "KeyboardTranslatorManager.h"
#include "FontDialog.h"
class KPluralHandlingSpinBox;
class KLocalizedString;
class QItemSelectionModel;
namespace Ui {
class EditProfileGeneralPage;
class EditProfileTabsPage;
class EditProfileAppearancePage;
class EditProfileScrollingPage;
class EditProfileKeyboardPage;
class EditProfileMousePage;
class EditProfileAdvancedPage;
}
namespace Konsole {
/**
* A dialog which allows the user to edit a profile.
* After the dialog is created, it can be initialized with the settings
* for a profile using setProfile(). When the user makes changes to the
* dialog and accepts the changes, the dialog will update the
* profile in the SessionManager by calling the SessionManager's
* changeProfile() method.
*
* Some changes made in the dialog are preview-only changes which cause
* the SessionManager's changeProfile() method to be called with
* the persistent argument set to false. These changes are then
* un-done when the dialog is closed.
*/
class KONSOLEPRIVATE_EXPORT EditProfileDialog: public KPageDialog
{
Q_OBJECT
public:
/** Constructs a new dialog with the specified parent. */
explicit EditProfileDialog(QWidget *parent = nullptr);
~EditProfileDialog() override;
/**
* Initializes the dialog with the settings for the specified session
* type.
*
* When the dialog closes, the profile will be updated in the SessionManager
* with the altered settings.
*
* @param profile The profile to be edited
*/
void setProfile(const Profile::Ptr &profile);
/**
* Selects the text in the profile name edit area.
* When the dialog is being used to create a new profile,
* this can be used to draw the user's attention to the profile name
* and make it easy for them to change it.
*/
void selectProfileName();
const Profile::Ptr lookupProfile() const;
public Q_SLOTS:
// reimplemented
void accept() override;
// reimplemented
void reject() override;
void apply();
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private Q_SLOTS:
QSize sizeHint() const override;
// sets up the specified tab page if necessary
void preparePage(KPageWidgetItem *current, KPageWidgetItem *before = nullptr);
// saves changes to profile
void save();
// general page
void selectInitialDir();
void selectIcon();
void profileNameChanged(const QString &name);
void initialDirChanged(const QString &dir);
void startInSameDir(bool);
void commandChanged(const QString &command);
void tabTitleFormatChanged(const QString &format);
void remoteTabTitleFormatChanged(const QString &format);
void terminalColumnsEntryChanged(int);
void terminalRowsEntryChanged(int);
void showTerminalSizeHint(bool);
void setDimWhenInactive(bool);
void showEnvironmentEditor();
void silenceSecondsChanged(int);
// appearance page
void setAntialiasText(bool enable);
void setBoldIntense(bool enable);
void useFontLineCharacters(bool enable);
void newColorScheme();
void editColorScheme();
void saveColorScheme(const ColorScheme &scheme, bool isNewScheme);
void removeColorScheme();
void gotNewColorSchemes(const KNS3::Entry::List &changedEntries);
void toggleBlinkingCursor(bool);
void setCursorShape(int);
void autoCursorColor();
void customCursorColor();
void customCursorColorChanged(const QColor &);
+ void customCursorTextColorChanged(const QColor &);
void terminalMarginChanged(int margin);
void lineSpacingChanged(int);
void setTerminalCenter(bool enable);
/**
* Deletes the selected colorscheme from the user's home dir location
* so that the original one from the system-wide location can be used
* instead
*/
void resetColorScheme();
void colorSchemeSelected();
void previewColorScheme(const QModelIndex &index);
void showFontDialog();
void toggleMouseWheelZoom(bool enable);
// scrolling page
void historyModeChanged(Enum::HistoryModeEnum mode);
void historySizeChanged(int);
void scrollFullPage();
void scrollHalfPage();
// keyboard page
void editKeyBinding();
void newKeyBinding();
void keyBindingSelected();
void removeKeyBinding();
void resetKeyBindings();
// mouse page
void toggleUnderlineFiles(bool enable);
void toggleUnderlineLinks(bool);
void toggleOpenLinksByDirectClick(bool);
void toggleCtrlRequiredForDrag(bool);
void toggleDropUrlsAsText(bool);
void toggleCopyTextToClipboard(bool);
void toggleCopyTextAsHTML(bool);
void toggleTrimLeadingSpacesInSelectedText(bool);
void toggleTrimTrailingSpacesInSelectedText(bool);
void pasteFromX11Selection();
void pasteFromClipboard();
void toggleAlternateScrolling(bool enable);
void TripleClickModeChanged(int);
void wordCharactersChanged(const QString &);
// advanced page
void toggleBlinkingText(bool);
void toggleFlowControl(bool);
void togglebidiRendering(bool);
void updateUrlHintsModifier(bool);
void toggleReverseUrlHints(bool);
void setDefaultCodec(QTextCodec *);
// apply the first previewed changes stored up by delayedPreview()
void delayedPreviewActivate();
private:
Q_DISABLE_COPY(EditProfileDialog)
enum PageID {
GeneralPage = 0,
TabsPage,
AppearancePage,
ScrollingPage,
KeyboardPage,
MousePage,
AdvancedPage,
PagesCount
};
// initialize various pages of the dialog
void setupGeneralPage(const Profile::Ptr &profile);
void setupTabsPage(const Profile::Ptr &profile);
void setupAppearancePage(const Profile::Ptr &profile);
void setupKeyboardPage(const Profile::Ptr &profile);
void setupScrollingPage(const Profile::Ptr &profile);
void setupAdvancedPage(const Profile::Ptr &profile);
void setupMousePage(const Profile::Ptr &profile);
int maxSpinBoxWidth(const KPluralHandlingSpinBox *spinBox, const KLocalizedString &suffix);
// Returns the name of the colorScheme used in the current profile
const QString currentColorSchemeName() const;
// select @p selectedColorSchemeName after the changes are saved
// in the colorScheme editor
void updateColorSchemeList(const QString &selectedColorSchemeName = QString());
void updateColorSchemeButtons();
// Convenience method
KeyboardTranslatorManager *_keyManager = KeyboardTranslatorManager::instance();
// Updates the key bindings list widget on the Keyboard tab and selects
// @p selectKeyBindingsName
void updateKeyBindingsList(const QString &selectKeyBindingsName = QString());
void updateKeyBindingsButtons();
void showKeyBindingEditor(bool isNewTranslator);
void showColorSchemeEditor(bool isNewScheme);
void closeColorSchemeEditor();
void preview(int property, const QVariant &value);
void delayedPreview(int property, const QVariant &value);
void unpreview(int property);
void unpreviewAll();
void enableIfNonEmptySelection(QWidget *widget, QItemSelectionModel *selectionModel);
void updateCaption(const Profile::Ptr &profile);
void updateTransparencyWarning();
void updateFontPreview(QFont font);
// Update _tempProfile in a way of respecting the apply button.
// When used with some previewed property, this method should
// always come after the preview operation.
void updateTempProfileProperty(Profile::Property, const QVariant &value);
// helper method for creating an empty & hidden profile and assigning
// it to _tempProfile.
void createTempProfile();
// Enable or disable apply button, used only within
// updateTempProfileProperty().
void updateButtonApply();
static QString groupProfileNames(const ProfileGroup::Ptr &group, int maxLength = -1);
struct RadioOption {
QAbstractButton *button;
int value;
const char *slot;
};
void setupRadio(const QVector& possibilities, int actual);
struct BooleanOption {
QAbstractButton *button;
Profile::Property property;
const char *slot;
};
void setupCheckBoxes(const QVector& options, const Profile::Ptr &profile);
struct ButtonGroupOption {
QAbstractButton *button;
int value;
};
struct ButtonGroupOptions {
QButtonGroup *group;
Profile::Property profileProperty;
bool preview;
QVector buttons;
};
void setupButtonGroup(const ButtonGroupOptions &options, const Profile::Ptr &profile);
// returns false if:
// - the profile name is empty
// - the name matches the name of an already existing profile
// - the existing profile config file is read-only
// otherwise returns true.
bool isValidProfileName();
Ui::EditProfileGeneralPage *_generalUi;
Ui::EditProfileTabsPage *_tabsUi;
Ui::EditProfileAppearancePage *_appearanceUi;
Ui::EditProfileScrollingPage *_scrollingUi;
Ui::EditProfileKeyboardPage *_keyboardUi;
Ui::EditProfileMousePage *_mouseUi;
Ui::EditProfileAdvancedPage *_advancedUi;
using PageSetupMethod = void (EditProfileDialog::*)(const Profile::Ptr &);
struct Page {
Page(PageSetupMethod page = nullptr, bool update = false)
: setupPage(page)
, needsUpdate(update)
{}
PageSetupMethod setupPage;
bool needsUpdate;
};
QMap _pages;
Profile::Ptr _tempProfile;
Profile::Ptr _profile;
QHash _previewedProperties;
QHash _delayedPreviewProperties;
QTimer *_delayedPreviewTimer;
ColorSchemeEditor *_colorDialog;
QDialogButtonBox *_buttonBox;
FontDialog *_fontDialog;
};
/**
* A delegate which can display and edit color schemes in a view.
*/
class ColorSchemeViewDelegate : public QAbstractItemDelegate
{
Q_OBJECT
public:
explicit ColorSchemeViewDelegate(QObject *parent = nullptr);
// reimplemented
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
};
/**
* An utility class for aligning 0th column in multiple QGridLayouts.
*
* Limitations:
* - a layout can't be nested in another layout
* - reference widget must be an ancestor of all added layouts
* - only 0th column is processed (widgets spanning multiple columns
* are ignored)
*/
class LabelsAligner: public QObject {
Q_OBJECT
public:
explicit LabelsAligner(QWidget *refWidget): _refWidget(refWidget) {}
void addLayout(QGridLayout *layout) { _layouts.append(layout); }
void addLayouts(const QVector &layouts) { _layouts.append(layouts); }
void setReferenceWidget(QWidget *refWidget) { _refWidget = refWidget; }
public Q_SLOTS:
void updateLayouts() {
for (const auto *layout: qAsConst(_layouts)) {
QWidget *widget = layout->parentWidget();
Q_ASSERT(widget);
do {
QLayout *widgetLayout = widget->layout();
if (widgetLayout != nullptr) {
widgetLayout->update();
widgetLayout->activate();
}
widget = widget->parentWidget();
} while (widget != _refWidget && widget != nullptr);
}
}
void align() {
Q_ASSERT(_refWidget);
if (_layouts.count() <= 1) {
return;
}
int maxRight = 0;
for (const auto *layout: qAsConst(_layouts)) {
int left = getLeftMargin(layout);
for (int row = 0; row < layout->rowCount(); ++row) {
QLayoutItem *layoutItem = layout->itemAtPosition(row, LABELS_COLUMN);
if (layoutItem == nullptr) {
continue;
}
QWidget *widget = layoutItem->widget();
if (widget == nullptr) {
continue;
}
const int idx = layout->indexOf(widget);
int rows, cols, rowSpan, colSpan;
layout->getItemPosition(idx, &rows, &cols, &rowSpan, &colSpan);
if (colSpan > 1) {
continue;
}
const int right = left + widget->sizeHint().width();
if (maxRight < right) {
maxRight = right;
}
}
}
for (auto *l: qAsConst(_layouts)) {
int left = getLeftMargin(l);
l->setColumnMinimumWidth(LABELS_COLUMN, maxRight - left);
}
}
private:
int getLeftMargin(const QGridLayout *layout) {
int left = layout->contentsMargins().left();
if (layout->parent()->isWidgetType()) {
auto *parentWidget = layout->parentWidget();
Q_ASSERT(parentWidget);
left += parentWidget->contentsMargins().left();
} else {
auto *parentLayout = qobject_cast(layout->parent());
Q_ASSERT(parentLayout);
left += parentLayout->contentsMargins().left();
}
QWidget *parent = layout->parentWidget();
while (parent != _refWidget && parent != nullptr) {
left = parent->mapToParent(QPoint(left, 0)).x();
parent = parent->parentWidget();
}
return left;
}
static constexpr int LABELS_COLUMN = 0;
QWidget *_refWidget;
QVector _layouts;
};
}
#endif // EDITPROFILEDIALOG_H
diff --git a/src/Profile.cpp b/src/Profile.cpp
index 63a7376b..1cf3eb78 100644
--- a/src/Profile.cpp
+++ b/src/Profile.cpp
@@ -1,395 +1,397 @@
/*
This source file is part of Konsole, a terminal emulator.
Copyright 2006-2008 by Robert Knight
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
*/
// Own
#include "Profile.h"
// Qt
#include
#include
// KDE
#include
#include
// Konsole
#include "Enumeration.h"
using namespace Konsole;
// mappings between property enum values and names
//
// multiple names are defined for some property values,
// in these cases, the "proper" string name comes first,
// as that is used when reading/writing profiles from/to disk
//
// the other names are usually shorter versions for convenience
// when parsing konsoleprofile commands
static const char GENERAL_GROUP[] = "General";
static const char KEYBOARD_GROUP[] = "Keyboard";
static const char APPEARANCE_GROUP[] = "Appearance";
static const char SCROLLING_GROUP[] = "Scrolling";
static const char TERMINAL_GROUP[] = "Terminal Features";
static const char CURSOR_GROUP[] = "Cursor Options";
static const char INTERACTION_GROUP[] = "Interaction Options";
static const char ENCODING_GROUP[] = "Encoding Options";
const Profile::PropertyInfo Profile::DefaultPropertyNames[] = {
// General
{ Path , "Path" , nullptr , QVariant::String }
, { Name , "Name" , GENERAL_GROUP , QVariant::String }
, { UntranslatedName, "UntranslatedName" , nullptr , QVariant::String }
, { Icon , "Icon" , GENERAL_GROUP , QVariant::String }
, { Command , "Command" , nullptr , QVariant::String }
, { Arguments , "Arguments" , nullptr , QVariant::StringList }
, { MenuIndex, "MenuIndex" , nullptr, QVariant::String }
, { Environment , "Environment" , GENERAL_GROUP , QVariant::StringList }
, { Directory , "Directory" , GENERAL_GROUP , QVariant::String }
, { LocalTabTitleFormat , "LocalTabTitleFormat" , GENERAL_GROUP , QVariant::String }
, { LocalTabTitleFormat , "tabtitle" , nullptr , QVariant::String }
, { RemoteTabTitleFormat , "RemoteTabTitleFormat" , GENERAL_GROUP , QVariant::String }
, { ShowTerminalSizeHint , "ShowTerminalSizeHint" , GENERAL_GROUP , QVariant::Bool }
, { DimWhenInactive , "DimWhenInactive" , GENERAL_GROUP , QVariant::Bool }
, { StartInCurrentSessionDir , "StartInCurrentSessionDir" , GENERAL_GROUP , QVariant::Bool }
, { SilenceSeconds, "SilenceSeconds" , GENERAL_GROUP , QVariant::Int }
, { TerminalColumns, "TerminalColumns" , GENERAL_GROUP , QVariant::Int }
, { TerminalRows, "TerminalRows" , GENERAL_GROUP , QVariant::Int }
, { TerminalMargin, "TerminalMargin" , GENERAL_GROUP , QVariant::Int }
, { TerminalCenter, "TerminalCenter" , GENERAL_GROUP , QVariant::Bool }
// Appearance
, { Font , "Font" , APPEARANCE_GROUP , QVariant::Font }
, { ColorScheme , "ColorScheme" , APPEARANCE_GROUP , QVariant::String }
, { ColorScheme , "colors" , nullptr , QVariant::String }
, { AntiAliasFonts, "AntiAliasFonts" , APPEARANCE_GROUP , QVariant::Bool }
, { BoldIntense, "BoldIntense", APPEARANCE_GROUP, QVariant::Bool }
, { UseFontLineCharacters, "UseFontLineChararacters", APPEARANCE_GROUP, QVariant::Bool }
, { LineSpacing , "LineSpacing" , APPEARANCE_GROUP , QVariant::Int }
// Keyboard
, { KeyBindings , "KeyBindings" , KEYBOARD_GROUP , QVariant::String }
// Scrolling
, { HistoryMode , "HistoryMode" , SCROLLING_GROUP , QVariant::Int }
, { HistorySize , "HistorySize" , SCROLLING_GROUP , QVariant::Int }
, { ScrollBarPosition , "ScrollBarPosition" , SCROLLING_GROUP , QVariant::Int }
, { ScrollFullPage , "ScrollFullPage" , SCROLLING_GROUP , QVariant::Bool }
// Terminal Features
, { UrlHintsModifiers , "UrlHintsModifiers" , TERMINAL_GROUP , QVariant::Int }
, { ReverseUrlHints , "ReverseUrlHints" , TERMINAL_GROUP , QVariant::Bool }
, { BlinkingTextEnabled , "BlinkingTextEnabled" , TERMINAL_GROUP , QVariant::Bool }
, { FlowControlEnabled , "FlowControlEnabled" , TERMINAL_GROUP , QVariant::Bool }
, { BidiRenderingEnabled , "BidiRenderingEnabled" , TERMINAL_GROUP , QVariant::Bool }
, { BlinkingCursorEnabled , "BlinkingCursorEnabled" , TERMINAL_GROUP , QVariant::Bool }
, { BellMode , "BellMode" , TERMINAL_GROUP , QVariant::Int }
// Cursor
, { UseCustomCursorColor , "UseCustomCursorColor" , CURSOR_GROUP , QVariant::Bool}
, { CursorShape , "CursorShape" , CURSOR_GROUP , QVariant::Int}
, { CustomCursorColor , "CustomCursorColor" , CURSOR_GROUP , QVariant::Color }
+ , { CustomCursorTextColor , "CustomCursorTextColor" , CURSOR_GROUP , QVariant::Color }
// Interaction
, { WordCharacters , "WordCharacters" , INTERACTION_GROUP , QVariant::String }
, { TripleClickMode , "TripleClickMode" , INTERACTION_GROUP , QVariant::Int }
, { UnderlineLinksEnabled , "UnderlineLinksEnabled" , INTERACTION_GROUP , QVariant::Bool }
, { UnderlineFilesEnabled , "UnderlineFilesEnabled" , INTERACTION_GROUP , QVariant::Bool }
, { OpenLinksByDirectClickEnabled , "OpenLinksByDirectClickEnabled" , INTERACTION_GROUP , QVariant::Bool }
, { CtrlRequiredForDrag, "CtrlRequiredForDrag" , INTERACTION_GROUP , QVariant::Bool }
, { DropUrlsAsText , "DropUrlsAsText" , INTERACTION_GROUP , QVariant::Bool }
, { AutoCopySelectedText , "AutoCopySelectedText" , INTERACTION_GROUP , QVariant::Bool }
, { CopyTextAsHTML , "CopyTextAsHTML" , INTERACTION_GROUP , QVariant::Bool }
, { TrimLeadingSpacesInSelectedText , "TrimLeadingSpacesInSelectedText" , INTERACTION_GROUP , QVariant::Bool }
, { TrimTrailingSpacesInSelectedText , "TrimTrailingSpacesInSelectedText" , INTERACTION_GROUP , QVariant::Bool }
, { PasteFromSelectionEnabled , "PasteFromSelectionEnabled" , INTERACTION_GROUP , QVariant::Bool }
, { PasteFromClipboardEnabled , "PasteFromClipboardEnabled" , INTERACTION_GROUP , QVariant::Bool }
, { MiddleClickPasteMode, "MiddleClickPasteMode" , INTERACTION_GROUP , QVariant::Int }
, { MouseWheelZoomEnabled, "MouseWheelZoomEnabled", INTERACTION_GROUP, QVariant::Bool }
, { AlternateScrolling, "AlternateScrolling", INTERACTION_GROUP, QVariant::Bool }
// Encoding
, { DefaultEncoding , "DefaultEncoding" , ENCODING_GROUP , QVariant::String }
, { static_cast(0) , nullptr , nullptr, QVariant::Invalid }
};
QHash Profile::PropertyInfoByName;
QHash Profile::PropertyInfoByProperty;
void Profile::fillTableWithDefaultNames()
{
static bool filledDefaults = false;
if (filledDefaults) {
return;
}
const PropertyInfo* iter = DefaultPropertyNames;
while (iter->name != nullptr) {
registerProperty(*iter);
iter++;
}
filledDefaults = true;
}
void Profile::useFallback()
{
// Fallback settings
setProperty(Name, i18nc("Name of the default/builtin profile", "Default"));
setProperty(UntranslatedName, QStringLiteral("Default"));
// magic path for the fallback profile which is not a valid
// non-directory file name
setProperty(Path, QStringLiteral("FALLBACK/"));
setProperty(Command, QString::fromUtf8(qgetenv("SHELL")));
// See Pty.cpp on why Arguments is populated
setProperty(Arguments, QStringList() << QString::fromUtf8(qgetenv("SHELL")));
setProperty(Icon, QStringLiteral("utilities-terminal"));
setProperty(Environment, QStringList() << QStringLiteral("TERM=xterm-256color") << QStringLiteral("COLORTERM=truecolor"));
setProperty(LocalTabTitleFormat, QStringLiteral("%d : %n"));
setProperty(RemoteTabTitleFormat, QStringLiteral("(%u) %H"));
setProperty(ShowTerminalSizeHint, true);
setProperty(DimWhenInactive, false);
setProperty(StartInCurrentSessionDir, true);
setProperty(MenuIndex, QStringLiteral("0"));
setProperty(SilenceSeconds, 10);
setProperty(TerminalColumns, 80);
setProperty(TerminalRows, 24);
setProperty(TerminalMargin, 1);
setProperty(TerminalCenter, false);
setProperty(MouseWheelZoomEnabled, true);
setProperty(AlternateScrolling, true);
setProperty(KeyBindings, QStringLiteral("default"));
setProperty(ColorScheme, QStringLiteral("Breeze"));
setProperty(Font, QFontDatabase::systemFont(QFontDatabase::FixedFont));
setProperty(HistoryMode, Enum::FixedSizeHistory);
setProperty(HistorySize, 1000);
setProperty(ScrollBarPosition, Enum::ScrollBarRight);
setProperty(ScrollFullPage, false);
setProperty(FlowControlEnabled, true);
setProperty(UrlHintsModifiers, 0);
setProperty(ReverseUrlHints, false);
setProperty(BlinkingTextEnabled, true);
setProperty(UnderlineLinksEnabled, true);
setProperty(UnderlineFilesEnabled, false);
setProperty(OpenLinksByDirectClickEnabled, false);
setProperty(CtrlRequiredForDrag, true);
setProperty(AutoCopySelectedText, false);
setProperty(CopyTextAsHTML, true);
setProperty(TrimLeadingSpacesInSelectedText, false);
setProperty(TrimTrailingSpacesInSelectedText, false);
setProperty(DropUrlsAsText, true);
setProperty(PasteFromSelectionEnabled, true);
setProperty(PasteFromClipboardEnabled, false);
setProperty(MiddleClickPasteMode, Enum::PasteFromX11Selection);
setProperty(TripleClickMode, Enum::SelectWholeLine);
setProperty(BlinkingCursorEnabled, false);
setProperty(BidiRenderingEnabled, true);
setProperty(LineSpacing, 0);
setProperty(CursorShape, Enum::BlockCursor);
setProperty(UseCustomCursorColor, false);
- setProperty(CustomCursorColor, QColor(Qt::black));
+ setProperty(CustomCursorColor, QColor(Qt::white));
+ setProperty(CustomCursorTextColor, QColor(Qt::black));
setProperty(BellMode, Enum::NotifyBell);
setProperty(DefaultEncoding, QLatin1String(QTextCodec::codecForLocale()->name()));
setProperty(AntiAliasFonts, true);
setProperty(BoldIntense, true);
setProperty(UseFontLineCharacters, false);
setProperty(WordCharacters, QStringLiteral(":@-./_~?&=%+#"));
// Fallback should not be shown in menus
setHidden(true);
}
Profile::Profile(const Profile::Ptr &parent)
: _propertyValues(QHash())
, _parent(parent)
, _hidden(false)
{
}
void Profile::clone(Profile::Ptr profile, bool differentOnly)
{
const PropertyInfo* properties = DefaultPropertyNames;
while (properties->name != nullptr) {
Property current = properties->property;
QVariant otherValue = profile->property(current);
switch (current) {
case Name:
case Path:
break;
default:
if (!differentOnly ||
property(current) != otherValue) {
setProperty(current, otherValue);
}
}
properties++;
}
}
Profile::~Profile() = default;
bool Profile::isHidden() const
{
return _hidden;
}
void Profile::setHidden(bool hidden)
{
_hidden = hidden;
}
void Profile::setParent(const Profile::Ptr &parent)
{
_parent = parent;
}
const Profile::Ptr Profile::parent() const
{
return _parent;
}
bool Profile::isEmpty() const
{
return _propertyValues.isEmpty();
}
QHash Profile::setProperties() const
{
return _propertyValues;
}
void Profile::setProperty(Property p, const QVariant& value)
{
_propertyValues.insert(p, value);
}
bool Profile::isPropertySet(Property p) const
{
return _propertyValues.contains(p);
}
Profile::Property Profile::lookupByName(const QString& name)
{
// insert default names into table the first time this is called
fillTableWithDefaultNames();
return PropertyInfoByName[name.toLower()].property;
}
void Profile::registerProperty(const PropertyInfo& info)
{
QString name = QLatin1String(info.name);
PropertyInfoByName.insert(name.toLower(), info);
// only allow one property -> name map
// (multiple name -> property mappings are allowed though)
if (!PropertyInfoByProperty.contains(info.property)) {
PropertyInfoByProperty.insert(info.property, info);
}
}
int Profile::menuIndexAsInt() const
{
bool ok;
int index = menuIndex().toInt(&ok, 10);
if (ok) {
return index;
}
return 0;
}
const QStringList Profile::propertiesInfoList() const
{
QStringList info;
const PropertyInfo* iter = DefaultPropertyNames;
while (iter->name != nullptr) {
info << QLatin1String(iter->name) + QStringLiteral(" : ") + QLatin1String(QVariant(iter->type).typeName());
iter++;
}
return info;
}
QHash ProfileCommandParser::parse(const QString& input)
{
QHash changes;
// regular expression to parse profile change requests.
//
// format: property=value;property=value ...
//
// where 'property' is a word consisting only of characters from A-Z
// where 'value' is any sequence of characters other than a semi-colon
//
static const QRegularExpression regExp(QStringLiteral("([a-zA-Z]+)=([^;]+)"));
QRegularExpressionMatchIterator iterator(regExp.globalMatch(input));
while (iterator.hasNext()) {
QRegularExpressionMatch match(iterator.next());
Profile::Property property = Profile::lookupByName(match.captured(1));
const QString value = match.captured(2);
changes.insert(property, value);
}
return changes;
}
void ProfileGroup::updateValues()
{
const PropertyInfo* properties = Profile::DefaultPropertyNames;
while (properties->name != nullptr) {
// the profile group does not store a value for some properties
// (eg. name, path) if even they are equal between profiles -
//
// the exception is when the group has only one profile in which
// case it behaves like a standard Profile
if (_profiles.count() > 1 &&
!canInheritProperty(properties->property)) {
properties++;
continue;
}
QVariant value;
for (int i = 0; i < _profiles.count(); i++) {
QVariant profileValue = _profiles[i]->property(properties->property);
if (value.isNull()) {
value = profileValue;
} else if (value != profileValue) {
value = QVariant();
break;
}
}
Profile::setProperty(properties->property, value);
properties++;
}
}
void ProfileGroup::setProperty(Property p, const QVariant& value)
{
if (_profiles.count() > 1 && !canInheritProperty(p)) {
return;
}
Profile::setProperty(p, value);
for (const Profile::Ptr &profile : qAsConst(_profiles)) {
profile->setProperty(p, value);
}
}
diff --git a/src/Profile.h b/src/Profile.h
index eb9ce9e7..9edea010 100644
--- a/src/Profile.h
+++ b/src/Profile.h
@@ -1,810 +1,821 @@
/*
This source file is part of Konsole, a terminal emulator.
Copyright 2007-2008 by Robert Knight
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
*/
#ifndef PROFILE_H
#define PROFILE_H
// Qt
#include
#include
#include
#include
#include
// Konsole
#include "konsoleprivate_export.h"
namespace Konsole {
class ProfileGroup;
/**
* Represents a terminal set-up which can be used to
* set the initial state of new terminal sessions or applied
* to existing sessions. Profiles consist of a number of named
* properties, which can be retrieved using property() and
* set using setProperty(). isPropertySet() can be used to check
* whether a particular property has been set in a profile.
*
* Profiles support a simple form of inheritance. When a new Profile
* is constructed, a pointer to a parent profile can be passed to
* the constructor. When querying a particular property of a profile
* using property(), the profile will return its own value for that
* property if one has been set or otherwise it will return the
* parent's value for that property.
*
* Profiles can be loaded from disk using ProfileReader instances
* and saved to disk using ProfileWriter instances.
*/
class KONSOLEPRIVATE_EXPORT Profile : public QSharedData
{
Q_GADGET
friend class ProfileReader;
friend class ProfileWriter;
friend class ProfileGroup;
public:
using Ptr = QExplicitlySharedDataPointer;
using GroupPtr = QExplicitlySharedDataPointer;
/**
* This enum describes the available properties
* which a Profile may consist of.
*
* Properties can be set using setProperty() and read
* using property()
*/
enum Property {
/** (QString) Path to the profile's configuration file on-disk. */
Path,
/** (QString) The descriptive name of this profile. */
Name,
/** (QString) The untranslated name of this profile.
* Warning: this is an internal property. Do not touch it.
*/
UntranslatedName,
/** (QString) The name of the icon associated with this profile.
* This is used in menus and tabs to represent the profile.
*/
Icon,
/** (QString) The command to execute ( excluding arguments ) when
* creating a new terminal session using this profile.
*/
Command,
/** (QStringList) The arguments which are passed to the program
* specified by the Command property when creating a new terminal
* session using this profile.
*/
Arguments,
/** (QStringList) Additional environment variables (in the form of
* NAME=VALUE pairs) which are passed to the program specified by
* the Command property when creating a new terminal session using
* this profile.
*/
Environment,
/** (QString) The initial working directory for sessions created
* using this profile.
*/
Directory,
/** (QString) The format used for tab titles when running normal
* commands.
*/
LocalTabTitleFormat,
/** (QString) The format used for tab titles when the session is
* running a remote command (eg. SSH)
*/
RemoteTabTitleFormat,
/** (bool) Specifies whether show hint for terminal size after
* resizing the application window.
*/
ShowTerminalSizeHint,
/** (bool) If the background color should change to indicate if the window is active
*/
DimWhenInactive,
/** (QFont) The font to use in terminal displays using this profile. */
Font,
/** (QString) The name of the color scheme to use in terminal
* displays using this profile.
* Color schemes are managed by the ColorSchemeManager class.
*/
ColorScheme,
/** (QString) The name of the key bindings.
* Key bindings are managed by the KeyboardTranslatorManager class.
*/
KeyBindings,
/** (HistoryModeEnum) Specifies the storage type used for keeping
* the output produced by terminal sessions using this profile.
*
* See Enum::HistoryModeEnum
*/
HistoryMode,
/** (int) Specifies the number of lines of output to remember in
* terminal sessions using this profile. Once the limit is reached,
* the oldest lines are lost if the HistoryMode property is
* FixedSizeHistory
*/
HistorySize,
/** (ScrollBarPositionEnum) Specifies the position of the scroll bar
* in terminal displays using this profile.
*
* See Enum::ScrollBarPositionEnum
*/
ScrollBarPosition,
/** (bool) Specifies whether the PageUp/Down will scroll the full
* height or half height.
*/
ScrollFullPage,
/** (bool) Specifies whether the terminal will enable Bidirectional
* text display
*/
BidiRenderingEnabled,
/** (bool) Specifies whether text in terminal displays is allowed
* to blink.
*/
BlinkingTextEnabled,
/** (bool) Specifies whether the flow control keys (typically Ctrl+S,
* Ctrl+Q) have any effect. Also known as Xon/Xoff
*/
FlowControlEnabled,
/** (int) Specifies the pixels between the terminal lines.
*/
LineSpacing,
/** (bool) Specifies whether the cursor blinks ( in a manner similar
* to text editing applications )
*/
BlinkingCursorEnabled,
/** (bool) If true, terminal displays use a fixed color to draw the
* cursor, specified by the CustomCursorColor property. Otherwise
* the cursor changes color to match the character underneath it.
*/
UseCustomCursorColor,
/** (CursorShapeEnum) The shape used by terminal displays to
* represent the cursor.
*
* See Enum::CursorShapeEnum
*/
CursorShape,
/** (QColor) The color used by terminal displays to draw the cursor.
* Only applicable if the UseCustomCursorColor property is true.
*/
CustomCursorColor,
+ /** (QColor) The color used by terminal displays to draw the character
+ * underneath the cursor. Only applicable if the UseCustomCursorColor
+ * property is true and CursorShape property is Enum::BlockCursor.
+ */
+ CustomCursorTextColor,
/** (QString) A string consisting of the characters used to delimit
* words when selecting text in the terminal display.
*/
WordCharacters,
/** (TripleClickModeEnum) Specifies which part of current line should
* be selected with triple click action.
*
* See Enum::TripleClickModeEnum
*/
TripleClickMode,
/** (bool) If true, text that matches a link or an email address is
* underlined when hovered by the mouse pointer.
*/
UnderlineLinksEnabled,
/** (bool) If true, text that matches a file is
* underlined when hovered by the mouse pointer.
*/
UnderlineFilesEnabled,
/** (bool) If true, links can be opened by direct mouse click.*/
OpenLinksByDirectClickEnabled,
/** (bool) If true, control key must be pressed to click and drag selected text. */
CtrlRequiredForDrag,
/** (bool) If true, automatically copy selected text into the clipboard */
AutoCopySelectedText,
/** (bool) The QMimeData object used when copying text always
* has the plain/text MIME set and if this is @c true then the
* text/html MIME is set too in that object i.e. the copied
* text will include formatting, font faces, colors... etc; users
* can paste the text as HTML (default) or as plain/text by using
* e.g. the "Paste Special" functionality in LibreOffice.
*/
CopyTextAsHTML,
/** (bool) If true, leading spaces are trimmed in selected text */
TrimLeadingSpacesInSelectedText,
/** (bool) If true, trailing spaces are trimmed in selected text */
TrimTrailingSpacesInSelectedText,
/** (bool) If true, then dropped URLs will be pasted as text without asking */
DropUrlsAsText,
/** (bool) If true, middle mouse button pastes from X Selection */
PasteFromSelectionEnabled,
/** (bool) If true, middle mouse button pastes from Clipboard */
PasteFromClipboardEnabled,
/** (MiddleClickPasteModeEnum) Specifies the source from which mouse
* middle click pastes data.
*
* See Enum::MiddleClickPasteModeEnum
*/
MiddleClickPasteMode,
/** (String) Default text codec */
DefaultEncoding,
/** (bool) Whether fonts should be aliased or not */
AntiAliasFonts,
/** (bool) Whether character with intense colors should be rendered
* in bold font or just in bright color. */
BoldIntense,
/** (bool) Whether to use font's line characters instead of the
* builtin code. */
UseFontLineCharacters,
/** (bool) Whether new sessions should be started in the same
* directory as the currently active session.
*/
StartInCurrentSessionDir,
/** (int) Specifies the threshold of detected silence in seconds. */
SilenceSeconds,
/** (BellModeEnum) Specifies the behavior of bell.
*
* See Enum::BellModeEnum
*/
BellMode,
/** (int) Specifies the preferred columns. */
TerminalColumns,
/** (int) Specifies the preferred rows. */
TerminalRows,
/** Index of profile in the File Menu
* WARNING: this is currently an internal field, which is
* expected to be zero on disk. Do not modify it manually.
*
* In future, the format might be #.#.# to account for levels
*/
MenuIndex,
/** (int) Margin width in pixels */
TerminalMargin,
/** (bool) Center terminal when there is a margin */
TerminalCenter,
/** (bool) If true, mouse wheel scroll with Ctrl key pressed
* increases/decreases the terminal font size.
*/
MouseWheelZoomEnabled,
/** (bool) Specifies whether emulated up/down key press events are
* sent, for mouse scroll wheel events, to programs using the
* Alternate Screen buffer; this is mainly for the benefit of
* programs that don't natively support mouse scroll events, e.g.
* less.
*
* This also works for scrolling in applications that support Mouse
* Tracking events but don't indicate they're interested in those
* events; for example, when vim doesn't indicate it's interested
* in Mouse Tracking events (i.e. when the mouse is in Normal
* (not Visual) mode): https://vimhelp.org/intro.txt.html#vim-modes-intro
* mouse wheel scroll events will send up/down key press events.
*
* Default value is true.
* See also, MODE_Mouse1007 in the Emulation header, which toggles
* Alternate Scrolling with escape sequences.
*/
AlternateScrolling,
/** (int) Keyboard modifiers to show URL hints */
UrlHintsModifiers,
/** (bool) Reverse the order of URL hints */
ReverseUrlHints
};
Q_ENUM(Property)
/**
* Constructs a new profile
*
* @param parent The parent profile. When querying the value of a
* property using property(), if the property has not been set in this
* profile then the parent's value for the property will be returned.
*/
explicit Profile(const Ptr &parent = Ptr());
virtual ~Profile();
/**
* Copies all properties except Name and Path from the specified @p
* profile into this profile
*
* @param profile The profile to copy properties from
* @param differentOnly If true, only properties in @p profile which have
* a different value from this profile's current value (either set via
* setProperty() or inherited from the parent profile) will be set.
*/
void clone(Ptr profile, bool differentOnly = true);
/**
* A profile which contains a number of default settings for various
* properties. This can be used as a parent for other profiles or a
* fallback in case a profile cannot be loaded from disk.
*/
void useFallback();
/**
* Changes the parent profile. When calling the property() method,
* if the specified property has not been set for this profile,
* the parent's value for the property will be returned instead.
*/
void setParent(const Ptr &parent);
/** Returns the parent profile. */
const Ptr parent() const;
/** Returns this profile as a group or null if this profile is not a
* group.
*/
const GroupPtr asGroup() const;
GroupPtr asGroup();
/**
* Returns the current value of the specified @p property, cast to type T.
* Internally properties are stored using the QVariant type and cast to T
* using QVariant::value();
*
* If the specified @p property has not been set in this profile,
* and a non-null parent was specified in the Profile's constructor,
* the parent's value for @p property will be returned.
*/
template
T property(Property p) const;
/** Sets the value of the specified @p property to @p value. */
virtual void setProperty(Property p, const QVariant &value);
/** Returns true if the specified property has been set in this Profile
* instance.
*/
virtual bool isPropertySet(Property p) const;
/** Returns a map of the properties set in this Profile instance. */
virtual QHash setProperties() const;
/** Returns true if no properties have been set in this Profile instance. */
bool isEmpty() const;
/**
* Returns true if this is a 'hidden' profile which should not be
* displayed in menus or saved to disk.
*
* This is used for the fallback profile, in case there are no profiles on
* disk which can be loaded, or for overlay profiles created to handle
* command-line arguments which change profile properties.
*/
bool isHidden() const;
/** Specifies whether this is a hidden profile. See isHidden() */
void setHidden(bool hidden);
//
// Convenience methods for property() and setProperty() go here
//
/** Convenience method for property(Profile::Path) */
QString path() const
{
return property(Profile::Path);
}
/** Convenience method for property(Profile::Name) */
QString name() const
{
return property(Profile::Name);
}
/** Convenience method for property(Profile::UntranslatedName) */
QString untranslatedName() const
{
return property(Profile::UntranslatedName);
}
/** Convenience method for property(Profile::Directory) */
QString defaultWorkingDirectory() const
{
return property(Profile::Directory);
}
/** Convenience method for property(Profile::Icon) */
QString icon() const
{
return property(Profile::Icon);
}
/** Convenience method for property(Profile::Command) */
QString command() const
{
return property(Profile::Command);
}
/** Convenience method for property(Profile::Arguments) */
QStringList arguments() const
{
return property(Profile::Arguments);
}
/** Convenience method for property(Profile::LocalTabTitleFormat) */
QString localTabTitleFormat() const
{
return property(Profile::LocalTabTitleFormat);
}
/** Convenience method for property(Profile::RemoteTabTitleFormat) */
QString remoteTabTitleFormat() const
{
return property(Profile::RemoteTabTitleFormat);
}
/** Convenience method for property(Profile::ShowTerminalSizeHint) */
bool showTerminalSizeHint() const
{
return property(Profile::ShowTerminalSizeHint);
}
/** Convenience method for property(Profile::DimWhenInactive) */
bool dimWhenInactive() const
{
return property(Profile::DimWhenInactive);
}
/** Convenience method for property(Profile::Font) */
QFont font() const
{
return property(Profile::Font);
}
/** Convenience method for property(Profile::ColorScheme) */
QString colorScheme() const
{
return property(Profile::ColorScheme);
}
/** Convenience method for property(Profile::Environment) */
QStringList environment() const
{
return property(Profile::Environment);
}
/** Convenience method for property(Profile::KeyBindings) */
QString keyBindings() const
{
return property(Profile::KeyBindings);
}
/** Convenience method for property(Profile::HistorySize) */
int historySize() const
{
return property(Profile::HistorySize);
}
/** Convenience method for property(Profile::BidiRenderingEnabled) */
bool bidiRenderingEnabled() const
{
return property(Profile::BidiRenderingEnabled);
}
/** Convenience method for property(Profile::LineSpacing) */
int lineSpacing() const
{
return property(Profile::LineSpacing);
}
/** Convenience method for property(Profile::BlinkingTextEnabled) */
bool blinkingTextEnabled() const
{
return property(Profile::BlinkingTextEnabled);
}
/** Convenience method for property(Profile::MouseWheelZoomEnabled) */
bool mouseWheelZoomEnabled() const
{
return property(Profile::MouseWheelZoomEnabled);
}
/** Convenience method for property(Profile::BlinkingCursorEnabled) */
bool blinkingCursorEnabled() const
{
return property(Profile::BlinkingCursorEnabled);
}
/** Convenience method for property(Profile::FlowControlEnabled) */
bool flowControlEnabled() const
{
return property(Profile::FlowControlEnabled);
}
/** Convenience method for property(Profile::UseCustomCursorColor) */
bool useCustomCursorColor() const
{
return property(Profile::UseCustomCursorColor);
}
- /** Convenience method for property(Profile::CustomCursorColor) */
+ /** Convenience method for property(Profile::CustomCursorColor) */
QColor customCursorColor() const
{
return property(Profile::CustomCursorColor);
}
+ /** Convenience method for property(Profile::CustomCursorTextColor) */
+ QColor customCursorTextColor() const
+ {
+ return property(Profile::CustomCursorTextColor);
+ }
+
/** Convenience method for property(Profile::WordCharacters) */
QString wordCharacters() const
{
return property(Profile::WordCharacters);
}
/** Convenience method for property(Profile::UnderlineLinksEnabled) */
bool underlineLinksEnabled() const
{
return property(Profile::UnderlineLinksEnabled);
}
/** Convenience method for property(Profile::UnderlineFilesEnabled) */
bool underlineFilesEnabled() const
{
return property(Profile::UnderlineFilesEnabled);
}
bool autoCopySelectedText() const
{
return property(Profile::AutoCopySelectedText);
}
/** Convenience method for property(Profile::DefaultEncoding) */
QString defaultEncoding() const
{
return property(Profile::DefaultEncoding);
}
/** Convenience method for property(Profile::AntiAliasFonts) */
bool antiAliasFonts() const
{
return property(Profile::AntiAliasFonts);
}
/** Convenience method for property(Profile::BoldIntense) */
bool boldIntense() const
{
return property(Profile::BoldIntense);
}
/** Convenience method for property(Profile::UseFontLineCharacters)*/
bool useFontLineCharacters() const
{
return property(Profile::UseFontLineCharacters);
}
/** Convenience method for property(Profile::StartInCurrentSessionDir) */
bool startInCurrentSessionDir() const
{
return property(Profile::StartInCurrentSessionDir);
}
/** Convenience method for property(Profile::SilenceSeconds) */
int silenceSeconds() const
{
return property(Profile::SilenceSeconds);
}
/** Convenience method for property(Profile::TerminalColumns) */
int terminalColumns() const
{
return property(Profile::TerminalColumns);
}
/** Convenience method for property(Profile::TerminalRows) */
int terminalRows() const
{
return property(Profile::TerminalRows);
}
/** Convenience method for property(Profile::TerminalMargin) */
int terminalMargin() const
{
return property(Profile::TerminalMargin);
}
/** Convenience method for property(Profile::TerminalCenter) */
bool terminalCenter() const
{
return property(Profile::TerminalCenter);
}
/** Convenience method for property(Profile::MenuIndex) */
QString menuIndex() const
{
return property(Profile::MenuIndex);
}
int menuIndexAsInt() const;
/** Return a list of all properties names and their type
* (for use with -p option).
*/
const QStringList propertiesInfoList() const;
/**
* Returns the element from the Property enum associated with the
* specified @p name.
*
* @param name The name of the property to look for, this is case
* insensitive.
*/
static Property lookupByName(const QString &name);
private:
struct PropertyInfo;
// Defines a new property, this property is then available
// to all Profile instances.
static void registerProperty(const PropertyInfo &info);
// fills the table with default names for profile properties
// the first time it is called.
// subsequent calls return immediately
static void fillTableWithDefaultNames();
// returns true if the property can be inherited
static bool canInheritProperty(Property p);
QHash _propertyValues;
Ptr _parent;
bool _hidden;
static QHash PropertyInfoByName;
static QHash PropertyInfoByProperty;
// Describes a property. Each property has a name and group
// which is used when saving/loading the profile.
struct PropertyInfo {
Property property;
const char *name;
const char *group;
QVariant::Type type;
};
static const PropertyInfo DefaultPropertyNames[];
};
inline bool Profile::canInheritProperty(Property p)
{
return p != Name && p != Path;
}
template
inline T Profile::property(Property p) const
{
return property(p).value();
}
template<>
inline QVariant Profile::property(Property p) const
{
if (_propertyValues.contains(p)) {
return _propertyValues[p];
} else if (_parent && canInheritProperty(p)) {
return _parent->property(p);
} else {
return QVariant();
}
}
/**
* A composite profile which allows a group of profiles to be treated as one.
* When setting a property, the new value is applied to all profiles in the
* group. When reading a property, if all profiles in the group have the same
* value then that value is returned, otherwise the result is null.
*
* Profiles can be added to the group using addProfile(). When all profiles
* have been added updateValues() must be called
* to sync the group's property values with those of the group's profiles.
*
* The Profile::Name and Profile::Path properties are unique to individual
* profiles, setting these properties on a ProfileGroup has no effect.
*/
class KONSOLEPRIVATE_EXPORT ProfileGroup : public Profile
{
public:
using Ptr = QExplicitlySharedDataPointer;
/** Construct a new profile group, which is hidden by default. */
explicit ProfileGroup(const Profile::Ptr &profileParent = Profile::Ptr());
/** Add a profile to the group. Calling setProperty() will update this
* profile. When creating a group, add the profiles to the group then
* call updateValues() to make the group's property values reflect the
* profiles currently in the group.
*/
void addProfile(const Profile::Ptr &profile)
{
_profiles.append(profile);
}
/** Remove a profile from the group. Calling setProperty() will no longer
* affect this profile. */
void removeProfile(const Profile::Ptr &profile)
{
_profiles.removeAll(profile);
}
/** Returns the profiles in this group .*/
QList profiles() const
{
return _profiles;
}
/**
* Updates the property values in this ProfileGroup to match those from
* the group's profiles()
*
* For each available property, if each profile in the group has the same
* value then the ProfileGroup will use that value for the property.
* Otherwise the value for the property will be set to a null QVariant
*
* Some properties such as the name and the path of the profile
* will always be set to null if the group has more than one profile.
*/
void updateValues();
/** Sets the value of @p property in each of the group's profiles to
* @p value.
*/
void setProperty(Property p, const QVariant &value) override;
private:
Q_DISABLE_COPY(ProfileGroup)
QList _profiles;
};
inline ProfileGroup::ProfileGroup(const Profile::Ptr &profileParent) :
Profile(profileParent),
_profiles(QList())
{
setHidden(true);
}
inline const Profile::GroupPtr Profile::asGroup() const
{
const Profile::GroupPtr ptr(dynamic_cast(
const_cast(this)));
return ptr;
}
inline Profile::GroupPtr Profile::asGroup()
{
return Profile::GroupPtr(dynamic_cast(this));
}
/**
* Parses an input string consisting of property names
* and assigned values and returns a table of properties
* and values.
*
* The input string will typically look like this:
*
* @code
* PropertyName=Value;PropertyName=Value ...
* @endcode
*
* For example:
*
* @code
* Icon=konsole;Directory=/home/bob
* @endcode
*/
class KONSOLEPRIVATE_EXPORT ProfileCommandParser
{
public:
/**
* Parses an input string consisting of property names
* and assigned values and returns a table of
* properties and values.
*/
QHash parse(const QString &input);
};
}
Q_DECLARE_METATYPE(Konsole::Profile::Ptr)
#endif // PROFILE_H
diff --git a/src/TerminalDisplay.cpp b/src/TerminalDisplay.cpp
index b107c8c4..abecf113 100644
--- a/src/TerminalDisplay.cpp
+++ b/src/TerminalDisplay.cpp
@@ -1,4020 +1,4026 @@
/*
This file is part of Konsole, a terminal emulator for KDE.
Copyright 2006-2008 by Robert Knight
Copyright 1997,1998 by Lars Doelle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
*/
// Own
#include "TerminalDisplay.h"
// Config
#include "config-konsole.h"
// Qt
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// KDE
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Konsole
#include "Filter.h"
#include "konsoledebug.h"
#include "TerminalCharacterDecoder.h"
#include "Screen.h"
#include "SessionController.h"
#include "ExtendedCharTable.h"
#include "TerminalDisplayAccessible.h"
#include "SessionManager.h"
#include "Session.h"
#include "WindowSystemInfo.h"
#include "IncrementalSearchBar.h"
#include "Profile.h"
#include "ViewManager.h" // for colorSchemeForProfile. // TODO: Rewrite this.
#include "LineBlockCharacters.h"
using namespace Konsole;
#define REPCHAR "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"abcdefgjijklmnopqrstuvwxyz" \
"0123456789./+@"
// we use this to force QPainter to display text in LTR mode
// more information can be found in: https://unicode.org/reports/tr9/
const QChar LTR_OVERRIDE_CHAR(0x202D);
inline int TerminalDisplay::loc(int x, int y) const {
Q_ASSERT(y >= 0 && y < _lines);
Q_ASSERT(x >= 0 && x < _columns);
x = qBound(0, x, _columns - 1);
y = qBound(0, y, _lines - 1);
return y * _columns + x;
}
/* ------------------------------------------------------------------------- */
/* */
/* Colors */
/* */
/* ------------------------------------------------------------------------- */
/* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb)
Code 0 1 2 3 4 5 6 7
----------- ------- ------- ------- ------- ------- ------- ------- -------
ANSI (bgr) Black Red Green Yellow Blue Magenta Cyan White
IBMPC (rgb) Black Blue Green Cyan Red Magenta Yellow White
*/
ScreenWindow* TerminalDisplay::screenWindow() const
{
return _screenWindow;
}
void TerminalDisplay::setScreenWindow(ScreenWindow* window)
{
// disconnect existing screen window if any
if (!_screenWindow.isNull()) {
disconnect(_screenWindow , nullptr , this , nullptr);
}
_screenWindow = window;
if (!_screenWindow.isNull()) {
connect(_screenWindow.data() , &Konsole::ScreenWindow::outputChanged , this , &Konsole::TerminalDisplay::updateLineProperties);
connect(_screenWindow.data() , &Konsole::ScreenWindow::outputChanged , this , &Konsole::TerminalDisplay::updateImage);
connect(_screenWindow.data() , &Konsole::ScreenWindow::currentResultLineChanged , this , &Konsole::TerminalDisplay::updateImage);
connect(_screenWindow.data(), &Konsole::ScreenWindow::outputChanged, this, [this]() {
_filterUpdateRequired = true;
});
connect(_screenWindow.data(), &Konsole::ScreenWindow::scrolled, this, [this]() {
_filterUpdateRequired = true;
});
_screenWindow->setWindowLines(_lines);
}
}
const ColorEntry* TerminalDisplay::colorTable() const
{
return _colorTable;
}
void TerminalDisplay::onColorsChanged()
{
// Mostly just fix the scrollbar
// this is a workaround to add some readability to old themes like Fusion
// changing the light value for button a bit makes themes like fusion, windows and oxygen way more readable and pleasing
QPalette p = QApplication::palette();
QColor buttonTextColor = _colorTable[DEFAULT_FORE_COLOR];
QColor backgroundColor = _colorTable[DEFAULT_BACK_COLOR];
backgroundColor.setAlphaF(_opacity);
QColor buttonColor = backgroundColor.toHsv();
if (buttonColor.valueF() < 0.5) {
buttonColor = buttonColor.lighter();
} else {
buttonColor = buttonColor.darker();
}
p.setColor(QPalette::Button, buttonColor);
p.setColor(QPalette::Window, backgroundColor);
p.setColor(QPalette::Base, backgroundColor);
p.setColor(QPalette::WindowText, buttonTextColor);
p.setColor(QPalette::ButtonText, buttonTextColor);
setPalette(p);
_scrollBar->setPalette(p);
update();
}
void TerminalDisplay::setBackgroundColor(const QColor& color)
{
_colorTable[DEFAULT_BACK_COLOR] = color;
onColorsChanged();
}
QColor TerminalDisplay::getBackgroundColor() const
{
return _colorTable[DEFAULT_BACK_COLOR];
}
void TerminalDisplay::setForegroundColor(const QColor& color)
{
_colorTable[DEFAULT_FORE_COLOR] = color;
onColorsChanged();
}
QColor TerminalDisplay::getForegroundColor() const
{
return _colorTable[DEFAULT_FORE_COLOR];
}
void TerminalDisplay::setColorTable(const ColorEntry table[])
{
for (int i = 0; i < TABLE_COLORS; i++) {
_colorTable[i] = table[i];
}
setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR]);
onColorsChanged();
}
/* ------------------------------------------------------------------------- */
/* */
/* Font */
/* */
/* ------------------------------------------------------------------------- */
static inline bool isLineCharString(const QString& string)
{
if (string.length() == 0) {
return false;
}
return LineBlockCharacters::canDraw(string.at(0).unicode());
}
void TerminalDisplay::fontChange(const QFont&)
{
QFontMetrics fm(font());
_fontHeight = fm.height() + _lineSpacing;
Q_ASSERT(_fontHeight > 0);
/* TODO: When changing the three deprecated width() below
* consider the info in
* https://phabricator.kde.org/D23144 comments
* horizontalAdvance() was added in Qt 5.11 (which should be the
* minimum for 20.04 or 20.08 KDE Applications release)
*/
// waba TerminalDisplay 1.123:
// "Base character width on widest ASCII character. This prevents too wide
// characters in the presence of double wide (e.g. Japanese) characters."
// Get the width from representative normal width characters
_fontWidth = qRound((static_cast(fm.width(QStringLiteral(REPCHAR))) / static_cast(qstrlen(REPCHAR))));
_fixedFont = true;
const int fw = fm.width(QLatin1Char(REPCHAR[0]));
for (unsigned int i = 1; i < qstrlen(REPCHAR); i++) {
if (fw != fm.width(QLatin1Char(REPCHAR[i]))) {
_fixedFont = false;
break;
}
}
if (_fontWidth < 1) {
_fontWidth = 1;
}
_fontAscent = fm.ascent();
emit changedFontMetricSignal(_fontHeight, _fontWidth);
propagateSize();
update();
}
void TerminalDisplay::setVTFont(const QFont& f)
{
QFont newFont(f);
int strategy = 0;
// hint that text should be drawn with- or without anti-aliasing.
// depending on the user's font configuration, this may not be respected
strategy |= _antialiasText ? QFont::PreferAntialias : QFont::NoAntialias;
// Konsole cannot handle non-integer font metrics
strategy |= QFont::ForceIntegerMetrics;
// In case the provided font doesn't have some specific characters it should
// fall back to a Monospace fonts.
newFont.setStyleHint(QFont::TypeWriter, QFont::StyleStrategy(strategy));
// Try to check that a good font has been loaded.
// For some fonts, ForceIntegerMetrics causes height() == 0 which
// will cause Konsole to crash later.
QFontMetrics fontMetrics2(newFont);
if (fontMetrics2.height() < 1) {
qCDebug(KonsoleDebug)<<"The font "<(fontInfo.styleHint()),
fontInfo.weight(),
static_cast