diff --git a/src/EditProfileDialog.cpp b/src/EditProfileDialog.cpp index e47b56a7..3c7b8bc9 100644 --- a/src/EditProfileDialog.cpp +++ b/src/EditProfileDialog.cpp @@ -1,1765 +1,1765 @@ /* 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. */ // Own #include "EditProfileDialog.h" // Standard #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE #include #include #include #include #include #include // Konsole #include "ColorSchemeManager.h" #include "ui_EditProfileDialog.h" #include "KeyBindingEditor.h" #include "KeyboardTranslator.h" #include "KeyboardTranslatorManager.h" #include "ProfileManager.h" #include "ShellCommand.h" #include "WindowSystemInfo.h" #include "Shortcut_p.h" using namespace Konsole; EditProfileDialog::EditProfileDialog(QWidget *aParent) : QDialog(aParent), _ui(nullptr), _tempProfile(nullptr), _profile(nullptr), _pageNeedsUpdate(QVector()), _previewedProperties(QHash()), _delayedPreviewProperties(QHash()), _delayedPreviewTimer(new QTimer(this)), _colorDialog(nullptr), mButtonBox(nullptr) { setWindowTitle(i18n("Edit Profile")); mButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Apply); auto mainWidget = new QWidget(this); auto mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); connect(mButtonBox, &QDialogButtonBox::accepted, this, &Konsole::EditProfileDialog::accept); connect(mButtonBox, &QDialogButtonBox::rejected, this, &Konsole::EditProfileDialog::reject); // disable the apply button , since no modification has been made mButtonBox->button(QDialogButtonBox::Apply)->setEnabled(false); connect(mButtonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &Konsole::EditProfileDialog::apply); connect(_delayedPreviewTimer, &QTimer::timeout, this, &Konsole::EditProfileDialog::delayedPreviewActivate); _ui = new Ui::EditProfileDialog(); _ui->setupUi(mainWidget); mainLayout->addWidget(mButtonBox); // there are various setupXYZPage() methods to load the items // for each page and update their states to match the profile // being edited. // // these are only called when needed ( ie. when the user clicks // the tab to move to that page ). // // the _pageNeedsUpdate vector keeps track of the pages that have // not been updated since the last profile change and will need // to be refreshed when the user switches to them _pageNeedsUpdate.resize(_ui->tabWidget->count()); connect(_ui->tabWidget, &QTabWidget::currentChanged, this, &Konsole::EditProfileDialog::preparePage); createTempProfile(); } EditProfileDialog::~EditProfileDialog() { delete _ui; } void EditProfileDialog::save() { if (_tempProfile->isEmpty()) { return; } ProfileManager::instance()->changeProfile(_profile, _tempProfile->setProperties()); // ensure that these settings are not undone by a call // to unpreview() QHashIterator iter(_tempProfile->setProperties()); while (iter.hasNext()) { iter.next(); _previewedProperties.remove(iter.key()); } createTempProfile(); mButtonBox->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 (mButtonBox->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; foreach(auto 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 _ui->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 _ui->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(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 _pageNeedsUpdate.fill(true); preparePage(_ui->tabWidget->currentIndex()); 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(int page) { const Profile::Ptr profile = lookupProfile(); Q_ASSERT(_pageNeedsUpdate.count() > page); Q_ASSERT(profile); QWidget *pageWidget = _ui->tabWidget->widget(page); if (_pageNeedsUpdate[page]) { if (pageWidget == _ui->generalTab) { setupGeneralPage(profile); } else if (pageWidget == _ui->tabsTab) { setupTabsPage(profile); } else if (pageWidget == _ui->appearanceTab) { setupAppearancePage(profile); } else if (pageWidget == _ui->scrollingTab) { setupScrollingPage(profile); } else if (pageWidget == _ui->keyboardTab) { setupKeyboardPage(profile); } else if (pageWidget == _ui->mouseTab) { setupMousePage(profile); } else if (pageWidget == _ui->advancedTab) { setupAdvancedPage(profile); } else { Q_ASSERT(false); } _pageNeedsUpdate[page] = false; } } void EditProfileDialog::selectProfileName() { _ui->profileNameEdit->setFocus(); _ui->profileNameEdit->selectAll(); } void EditProfileDialog::setupGeneralPage(const Profile::Ptr profile) { // basic profile options { _ui->profileNameEdit->setPlaceholderText(i18nc("@label:textbox", "Enter descriptive label")); ProfileGroup::Ptr group = profile->asGroup(); if (!group || group->profiles().count() < 2) { _ui->profileNameEdit->setText(profile->name()); _ui->profileNameEdit->setClearButtonEnabled(true); } else { _ui->profileNameEdit->setText(groupProfileNames(group, -1)); _ui->profileNameEdit->setEnabled(false); _ui->profileNameLabel->setEnabled(false); } } ShellCommand command(profile->command(), profile->arguments()); _ui->commandEdit->setText(command.fullCommand()); // If a "completion" is requested, consider changing this to KLineEdit // and using KCompletion. _ui->initialDirEdit->setText(profile->defaultWorkingDirectory()); _ui->initialDirEdit->setClearButtonEnabled(true); _ui->dirSelectButton->setIcon(QIcon::fromTheme(QStringLiteral("folder-open"))); _ui->iconSelectButton->setIcon(QIcon::fromTheme(profile->icon())); _ui->startInSameDirButton->setChecked(profile->startInCurrentSessionDir()); // terminal options _ui->terminalColumnsEntry->setValue(profile->terminalColumns()); _ui->terminalRowsEntry->setValue(profile->terminalRows()); // window options _ui->showTerminalSizeHintButton->setChecked(profile->showTerminalSizeHint()); - _ui->indicateActiveButton->setChecked(profile->indicateActiveWindow()); + _ui->dimWhenInactiveCheckbox->setChecked(profile->dimWhenInactive()); // signals and slots connect(_ui->dirSelectButton, &QToolButton::clicked, this, &Konsole::EditProfileDialog::selectInitialDir); connect(_ui->iconSelectButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::selectIcon); connect(_ui->startInSameDirButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::startInSameDir); connect(_ui->profileNameEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::profileNameChanged); connect(_ui->initialDirEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::initialDirChanged); connect(_ui->commandEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::commandChanged); connect(_ui->environmentEditButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::showEnvironmentEditor); connect(_ui->terminalColumnsEntry, static_cast(&QSpinBox::valueChanged), this, &Konsole::EditProfileDialog::terminalColumnsEntryChanged); connect(_ui->terminalRowsEntry, static_cast(&QSpinBox::valueChanged), this, &Konsole::EditProfileDialog::terminalRowsEntryChanged); connect(_ui->showTerminalSizeHintButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::showTerminalSizeHint); - connect(_ui->indicateActiveButton, &QCheckBox::toggled, this, - &Konsole::EditProfileDialog::setIndicateActive); + connect(_ui->dimWhenInactiveCheckbox, &QCheckBox::toggled, this, + &Konsole::EditProfileDialog::setDimWhenInactive); } 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 _ui->renameTabWidget->setTabTitleText(profile->localTabTitleFormat()); _ui->renameTabWidget->setRemoteTabTitleText(profile->remoteTabTitleFormat()); connect(_ui->renameTabWidget, &Konsole::RenameTabWidget::tabTitleFormatChanged, this, &Konsole::EditProfileDialog::tabTitleFormatChanged); connect(_ui->renameTabWidget, &Konsole::RenameTabWidget::remoteTabTitleFormatChanged, this, &Konsole::EditProfileDialog::remoteTabTitleFormatChanged); // tab monitoring const int silenceSeconds = profile->silenceSeconds(); _ui->silenceSecondsSpinner->setValue(silenceSeconds); _ui->silenceSecondsSpinner->setSuffix(ki18ncp("Unit of time", " second", " seconds")); connect(_ui->silenceSecondsSpinner, static_cast(&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::setIndicateActive(bool value) +void EditProfileDialog::setDimWhenInactive(bool value) { - updateTempProfileProperty(Profile::IndicateActiveWindow, 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()) { _ui->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(_ui->initialDirEdit-> text())); if (!url.isEmpty()) { _ui->initialDirEdit->setText(url.path()); } } void EditProfileDialog::setupAppearancePage(const Profile::Ptr profile) { auto delegate = new ColorSchemeViewDelegate(this); _ui->colorSchemeList->setItemDelegate(delegate); _ui->transparencyWarningWidget->setVisible(false); _ui->transparencyWarningWidget->setWordWrap(true); _ui->transparencyWarningWidget->setCloseButtonVisible(false); _ui->transparencyWarningWidget->setMessageType(KMessageWidget::Warning); _ui->colorSchemeMessageWidget->setVisible(false); _ui->colorSchemeMessageWidget->setWordWrap(true); _ui->colorSchemeMessageWidget->setCloseButtonVisible(false); _ui->colorSchemeMessageWidget->setMessageType(KMessageWidget::Warning); _ui->editColorSchemeButton->setEnabled(false); _ui->removeColorSchemeButton->setEnabled(false); _ui->resetColorSchemeButton->setEnabled(false); _ui->downloadColorSchemeButton->setConfigFile(QStringLiteral("konsole.knsrc")); // setup color list // select the colorScheme used in the current profile updateColorSchemeList(currentColorSchemeName()); _ui->colorSchemeList->setMouseTracking(true); _ui->colorSchemeList->installEventFilter(this); _ui->colorSchemeList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); connect(_ui->colorSchemeList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Konsole::EditProfileDialog::colorSchemeSelected); connect(_ui->colorSchemeList, &QListView::entered, this, &Konsole::EditProfileDialog::previewColorScheme); updateColorSchemeButtons(); connect(_ui->editColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::editColorScheme); connect(_ui->removeColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::removeColorScheme); connect(_ui->newColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::newColorScheme); connect(_ui->downloadColorSchemeButton, &KNS3::Button::dialogFinished, this, &Konsole::EditProfileDialog::gotNewColorSchemes); connect(_ui->resetColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::resetColorScheme); // setup font preview const bool antialias = profile->antiAliasFonts(); QFont profileFont = profile->font(); profileFont.setStyleStrategy(antialias ? QFont::PreferAntialias : QFont::NoAntialias); _ui->fontPreviewLabel->installEventFilter(this); _ui->fontPreviewLabel->setFont(profileFont); setFontInputValue(profileFont); // Always set to unchecked _ui->showAllFontsButton->setChecked(false); connect(_ui->showAllFontsButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::showAllFontsButtonWarning); connect(_ui->fontSizeInput, static_cast(&QDoubleSpinBox::valueChanged), this, &Konsole::EditProfileDialog::setFontSize); connect(_ui->selectFontButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::showFontDialog); // setup font smoothing _ui->antialiasTextButton->setChecked(antialias); connect(_ui->antialiasTextButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setAntialiasText); _ui->boldIntenseButton->setChecked(profile->boldIntense()); connect(_ui->boldIntenseButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setBoldIntense); _ui->useFontLineCharactersButton->setChecked(profile->useFontLineCharacters()); connect(_ui->useFontLineCharactersButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::useFontLineCharacters); _ui->enableMouseWheelZoomButton->setChecked(profile->mouseWheelZoomEnabled()); connect(_ui->enableMouseWheelZoomButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::toggleMouseWheelZoom); } void EditProfileDialog::showAllFontsButtonWarning(bool enable) { if (enable) { KMessageBox::information(this, QStringLiteral( "By its very nature, a terminal program requires font characters that are equal width (monospace). Any non monospaced font may cause display issues. This should not be necessary except in rare cases."), QStringLiteral("Warning")); } } void EditProfileDialog::setAntialiasText(bool enable) { QFont profileFont = _ui->fontPreviewLabel->font(); profileFont.setStyleStrategy(enable ? QFont::PreferAntialias : QFont::NoAntialias); // update preview to reflect text smoothing state fontSelected(profileFont); updateTempProfileProperty(Profile::AntiAliasFonts, enable); } 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::toggleMouseWheelZoom(bool enable) { updateTempProfileProperty(Profile::MouseWheelZoomEnabled, enable); } void EditProfileDialog::toggleAlternateScrolling(bool enable) { updateTempProfileProperty(Profile::AlternateScrolling, enable); } void EditProfileDialog::updateColorSchemeList(const QString &selectedColorSchemeName) { if (_ui->colorSchemeList->model() == nullptr) { _ui->colorSchemeList->setModel(new QStandardItemModel(this)); } const ColorScheme *selectedColorScheme = ColorSchemeManager::instance()->findColorScheme(selectedColorSchemeName); QStandardItemModel *model = qobject_cast(_ui->colorSchemeList->model()); Q_ASSERT(model); model->clear(); QStandardItem *selectedItem = nullptr; QList schemeList = ColorSchemeManager::instance()->allColorSchemes(); foreach (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) { _ui->colorSchemeList->updateGeometry(); _ui->colorSchemeList->selectionModel()->setCurrentIndex(selectedItem->index(), QItemSelectionModel::Select); // update transparency warning label updateTransparencyWarning(); } } void EditProfileDialog::updateKeyBindingsList(const QString &selectKeyBindingsName) { if (_ui->keyBindingList->model() == nullptr) { _ui->keyBindingList->setModel(new QStandardItemModel(this)); } QStandardItemModel *model = qobject_cast(_ui->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) { _ui->keyBindingList->selectionModel()->setCurrentIndex(selectedItem->index(), QItemSelectionModel::Select); } } bool EditProfileDialog::eventFilter(QObject *watched, QEvent *event) { if (watched == _ui->colorSchemeList && event->type() == QEvent::Leave) { if (_tempProfile->isPropertySet(Profile::ColorScheme)) { preview(Profile::ColorScheme, _tempProfile->colorScheme()); } else { unpreview(Profile::ColorScheme); } } if (watched == _ui->fontPreviewLabel && event->type() == QEvent::FontChange) { const QFont &labelFont = _ui->fontPreviewLabel->font(); _ui->fontPreviewLabel->setText(i18n("%1", labelFont.family())); } return QDialog::eventFilter(watched, event); } 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::removeColorScheme() { QModelIndexList selected = _ui->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, [=](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) { _ui->colorSchemeList->model()->removeRow(selected.first().row()); } manager->deleteLater(); }); manager->checkForInstalled(); } void EditProfileDialog::gotNewColorSchemes(const KNS3::Entry::List &changedEntries) { int failures = 0; for (auto entry : 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()) { _ui->colorSchemeMessageWidget->setText( xi18nc("@info", "Scheme %1 failed to load.", entry.name())); _ui->colorSchemeMessageWidget->animatedShow(); QTimer::singleShot(8000, _ui->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 = _ui->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 = _ui->colorSchemeList->selectionModel()->selectedIndexes(); QAbstractItemModel *model = _ui->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 = _ui->colorSchemeList->selectionModel()->selectedIndexes(); if (!selected.isEmpty()) { QAbstractItemModel *model = _ui->colorSchemeList->model(); const ColorScheme *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(_ui->editColorSchemeButton, _ui->colorSchemeList->selectionModel()); QModelIndexList selected = _ui->colorSchemeList->selectionModel()->selectedIndexes(); if (!selected.isEmpty()) { const QString &name = selected.first().data(Qt::UserRole + 1).value()->name(); bool isResettable = ColorSchemeManager::instance()->canResetColorScheme(name); _ui->resetColorSchemeButton->setEnabled(isResettable); bool isDeletable = ColorSchemeManager::instance()->isColorSchemeDeletable(name); // if a colorScheme can be restored then it can't be deleted _ui->removeColorSchemeButton->setEnabled(isDeletable && !isResettable); } else { _ui->removeColorSchemeButton->setEnabled(false); _ui->resetColorSchemeButton->setEnabled(false); } } void EditProfileDialog::updateKeyBindingsButtons() { QModelIndexList selected = _ui->keyBindingList->selectionModel()->selectedIndexes(); if (!selected.isEmpty()) { _ui->editKeyBindingsButton->setEnabled(true); const QString &name = selected.first().data(Qt::UserRole + 1).value()->name(); bool isResettable = _keyManager->isTranslatorResettable(name); _ui->resetKeyBindingsButton->setEnabled(isResettable); bool isDeletable = _keyManager->isTranslatorDeletable(name); // if a key bindings scheme can be reset then it can't be deleted _ui->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 foreach (const QModelIndex &index, _ui->colorSchemeList->selectionModel()->selectedIndexes()) { bool needTransparency = index.data(Qt::UserRole + 1).value()->opacity() < 1.0; if (!needTransparency) { _ui->transparencyWarningWidget->setHidden(true); } else if (!KWindowSystem::compositingActive()) { _ui->transparencyWarningWidget->setText(i18n( "This color scheme uses a transparent background" " which does not appear to be supported on your" " desktop")); _ui->transparencyWarningWidget->setHidden(false); } else if (!WindowSystemInfo::HAVE_TRANSPARENCY) { _ui->transparencyWarningWidget->setText(i18n( "Konsole was started before desktop effects were enabled." " You need to restart Konsole to see transparent background.")); _ui->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; } } mButtonBox->button(QDialogButtonBox::Apply)->setEnabled(userModified); } void EditProfileDialog::setupKeyboardPage(const Profile::Ptr /* profile */) { // setup translator list updateKeyBindingsList(lookupProfile()->keyBindings()); connect(_ui->keyBindingList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Konsole::EditProfileDialog::keyBindingSelected); connect(_ui->newKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::newKeyBinding); _ui->editKeyBindingsButton->setEnabled(false); _ui->removeKeyBindingsButton->setEnabled(false); _ui->resetKeyBindingsButton->setEnabled(false); updateKeyBindingsButtons(); connect(_ui->editKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::editKeyBinding); connect(_ui->removeKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::removeKeyBinding); connect(_ui->resetKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::resetKeyBindings); } void EditProfileDialog::keyBindingSelected() { QModelIndexList selected = _ui->keyBindingList->selectionModel()->selectedIndexes(); if (!selected.isEmpty()) { QAbstractItemModel *model = _ui->keyBindingList->model(); const KeyboardTranslator *translator = model->data(selected.first(), Qt::UserRole + 1) .value(); if (translator != nullptr) { updateTempProfileProperty(Profile::KeyBindings, translator->name()); } } updateKeyBindingsButtons(); } void EditProfileDialog::removeKeyBinding() { QModelIndexList selected = _ui->keyBindingList->selectionModel()->selectedIndexes(); if (!selected.isEmpty()) { const QString &name = selected.first().data(Qt::UserRole + 1).value()->name(); if (KeyboardTranslatorManager::instance()->deleteTranslator(name)) { _ui->keyBindingList->model()->removeRow(selected.first().row()); } } } void EditProfileDialog::showKeyBindingEditor(bool isNewTranslator) { QModelIndexList selected = _ui->keyBindingList->selectionModel()->selectedIndexes(); QAbstractItemModel *model = _ui->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 = _ui->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::setupScrollingPage(const Profile::Ptr profile) { // setup scrollbar radio int scrollBarPosition = profile->property(Profile::ScrollBarPosition); const auto positions = QVector{ {_ui->scrollBarHiddenButton, Enum::ScrollBarHidden, SLOT(hideScrollBar())}, {_ui->scrollBarLeftButton, Enum::ScrollBarLeft, SLOT(showScrollBarLeft())}, {_ui->scrollBarRightButton, Enum::ScrollBarRight, SLOT(showScrollBarRight())}}; setupRadio(positions, scrollBarPosition); // setup scrollback type radio int scrollBackType = profile->property(Profile::HistoryMode); _ui->historySizeWidget->setMode(Enum::HistoryModeEnum(scrollBackType)); connect(_ui->historySizeWidget, &Konsole::HistorySizeWidget::historyModeChanged, this, &Konsole::EditProfileDialog::historyModeChanged); // setup scrollback line count spinner const int historySize = profile->historySize(); _ui->historySizeWidget->setLineCount(historySize); // setup scrollpageamount type radio int scrollFullPage = profile->property(Profile::ScrollFullPage); const auto pageamounts = QVector{ {_ui->scrollHalfPage, Enum::ScrollPageHalf, SLOT(scrollHalfPage())}, {_ui->scrollFullPage, Enum::ScrollPageFull, SLOT(scrollFullPage())} }; setupRadio(pageamounts, scrollFullPage); // signals and slots connect(_ui->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::hideScrollBar() { updateTempProfileProperty(Profile::ScrollBarPosition, Enum::ScrollBarHidden); } void EditProfileDialog::showScrollBarLeft() { updateTempProfileProperty(Profile::ScrollBarPosition, Enum::ScrollBarLeft); } void EditProfileDialog::showScrollBarRight() { updateTempProfileProperty(Profile::ScrollBarPosition, Enum::ScrollBarRight); } 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{ { _ui->underlineLinksButton, Profile::UnderlineLinksEnabled, SLOT(toggleUnderlineLinks(bool)) }, { _ui->underlineFilesButton, Profile::UnderlineFilesEnabled, SLOT(toggleUnderlineFiles(bool)) }, { _ui->ctrlRequiredForDragButton, Profile::CtrlRequiredForDrag, SLOT(toggleCtrlRequiredForDrag(bool)) }, { _ui->copyTextAsHTMLButton, Profile::CopyTextAsHTML, SLOT(toggleCopyTextAsHTML(bool)) }, { _ui->copyTextToClipboardButton, Profile::AutoCopySelectedText, SLOT(toggleCopyTextToClipboard(bool)) }, { _ui->trimLeadingSpacesButton, Profile::TrimLeadingSpacesInSelectedText, SLOT(toggleTrimLeadingSpacesInSelectedText(bool)) }, { _ui->trimTrailingSpacesButton, Profile::TrimTrailingSpacesInSelectedText, SLOT(toggleTrimTrailingSpacesInSelectedText(bool)) }, { _ui->openLinksByDirectClickButton, Profile::OpenLinksByDirectClickEnabled, SLOT(toggleOpenLinksByDirectClick(bool)) }, { _ui->dropUrlsAsText, Profile::DropUrlsAsText, SLOT(toggleDropUrlsAsText(bool)) }, { _ui->enableAlternateScrollingButton, Profile::AlternateScrolling, SLOT(toggleAlternateScrolling(bool)) } }; setupCheckBoxes(options, profile); // setup middle click paste mode const int middleClickPasteMode = profile->property(Profile::MiddleClickPasteMode); const auto pasteModes = QVector { {_ui->pasteFromX11SelectionButton, Enum::PasteFromX11Selection, SLOT(pasteFromX11Selection())}, {_ui->pasteFromClipboardButton, Enum::PasteFromClipboard, SLOT(pasteFromClipboard())} }; setupRadio(pasteModes, middleClickPasteMode); // interaction options _ui->wordCharacterEdit->setText(profile->wordCharacters()); connect(_ui->wordCharacterEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::wordCharactersChanged); int tripleClickMode = profile->property(Profile::TripleClickMode); _ui->tripleClickModeCombo->setCurrentIndex(tripleClickMode); connect(_ui->tripleClickModeCombo, static_cast(&KComboBox::activated), this, &Konsole::EditProfileDialog::TripleClickModeChanged); _ui->openLinksByDirectClickButton->setEnabled(_ui->underlineLinksButton->isChecked() || _ui->underlineFilesButton->isChecked()); _ui->enableMouseWheelZoomButton->setChecked(profile->mouseWheelZoomEnabled()); connect(_ui->enableMouseWheelZoomButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::toggleMouseWheelZoom); } void EditProfileDialog::setupAdvancedPage(const Profile::Ptr profile) { const auto options = QVector{ { _ui->enableBlinkingTextButton, Profile::BlinkingTextEnabled, SLOT(toggleBlinkingText(bool)) }, { _ui->enableFlowControlButton, Profile::FlowControlEnabled, SLOT(toggleFlowControl(bool)) }, { _ui->enableBlinkingCursorButton, Profile::BlinkingCursorEnabled, SLOT(toggleBlinkingCursor(bool)) }, { _ui->enableBidiRenderingButton, Profile::BidiRenderingEnabled, SLOT(togglebidiRendering(bool)) }, { _ui->enableReverseUrlHints, Profile::ReverseUrlHints, SLOT(toggleReverseUrlHints(bool)) } }; setupCheckBoxes(options, profile); // Setup the URL hints modifier checkboxes { int modifiers = profile->property(Profile::UrlHintsModifiers); _ui->urlHintsModifierShift->setChecked((modifiers &Qt::ShiftModifier) != 0u); _ui->urlHintsModifierCtrl->setChecked((modifiers &Qt::ControlModifier) != 0u); _ui->urlHintsModifierAlt->setChecked((modifiers &Qt::AltModifier) != 0u); _ui->urlHintsModifierMeta->setChecked((modifiers &Qt::MetaModifier) != 0u); connect(_ui->urlHintsModifierShift, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier); connect(_ui->urlHintsModifierCtrl, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier); connect(_ui->urlHintsModifierAlt, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier); connect(_ui->urlHintsModifierMeta, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier); } const int lineSpacing = profile->lineSpacing(); _ui->lineSpacingSpinner->setValue(lineSpacing); connect(_ui->lineSpacingSpinner, static_cast(&QSpinBox::valueChanged), this, &Konsole::EditProfileDialog::lineSpacingChanged); // cursor options if (profile->useCustomCursorColor()) { _ui->customCursorColorButton->setChecked(true); } else { _ui->autoCursorColorButton->setChecked(true); } _ui->customColorSelectButton->setColor(profile->customCursorColor()); connect(_ui->customCursorColorButton, &QRadioButton::clicked, this, &Konsole::EditProfileDialog::customCursorColor); connect(_ui->autoCursorColorButton, &QRadioButton::clicked, this, &Konsole::EditProfileDialog::autoCursorColor); connect(_ui->customColorSelectButton, &KColorButton::changed, this, &Konsole::EditProfileDialog::customCursorColorChanged); int shape = profile->property(Profile::CursorShape); _ui->cursorShapeCombo->setCurrentIndex(shape); connect(_ui->cursorShapeCombo, static_cast(&KComboBox::activated), this, &Konsole::EditProfileDialog::setCursorShape); // encoding options auto codecAction = new KCodecAction(this); _ui->selectEncodingButton->setMenu(codecAction->menu()); connect(codecAction, static_cast(&KCodecAction::triggered), this, &Konsole::EditProfileDialog::setDefaultCodec); _ui->characterEncodingLabel->setText(profile->defaultEncoding()); } void EditProfileDialog::setDefaultCodec(QTextCodec *codec) { QString name = QString::fromLocal8Bit(codec->name()); updateTempProfileProperty(Profile::DefaultEncoding, name); _ui->characterEncodingLabel->setText(name); } void EditProfileDialog::customCursorColorChanged(const QColor &color) { updateTempProfileProperty(Profile::CustomCursorColor, color); // ensure that custom cursor colors are enabled _ui->customCursorColorButton->click(); } void EditProfileDialog::wordCharactersChanged(const QString &text) { updateTempProfileProperty(Profile::WordCharacters, text); } void EditProfileDialog::autoCursorColor() { updateTempProfileProperty(Profile::UseCustomCursorColor, false); } void EditProfileDialog::customCursorColor() { updateTempProfileProperty(Profile::UseCustomCursorColor, true); } void EditProfileDialog::setCursorShape(int index) { updateTempProfileProperty(Profile::CursorShape, index); } void EditProfileDialog::togglebidiRendering(bool enable) { updateTempProfileProperty(Profile::BidiRenderingEnabled, enable); } void EditProfileDialog::lineSpacingChanged(int spacing) { updateTempProfileProperty(Profile::LineSpacing, spacing); } void EditProfileDialog::toggleBlinkingCursor(bool enable) { updateTempProfileProperty(Profile::BlinkingCursorEnabled, enable); } void EditProfileDialog::toggleUnderlineLinks(bool enable) { updateTempProfileProperty(Profile::UnderlineLinksEnabled, enable); bool enableClick = _ui->underlineFilesButton->isChecked() || enable; _ui->openLinksByDirectClickButton->setEnabled(enableClick); } void EditProfileDialog::toggleUnderlineFiles(bool enable) { updateTempProfileProperty(Profile::UnderlineFilesEnabled, enable); bool enableClick = _ui->underlineLinksButton->isChecked() || enable; _ui->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 (_ui->urlHintsModifierShift->isChecked()) { modifiers |= Qt::ShiftModifier; } if (_ui->urlHintsModifierCtrl->isChecked()) { modifiers |= Qt::ControlModifier; } if (_ui->urlHintsModifierAlt->isChecked()) { modifiers |= Qt::AltModifier; } if (_ui->urlHintsModifierMeta->isChecked()) { modifiers |= Qt::MetaModifier; } updateTempProfileProperty(Profile::UrlHintsModifiers, int(modifiers)); } void EditProfileDialog::toggleReverseUrlHints(bool enable) { updateTempProfileProperty(Profile::ReverseUrlHints, enable); } void EditProfileDialog::toggleBlinkingText(bool enable) { updateTempProfileProperty(Profile::BlinkingTextEnabled, enable); } void EditProfileDialog::toggleFlowControl(bool enable) { updateTempProfileProperty(Profile::FlowControlEnabled, enable); } void EditProfileDialog::fontSelected(const QFont &aFont) { QFont previewFont = aFont; setFontInputValue(aFont); _ui->fontPreviewLabel->setFont(previewFont); preview(Profile::Font, aFont); updateTempProfileProperty(Profile::Font, aFont); } void EditProfileDialog::showFontDialog() { QFont currentFont = _ui->fontPreviewLabel->font(); bool showAllFonts = _ui->showAllFontsButton->isChecked(); bool result; if (showAllFonts) { currentFont = QFontDialog::getFont(&result, currentFont, this, i18n("Select Any Font")); } else { currentFont = QFontDialog::getFont(&result, currentFont, this, i18n("Select Fixed Width Font"), QFontDialog::MonospacedFonts); } if (!result) { return; } fontSelected(currentFont); } void EditProfileDialog::setFontSize(double pointSize) { QFont newFont = _ui->fontPreviewLabel->font(); newFont.setPointSizeF(pointSize); _ui->fontPreviewLabel->setFont(newFont); preview(Profile::Font, newFont); updateTempProfileProperty(Profile::Font, newFont); } void EditProfileDialog::setFontInputValue(const QFont &aFont) { _ui->fontSizeInput->setValue(aFont.pointSizeF()); } ColorSchemeViewDelegate::ColorSchemeViewDelegate(QObject *aParent) : QAbstractItemDelegate(aParent) { } void ColorSchemeViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { const ColorScheme *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.width(sampleText); 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 QSize(width, heightForWidth); } diff --git a/src/EditProfileDialog.h b/src/EditProfileDialog.h index fdf4d34f..59880644 100644 --- a/src/EditProfileDialog.h +++ b/src/EditProfileDialog.h @@ -1,320 +1,320 @@ /* 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 // KDE #include // Konsole #include "Profile.h" #include "Enumeration.h" #include "ColorScheme.h" #include "ColorSchemeEditor.h" #include "KeyboardTranslatorManager.h" class QAbstractButton; class QItemSelectionModel; class QTextCodec; class QDialogButtonBox; namespace Ui { class EditProfileDialog; } 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 QDialog { Q_OBJECT public: /** Constructs a new dialog with the specified parent. */ explicit EditProfileDialog(QWidget *parent = nullptr); ~EditProfileDialog() Q_DECL_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(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() Q_DECL_OVERRIDE; // reimplemented void reject() Q_DECL_OVERRIDE; void apply(); protected: bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE; private Q_SLOTS: // sets up the specified tab page if necessary void preparePage(int); // 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 setIndicateActive(bool); + void setDimWhenInactive(bool); void showEnvironmentEditor(); void silenceSecondsChanged(int); // appearance page void setFontSize(double pointSize); void setFontInputValue(const QFont &); void showAllFontsButtonWarning(bool enable); void setAntialiasText(bool enable); void setBoldIntense(bool enable); void useFontLineCharacters(bool enable); void showFontDialog(); void newColorScheme(); void editColorScheme(); void saveColorScheme(const ColorScheme &scheme, bool isNewScheme); void removeColorScheme(); void gotNewColorSchemes(const KNS3::Entry::List &changedEntries); /** * 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 fontSelected(const QFont &); void toggleMouseWheelZoom(bool enable); // scrolling page void historyModeChanged(Enum::HistoryModeEnum mode); void historySizeChanged(int); void hideScrollBar(); void showScrollBarLeft(); void showScrollBarRight(); 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 lineSpacingChanged(int); void toggleBlinkingCursor(bool); void updateUrlHintsModifier(bool); void toggleReverseUrlHints(bool); void setCursorShape(int); void autoCursorColor(); void customCursorColor(); void customCursorColorChanged(const QColor &); void setDefaultCodec(QTextCodec *); // apply the first previewed changes stored up by delayedPreview() void delayedPreviewActivate(); private: Q_DISABLE_COPY(EditProfileDialog) // 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 info); // 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 selectTranslatorName void updateKeyBindingsList(const QString &selectTranslatorName = 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(); // 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); // 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::EditProfileDialog *_ui; Profile::Ptr _tempProfile; Profile::Ptr _profile; // keeps track of pages which need to be updated to match the current // profile. all elements in this vector are set to true when the // profile is changed and individual elements are set to false // after an update by a call to ensurePageLoaded() QVector _pageNeedsUpdate; QHash _previewedProperties; QHash _delayedPreviewProperties; QTimer *_delayedPreviewTimer; ColorSchemeEditor *_colorDialog; QDialogButtonBox *mButtonBox; }; /** * 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 Q_DECL_OVERRIDE; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; }; } #endif // EDITPROFILEDIALOG_H diff --git a/src/EditProfileDialog.ui b/src/EditProfileDialog.ui index ad788a95..d69b3341 100644 --- a/src/EditProfileDialog.ui +++ b/src/EditProfileDialog.ui @@ -1,1591 +1,1591 @@ EditProfileDialog 0 0 594 536 0 0 0 0 0 0 0 true General General true 0 0 64 64 0 0 Select the icon displayed on tabs using this profile 48 48 Qt::Horizontal QSizePolicy::Fixed 20 20 Profile name: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter A descriptive name for the profile Command: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter The command to execute when new terminal sessions are created using this profile Qt::LeftToRight Initial directory: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter The initial working directory for new terminal sessions using this profile Qt::LeftToRight Choose the initial directory ... Start in same directory as current tab Environment: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Edit the list of environment variables and associated values Edit... Qt::Horizontal 81 20 Terminal Size true Columns 1 500 Rows 1 Qt::Horizontal QSizePolicy::Fixed 40 20 This will not alter any open windows. Qt::Horizontal 81 20 Qt::Horizontal QSizePolicy::Fixed 40 20 Configure Konsole->General->Use current window size on next startup must be disabled for these entries to work. true Window true Show terminal size in columns and lines in the center of window after resizing Show hint for terminal size after resizing - + - Indicate whether the window is active by changing background color + Indicate whether the window is active by dimming the colors - Change background color when window loses focus + Dim the colors when the window loses focus Qt::Vertical 20 20 Tabs Tab Titles true Tab Monitoring true Threshold for continuous silence: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter The threshold for continuous silence to be detected by Konsole 1 3600 Qt::Horizontal 20 20 Qt::Vertical 20 10 Appearance 0 1 Color Scheme && Background true Reset the selected color scheme settings to the default values Defaults Delete the selected color scheme Remove QAbstractItemView::ScrollPerPixel Get New... Create a new color scheme based upon the selected scheme New... Qt::Vertical 20 20 Edit the selected color scheme Edit... QFrame::StyledPanel QFrame::Raised Font true Preview: 1 0 KSqueezedTextLabel Qt::ElideRight Text size: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 1 4.000000000000000 999.000000000000000 1.000000000000000 Select the font used in this profile Select Font... Show all fonts instead of the monospaced fonts Show All Fonts Qt::Horizontal 40 20 Smooth fonts Draw intense colors in bold font Use the selected font for line characters instead of the builtin code Use line characters contained in font Scrolling Scrollback true Scroll Bar true 0 0 Hide the scroll bar Hide 0 0 Show the scroll bar on the left side of the terminal window Show on left side 0 0 Show the scroll bar on the right side of the terminal window Show on right side Scroll Page Up/Down Amount true Scroll the page the half height of window Half Page Height Scroll the page the full height of window Full Page Height Qt::Vertical 20 1 Keyboard Key Bindings true Key bindings control how combinations of keystrokes in the terminal window are converted into the stream of characters that is then sent to the current terminal program. For more information on how to customize the key bindings check the Konsole Handbook. true 32 32 Create a new key bindings scheme based upon the selected bindings New... Edit the selected key bindings scheme Edit... Delete the selected key bindings scheme Remove Qt::Vertical 20 20 Reset the selected key bindings scheme to its default values Defaults Mouse Select Text true Characters considered part of a word when double clicking: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true Characters which are considered part of a word when double-clicking to select whole words in the terminal Triple-click select: Which part of current line should be selected with triple click . The whole current line From mouse position to the end of line Copy && Paste true Trim leading spaces in selected text, useful in some instances Trim leading spaces Trim trailing spaces in selected text, useful in some instances Trim trailing spaces Mouse middle button: Paste from clipboard Paste from selection Automatically copy selected text into clipboard Copy on select Copy text as HTML (including formatting, font faces, colors... etc) Copy text as HTML Miscellaneous true Text recognized as a file will be underlined when hovered by the mouse pointer. Underline files Selected text will require control key plus click to drag. Require Ctrl key for drag && drop Always paste dropped files and URLs as text without offering move, copy and link actions. Disable drag && drop menu for files && URLs Mouse scroll wheel will emulate up/down key presses in programs that use the Alternate Screen buffer (e.g. less) Enable Alternate Screen buffer scrolling Qt::Horizontal QSizePolicy::Fixed 10 20 false 0 0 Text recognized as a file, link or an email address can be opened by direct mouse click. Open by direct click Text recognized as a link or an email address will be underlined when hovered by the mouse pointer. Underline links Pressing Ctrl+scrollwheel will increase/decrease the text size. Allow Ctrl+scrollwheel to zoom text size Advanced Terminal Features true Show URL hints when these keys are pressed: Qt::Horizontal QSizePolicy::Fixed 20 20 Shift Control Alt Meta Number URL hints in reverse, starting from the bottom Reverse URL hint numbering 0 0 Allow terminal programs to create blinking sections of text Allow blinking text 0 0 Allow the output to be suspended by pressing Ctrl+S Enable flow control using Ctrl+S, Ctrl+Q 0 0 Enable Bi-Directional display on terminals (valid for Arabic, Farsi or Hebrew only) Enable Bi-Directional text rendering Line Spacing: The number of pixels between two lines 0 5 Qt::Horizontal 40 20 Cursor true 0 0 Make the cursor blink regularly Blinking cursor Cursor shape: Change the shape of the cursor Block I-Beam Underline Qt::Horizontal 40 20 0 0 Set the cursor to match the color of the character underneath it. Set cursor color to match current character 0 0 Use a custom, fixed color for the cursor Custom cursor color: 0 0 Select the color used to draw the cursor Qt::Horizontal 40 20 Encoding true Default character encoding: 0 0 DEFAULTENCODING Select Qt::Vertical 20 20 KComboBox QComboBox
kcombobox.h
KNS3::Button QPushButton
KNS3/Button
KPluralHandlingSpinBox QSpinBox
KPluralHandlingSpinBox
KColorButton QPushButton
kcolorbutton.h
KSqueezedTextLabel QLabel
ksqueezedtextlabel.h
KMessageWidget QFrame
kmessagewidget.h
1
Konsole::RenameTabWidget QWidget
RenameTabWidget.h
Konsole::HistorySizeWidget QWidget
HistorySizeWidget.h
diff --git a/src/Profile.cpp b/src/Profile.cpp index 4dff8981..e0a21c4a 100644 --- a/src/Profile.cpp +++ b/src/Profile.cpp @@ -1,395 +1,395 @@ /* 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 } - , { IndicateActiveWindow , "IndicateActiveWindow" , 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 } // 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(IndicateActiveWindow, 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("Linux")); //use DarkPastels when is start support blue ncurses UI properly 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(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(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(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); foreach(Profile::Ptr profile, _profiles) { profile->setProperty(p, value); } } diff --git a/src/Profile.h b/src/Profile.h index 379a7132..622b560a 100644 --- a/src/Profile.h +++ b/src/Profile.h @@ -1,806 +1,806 @@ /* 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 { friend class ProfileReader; friend class ProfileWriter; friend class ProfileGroup; public: typedef QExplicitlySharedDataPointer Ptr; typedef QExplicitlySharedDataPointer GroupPtr; /** * 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 */ - IndicateActiveWindow, + 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, /** (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): http://vimdoc.sourceforge.net/htmldoc/intro.html#Normal * 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 }; /** * 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(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(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::IndicateActiveWindow) */ - bool indicateActiveWindow() const + /** Convenience method for property(Profile::DimWhenInactive) */ + bool dimWhenInactive() const { - return property(Profile::IndicateActiveWindow); + 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) */ QColor customCursorColor() const { return property(Profile::CustomCursorColor); } /** 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: typedef QExplicitlySharedDataPointer Ptr; /** Construct a new profile group, which is hidden by default. */ explicit ProfileGroup(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(Profile::Ptr profile) { _profiles.append(profile); } /** Remove a profile from the group. Calling setProperty() will no longer * affect this profile. */ void removeProfile(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) Q_DECL_OVERRIDE; private: Q_DISABLE_COPY(ProfileGroup) QList _profiles; }; inline ProfileGroup::ProfileGroup(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/ViewManager.cpp b/src/ViewManager.cpp index 4cab68ac..2ff5cfd8 100644 --- a/src/ViewManager.cpp +++ b/src/ViewManager.cpp @@ -1,1135 +1,1135 @@ /* 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 "ViewManager.h" #include // Qt #include #include // KDE #include #include #include #include // Konsole #include #include "ColorScheme.h" #include "ColorSchemeManager.h" #include "Session.h" #include "TerminalDisplay.h" #include "SessionController.h" #include "SessionManager.h" #include "ProfileManager.h" #include "ViewSplitter.h" #include "Enumeration.h" #include "ViewContainer.h" using namespace Konsole; int ViewManager::lastManagerId = 0; ViewManager::ViewManager(QObject *parent, KActionCollection *collection) : QObject(parent), _viewSplitter(nullptr), _pluggedController(nullptr), _sessionMap(QHash()), _actionCollection(collection), _navigationVisibility(NavigationNotSet), _newTabBehavior(PutNewTabAtTheEnd), _managerId(0) { // create main view area _viewSplitter = new ViewSplitter(nullptr); KAcceleratorManager::setNoAccel(_viewSplitter); // the ViewSplitter class supports both recursive and non-recursive splitting, // in non-recursive mode, all containers are inserted into the same top-level splitter // widget, and all the divider lines between the containers have the same orientation // // the ViewManager class is not currently able to handle a ViewSplitter in recursive-splitting // mode _viewSplitter->setRecursiveSplitting(false); _viewSplitter->setFocusPolicy(Qt::NoFocus); // setup actions which are related to the views setupActions(); // emit a signal when all of the views held by this view manager are destroyed connect(_viewSplitter.data(), &Konsole::ViewSplitter::allContainersEmpty, this, &Konsole::ViewManager::empty); connect(_viewSplitter.data(), &Konsole::ViewSplitter::empty, this, &Konsole::ViewManager::empty); // listen for profile changes connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged, this, &Konsole::ViewManager::profileChanged); connect(SessionManager::instance(), &Konsole::SessionManager::sessionUpdated, this, &Konsole::ViewManager::updateViewsForSession); //prepare DBus communication new WindowAdaptor(this); _managerId = ++lastManagerId; QDBusConnection::sessionBus().registerObject(QLatin1String("/Windows/") + QString::number(_managerId), this); } ViewManager::~ViewManager() = default; int ViewManager::managerId() const { return _managerId; } QWidget *ViewManager::activeView() const { TabbedViewContainer *container = _viewSplitter->activeContainer(); if (container != nullptr) { return container->currentWidget(); } else { return nullptr; } } QWidget *ViewManager::widget() const { return _viewSplitter; } void ViewManager::setupActions() { Q_ASSERT(_actionCollection); if (_actionCollection == nullptr) { return; } KActionCollection *collection = _actionCollection; QAction *nextViewAction = new QAction(i18nc("@action Shortcut entry", "Next Tab"), this); QAction *previousViewAction = new QAction(i18nc("@action Shortcut entry", "Previous Tab"), this); QAction *lastViewAction = new QAction(i18nc("@action Shortcut entry", "Switch to Last Tab"), this); QAction *nextContainerAction = new QAction(i18nc("@action Shortcut entry", "Next View Container"), this); QAction *moveViewLeftAction = new QAction(i18nc("@action Shortcut entry", "Move Tab Left"), this); QAction *moveViewRightAction = new QAction(i18nc("@action Shortcut entry", "Move Tab Right"), this); // list of actions that should only be enabled when there are multiple view // containers open QList multiViewOnlyActions; multiViewOnlyActions << nextContainerAction; QAction *splitLeftRightAction = new QAction(QIcon::fromTheme(QStringLiteral("view-split-left-right")), i18nc("@action:inmenu", "Split View Left/Right"), this); collection->setDefaultShortcut(splitLeftRightAction, Konsole::ACCEL + Qt::Key_ParenLeft); collection->addAction(QStringLiteral("split-view-left-right"), splitLeftRightAction); connect(splitLeftRightAction, &QAction::triggered, this, &Konsole::ViewManager::splitLeftRight); QAction *splitTopBottomAction = new QAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")), i18nc("@action:inmenu", "Split View Top/Bottom"), this); collection->setDefaultShortcut(splitTopBottomAction, Konsole::ACCEL + Qt::Key_ParenRight); collection->addAction(QStringLiteral("split-view-top-bottom"), splitTopBottomAction); connect(splitTopBottomAction, &QAction::triggered, this, &Konsole::ViewManager::splitTopBottom); QAction *closeActiveAction = new QAction(i18nc("@action:inmenu Close Active View", "Close Active"), this); closeActiveAction->setIcon(QIcon::fromTheme(QStringLiteral("view-close"))); collection->setDefaultShortcut(closeActiveAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_X); closeActiveAction->setEnabled(false); collection->addAction(QStringLiteral("close-active-view"), closeActiveAction); connect(closeActiveAction, &QAction::triggered, this, &Konsole::ViewManager::closeActiveContainer); multiViewOnlyActions << closeActiveAction; QAction *closeOtherAction = new QAction(i18nc("@action:inmenu Close Other Views", "Close Others"), this); collection->setDefaultShortcut(closeOtherAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_O); closeOtherAction->setEnabled(false); collection->addAction(QStringLiteral("close-other-views"), closeOtherAction); connect(closeOtherAction, &QAction::triggered, this, &Konsole::ViewManager::closeOtherContainers); multiViewOnlyActions << closeOtherAction; // Expand & Shrink Active View QAction *expandActiveAction = new QAction(i18nc("@action:inmenu", "Expand View"), this); collection->setDefaultShortcut(expandActiveAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketRight); expandActiveAction->setEnabled(false); collection->addAction(QStringLiteral("expand-active-view"), expandActiveAction); connect(expandActiveAction, &QAction::triggered, this, &Konsole::ViewManager::expandActiveContainer); multiViewOnlyActions << expandActiveAction; QAction *shrinkActiveAction = new QAction(i18nc("@action:inmenu", "Shrink View"), this); collection->setDefaultShortcut(shrinkActiveAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketLeft); shrinkActiveAction->setEnabled(false); collection->addAction(QStringLiteral("shrink-active-view"), shrinkActiveAction); connect(shrinkActiveAction, &QAction::triggered, this, &Konsole::ViewManager::shrinkActiveContainer); multiViewOnlyActions << shrinkActiveAction; // Crashes on Mac. #if defined(ENABLE_DETACHING) QAction *detachViewAction = collection->addAction(QStringLiteral("detach-view")); detachViewAction->setEnabled(true); detachViewAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach"))); detachViewAction->setText(i18nc("@action:inmenu", "D&etach Current Tab")); // Ctrl+Shift+D is not used as a shortcut by default because it is too close // to Ctrl+D - which will terminate the session in many cases collection->setDefaultShortcut(detachViewAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_H); connect(this, &Konsole::ViewManager::splitViewToggle, this, &Konsole::ViewManager::updateDetachViewState); connect(detachViewAction, &QAction::triggered, this, &Konsole::ViewManager::detachActiveView); #endif // Next / Previous View , Next Container collection->addAction(QStringLiteral("next-view"), nextViewAction); collection->addAction(QStringLiteral("previous-view"), previousViewAction); collection->addAction(QStringLiteral("last-tab"), lastViewAction); collection->addAction(QStringLiteral("next-container"), nextContainerAction); collection->addAction(QStringLiteral("move-view-left"), moveViewLeftAction); collection->addAction(QStringLiteral("move-view-right"), moveViewRightAction); // Switch to tab N shortcuts const int SWITCH_TO_TAB_COUNT = 19; for (int i = 0; i < SWITCH_TO_TAB_COUNT; i++) { QAction *switchToTabAction = new QAction(i18nc("@action Shortcut entry", "Switch to Tab %1", i + 1), this); connect(switchToTabAction, &QAction::triggered, this, [this, i]() { switchToView(i); }); collection->addAction(QStringLiteral("switch-to-tab-%1").arg(i), switchToTabAction); } foreach (QAction *action, multiViewOnlyActions) { connect(this, &Konsole::ViewManager::splitViewToggle, action, &QAction::setEnabled); } // keyboard shortcut only actions const QList nextViewActionKeys{Qt::SHIFT + Qt::Key_Right, Qt::CTRL + Qt::Key_PageDown}; collection->setDefaultShortcuts(nextViewAction, nextViewActionKeys); connect(nextViewAction, &QAction::triggered, this, &Konsole::ViewManager::nextView); _viewSplitter->addAction(nextViewAction); const QList previousViewActionKeys{Qt::SHIFT + Qt::Key_Left, Qt::CTRL + Qt::Key_PageUp}; collection->setDefaultShortcuts(previousViewAction, previousViewActionKeys); connect(previousViewAction, &QAction::triggered, this, &Konsole::ViewManager::previousView); _viewSplitter->addAction(previousViewAction); collection->setDefaultShortcut(nextContainerAction, Qt::SHIFT + Qt::Key_Tab); connect(nextContainerAction, &QAction::triggered, this, &Konsole::ViewManager::nextContainer); _viewSplitter->addAction(nextContainerAction); #ifdef Q_OS_MACOS collection->setDefaultShortcut(moveViewLeftAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketLeft); #else collection->setDefaultShortcut(moveViewLeftAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Left); #endif connect(moveViewLeftAction, &QAction::triggered, this, &Konsole::ViewManager::moveActiveViewLeft); _viewSplitter->addAction(moveViewLeftAction); #ifdef Q_OS_MACOS collection->setDefaultShortcut(moveViewRightAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketRight); #else collection->setDefaultShortcut(moveViewRightAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Right); #endif connect(moveViewRightAction, &QAction::triggered, this, &Konsole::ViewManager::moveActiveViewRight); _viewSplitter->addAction(moveViewRightAction); connect(lastViewAction, &QAction::triggered, this, &Konsole::ViewManager::lastView); _viewSplitter->addAction(lastViewAction); } void ViewManager::switchToView(int index) { _viewSplitter->activeContainer()->setCurrentIndex(index); } void ViewManager::updateDetachViewState() { Q_ASSERT(_actionCollection); if (_actionCollection == nullptr) { return; } const bool splitView = _viewSplitter->containers().count() >= 2; auto activeContainer = _viewSplitter->activeContainer(); const bool shouldEnable = splitView || ((activeContainer != nullptr) && activeContainer->count() >= 2); QAction *detachAction = _actionCollection->action(QStringLiteral("detach-view")); if ((detachAction != nullptr) && shouldEnable != detachAction->isEnabled()) { detachAction->setEnabled(shouldEnable); } } void ViewManager::moveActiveViewLeft() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->moveActiveView(TabbedViewContainer::MoveViewLeft); } void ViewManager::moveActiveViewRight() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->moveActiveView(TabbedViewContainer::MoveViewRight); } void ViewManager::nextContainer() { _viewSplitter->activateNextContainer(); } void ViewManager::nextView() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->activateNextView(); } void ViewManager::previousView() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->activatePreviousView(); } void ViewManager::lastView() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->activateLastView(); } void ViewManager::detachActiveView() { // find the currently active view and remove it from its container TabbedViewContainer *container = _viewSplitter->activeContainer(); detachView(container, container->currentWidget()); } void ViewManager::detachView(TabbedViewContainer *container, QWidget *view) { #if !defined(ENABLE_DETACHING) return; #endif TerminalDisplay *viewToDetach = qobject_cast(view); if (viewToDetach == nullptr) { return; } // BR390736 - some instances are sending invalid session to viewDetached() Session *sessionToDetach = _sessionMap[viewToDetach]; if (sessionToDetach == nullptr) { return; } emit viewDetached(sessionToDetach); _sessionMap.remove(viewToDetach); // remove the view from this window container->removeView(viewToDetach); viewToDetach->deleteLater(); // if the container from which the view was removed is now empty then it can be deleted, // unless it is the only container in the window, in which case it is left empty // so that there is always an active container if (_viewSplitter->containers().count() > 1 && container->count() == 0) { removeContainer(container); } } void ViewManager::sessionFinished() { // if this slot is called after the view manager's main widget // has been destroyed, do nothing if (_viewSplitter.isNull()) { return; } Session *session = qobject_cast(sender()); Q_ASSERT(session); // close attached views QList children = _viewSplitter->findChildren(); foreach (TerminalDisplay *view, children) { if (_sessionMap[view] == session) { _sessionMap.remove(view); view->deleteLater(); } } // Only remove the controller from factory() if it's actually controlling // the session from the sender. // This fixes BUG: 348478 - messed up menus after a detached tab is closed if ((!_pluggedController.isNull()) && (_pluggedController->session() == session)) { // This is needed to remove this controller from factory() in // order to prevent BUG: 185466 - disappearing menu popup emit unplugController(_pluggedController); } } void ViewManager::viewActivated(QWidget *view) { Q_ASSERT(view != nullptr); // focus the activated view, this will cause the SessionController // to notify the world that the view has been focused and the appropriate UI // actions will be plugged in. view->setFocus(Qt::OtherFocusReason); } void ViewManager::splitLeftRight() { splitView(Qt::Horizontal); } void ViewManager::splitTopBottom() { splitView(Qt::Vertical); } void ViewManager::splitView(Qt::Orientation orientation) { TabbedViewContainer *container = createContainer(); // iterate over each session which has a view in the current active // container and create a new view for that session in a new container for(int i = 0, end = _viewSplitter->activeContainer()->count(); i < end; i++) { auto view = _viewSplitter->activeContainer()->widget(i); Session *session = _sessionMap[qobject_cast(view)]; TerminalDisplay *display = createTerminalDisplay(session); const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); applyProfileToView(display, profile); ViewProperties *properties = createController(session, display); _sessionMap[display] = session; container->addView(display, properties); session->addView(display); } _viewSplitter->addContainer(container, orientation); emit splitViewToggle(_viewSplitter->containers().count() > 0); // focus the new container container->currentWidget()->setFocus(); // ensure that the active view is focused after the split / unsplit TabbedViewContainer *activeContainer = _viewSplitter->activeContainer(); QWidget *activeView = activeContainer != nullptr ? activeContainer->currentWidget() : nullptr; if (activeView != nullptr) { activeView->setFocus(Qt::OtherFocusReason); } } void ViewManager::removeContainer(TabbedViewContainer *container) { // remove session map entries for views in this container for(int i = 0, end = container->count(); i < end; i++) { auto view = container->widget(i); TerminalDisplay *display = qobject_cast(view); Q_ASSERT(display); _sessionMap.remove(display); } _viewSplitter->removeContainer(container); container->deleteLater(); emit splitViewToggle(_viewSplitter->containers().count() > 1); } void ViewManager::expandActiveContainer() { _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(), 10); } void ViewManager::shrinkActiveContainer() { _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(), -10); } void ViewManager::closeActiveContainer() { // only do something if there is more than one container active if (_viewSplitter->containers().count() > 1) { TabbedViewContainer *container = _viewSplitter->activeContainer(); removeContainer(container); // focus next container so that user can continue typing // without having to manually focus it themselves nextContainer(); } } void ViewManager::closeOtherContainers() { TabbedViewContainer *active = _viewSplitter->activeContainer(); foreach (TabbedViewContainer *container, _viewSplitter->containers()) { if (container != active) { removeContainer(container); } } } SessionController *ViewManager::createController(Session *session, TerminalDisplay *view) { // create a new controller for the session, and ensure that this view manager // is notified when the view gains the focus auto controller = new SessionController(session, view, this); connect(controller, &Konsole::SessionController::focused, this, &Konsole::ViewManager::controllerChanged); connect(session, &Konsole::Session::destroyed, controller, &Konsole::SessionController::deleteLater); connect(session, &Konsole::Session::primaryScreenInUse, controller, &Konsole::SessionController::setupPrimaryScreenSpecificActions); connect(session, &Konsole::Session::selectionChanged, controller, &Konsole::SessionController::selectionChanged); connect(view, &Konsole::TerminalDisplay::destroyed, controller, &Konsole::SessionController::deleteLater); // if this is the first controller created then set it as the active controller if (_pluggedController.isNull()) { controllerChanged(controller); } return controller; } void ViewManager::controllerChanged(SessionController *controller) { if (controller == _pluggedController) { return; } _viewSplitter->setFocusProxy(controller->view()); _pluggedController = controller; emit activeViewChanged(controller); } SessionController *ViewManager::activeViewController() const { return _pluggedController; } void ViewManager::createView(Session *session, TabbedViewContainer *container, int index) { // notify this view manager when the session finishes so that its view // can be deleted // // Use Qt::UniqueConnection to avoid duplicate connection connect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished, Qt::UniqueConnection); TerminalDisplay *display = createTerminalDisplay(session); const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); applyProfileToView(display, profile); // set initial size const QSize &preferredSize = session->preferredSize(); display->setSize(preferredSize.width(), preferredSize.height()); ViewProperties *properties = createController(session, display); _sessionMap[display] = session; container->addView(display, properties, index); session->addView(display); // tell the session whether it has a light or dark background session->setDarkBackground(colorSchemeForProfile(profile)->hasDarkBackground()); if (container == _viewSplitter->activeContainer()) { container->setCurrentWidget(display); display->setFocus(Qt::OtherFocusReason); } updateDetachViewState(); } void ViewManager::createView(Session *session) { // create the default container if (_viewSplitter->containers().count() == 0) { TabbedViewContainer *container = createContainer(); _viewSplitter->addContainer(container, Qt::Vertical); emit splitViewToggle(false); } // new tab will be put at the end by default. int index = -1; if (_newTabBehavior == PutNewTabAfterCurrentTab) { index = _viewSplitter->activeContainer()->currentIndex() + 1; } // iterate over the view containers owned by this view manager // and create a new terminal display for the session in each of them, along with // a controller for the session/display pair foreach (TabbedViewContainer *container, _viewSplitter->containers()) { createView(session, container, index); } } TabbedViewContainer *ViewManager::createContainer() { auto *container = new TabbedViewContainer(this, _viewSplitter); container->setNavigationVisibility(_navigationVisibility); //TODO: Fix Detaching. connect(container, &TabbedViewContainer::detachTab, this, &ViewManager::detachView); // connect signals and slots connect(container, &Konsole::TabbedViewContainer::viewAdded, this, [this, container]() { containerViewsChanged(container); }); connect(container, &Konsole::TabbedViewContainer::viewRemoved, this, [this, container]() { containerViewsChanged(container); }); connect(container, static_cast(&Konsole::TabbedViewContainer::newViewRequest), this, static_cast(&Konsole::ViewManager::newViewRequest)); connect(container, static_cast(&Konsole::TabbedViewContainer::newViewRequest), this, static_cast(&Konsole::ViewManager::newViewRequest)); connect(container, &Konsole::TabbedViewContainer::moveViewRequest, this, &Konsole::ViewManager::containerMoveViewRequest); connect(container, &Konsole::TabbedViewContainer::viewRemoved, this, &Konsole::ViewManager::viewDestroyed); connect(container, &Konsole::TabbedViewContainer::activeViewChanged, this, &Konsole::ViewManager::viewActivated); return container; } void ViewManager::containerMoveViewRequest(int index, int id, TabbedViewContainer *sourceTabbedContainer) { TabbedViewContainer *container = qobject_cast(sender()); SessionController *controller = qobject_cast(ViewProperties::propertiesById(id)); if (controller == nullptr) { return; } // do not move the last tab in a split view. if (sourceTabbedContainer != nullptr) { QPointer sourceContainer = qobject_cast(sourceTabbedContainer); if (_viewSplitter->containers().contains(sourceContainer)) { return; } else { ViewManager *sourceViewManager = sourceTabbedContainer->connectedViewManager(); // do not remove the last tab on the window if (qobject_cast(sourceViewManager->widget())->containers().size() > 1) { return; } } } createView(controller->session(), container, index); controller->session()->refresh(); } void ViewManager::setNavigationMethod(NavigationMethod method) { Q_ASSERT(_actionCollection); if (_actionCollection == nullptr) { return; } KActionCollection *collection = _actionCollection; // FIXME: The following disables certain actions for the KPart that it // doesn't actually have a use for, to avoid polluting the action/shortcut // namespace of an application using the KPart (otherwise, a shortcut may // be in use twice, and the user gets to see an "ambiguous shortcut over- // load" error dialog). However, this approach sucks - it's the inverse of // what it should be. Rather than disabling actions not used by the KPart, // a method should be devised to only enable those that are used, perhaps // by using a separate action collection. const bool enable = (method != NoNavigation); auto enableAction = [&enable, &collection](const QString& actionName) { auto *action = collection->action(actionName); if (action != nullptr) { action->setEnabled(enable); } }; enableAction(QStringLiteral("next-view")); enableAction(QStringLiteral("previous-view")); enableAction(QStringLiteral("last-tab")); enableAction(QStringLiteral("split-view-left-right")); enableAction(QStringLiteral("split-view-top-bottom")); enableAction(QStringLiteral("rename-session")); enableAction(QStringLiteral("move-view-left")); enableAction(QStringLiteral("move-view-right")); } ViewManager::NavigationMethod ViewManager::navigationMethod() const { return _navigationMethod; } void ViewManager::containerViewsChanged(TabbedViewContainer *container) { if ((!_viewSplitter.isNull()) && container == _viewSplitter->activeContainer()) { emit viewPropertiesChanged(viewProperties()); } } void ViewManager::viewDestroyed(QWidget *view) { // Note: the received QWidget has already been destroyed, so // using dynamic_cast<> or qobject_cast<> does not work here // We only need the pointer address to look it up below TerminalDisplay *display = reinterpret_cast(view); // 1. detach view from session // 2. if the session has no views left, close it Session *session = _sessionMap[ display ]; _sessionMap.remove(display); if (session != nullptr) { if (session->views().count() == 0) { session->close(); } } //we only update the focus if the splitter is still alive if (!_viewSplitter.isNull()) { updateDetachViewState(); } // The below causes the menus to be messed up // Only happens when using the tab bar close button // if (_pluggedController) // emit unplugController(_pluggedController); } TerminalDisplay *ViewManager::createTerminalDisplay(Session *session) { auto display = new TerminalDisplay(nullptr); display->setRandomSeed(session->sessionId() * 31); return display; } const ColorScheme *ViewManager::colorSchemeForProfile(const Profile::Ptr profile) { const ColorScheme *colorScheme = ColorSchemeManager::instance()-> findColorScheme(profile->colorScheme()); if (colorScheme == nullptr) { colorScheme = ColorSchemeManager::instance()->defaultColorScheme(); } Q_ASSERT(colorScheme); return colorScheme; } bool ViewManager::profileHasBlurEnabled(const Profile::Ptr profile) { return colorSchemeForProfile(profile)->blur(); } void ViewManager::applyProfileToView(TerminalDisplay *view, const Profile::Ptr profile) { Q_ASSERT(profile); emit updateWindowIcon(); // load color scheme ColorEntry table[TABLE_COLORS]; const ColorScheme *colorScheme = colorSchemeForProfile(profile); colorScheme->getColorTable(table, view->randomSeed()); view->setColorTable(table); view->setOpacity(colorScheme->opacity()); view->setWallpaper(colorScheme->wallpaper()); emit blurSettingChanged(colorScheme->blur()); // load font view->setAntialias(profile->antiAliasFonts()); view->setBoldIntense(profile->boldIntense()); view->setUseFontLineCharacters(profile->useFontLineCharacters()); view->setVTFont(profile->font()); // set scroll-bar position view->setScrollBarPosition(Enum::ScrollBarPositionEnum(profile->property(Profile::ScrollBarPosition))); view->setScrollFullPage(profile->property(Profile::ScrollFullPage)); // show hint about terminal size after resizing view->setShowTerminalSizeHint(profile->showTerminalSizeHint()); - view->setDimWhenInactive(profile->indicateActiveWindow()); + view->setDimWhenInactive(profile->dimWhenInactive()); // terminal features view->setBlinkingCursorEnabled(profile->blinkingCursorEnabled()); view->setBlinkingTextEnabled(profile->blinkingTextEnabled()); view->setTripleClickMode(Enum::TripleClickModeEnum(profile->property(Profile::TripleClickMode))); view->setAutoCopySelectedText(profile->autoCopySelectedText()); view->setControlDrag(profile->property(Profile::CtrlRequiredForDrag)); view->setDropUrlsAsText(profile->property(Profile::DropUrlsAsText)); view->setBidiEnabled(profile->bidiRenderingEnabled()); view->setLineSpacing(profile->lineSpacing()); view->setTrimLeadingSpaces(profile->property(Profile::TrimLeadingSpacesInSelectedText)); view->setTrimTrailingSpaces(profile->property(Profile::TrimTrailingSpacesInSelectedText)); view->setOpenLinksByDirectClick(profile->property(Profile::OpenLinksByDirectClickEnabled)); view->setUrlHintsModifiers(profile->property(Profile::UrlHintsModifiers)); view->setReverseUrlHintsEnabled(profile->property(Profile::ReverseUrlHints)); view->setMiddleClickPasteMode(Enum::MiddleClickPasteModeEnum(profile->property(Profile::MiddleClickPasteMode))); view->setCopyTextAsHTML(profile->property(Profile::CopyTextAsHTML)); // margin/center view->setMargin(profile->property(Profile::TerminalMargin)); view->setCenterContents(profile->property(Profile::TerminalCenter)); // cursor shape view->setKeyboardCursorShape(Enum::CursorShapeEnum(profile->property(Profile::CursorShape))); // cursor color // an invalid QColor is used to inform the view widget to // draw the cursor using the default color( matching the text) view->setKeyboardCursorColor(profile->useCustomCursorColor() ? profile->customCursorColor() : QColor()); // word characters view->setWordCharacters(profile->wordCharacters()); // bell mode view->setBellMode(profile->property(Profile::BellMode)); // mouse wheel zoom view->setMouseWheelZoom(profile->mouseWheelZoomEnabled()); view->setAlternateScrolling(profile->property(Profile::AlternateScrolling)); } void ViewManager::updateViewsForSession(Session *session) { const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); const QList sessionMapKeys = _sessionMap.keys(session); foreach (TerminalDisplay *view, sessionMapKeys) { applyProfileToView(view, profile); } } void ViewManager::profileChanged(Profile::Ptr profile) { // update all views associated with this profile QHashIterator iter(_sessionMap); while (iter.hasNext()) { iter.next(); // if session uses this profile, update the display if (iter.key() != nullptr && iter.value() != nullptr && SessionManager::instance()->sessionProfile(iter.value()) == profile) { applyProfileToView(iter.key(), profile); } } } QList ViewManager::viewProperties() const { QList list; TabbedViewContainer *container = _viewSplitter->activeContainer(); if (container == nullptr) { return {}; } list.reserve(container->count()); for(int i = 0, end = container->count(); i < end; i++) { auto view = container->widget(i); ViewProperties *properties = container->viewProperties(view); Q_ASSERT(properties); list << properties; } return list; } void ViewManager::saveSessions(KConfigGroup &group) { // find all unique session restore IDs QList ids; QSet unique; int tab = 1; TabbedViewContainer *container = _viewSplitter->activeContainer(); // first: sessions in the active container, preserving the order Q_ASSERT(container); if (container == nullptr) { return; } ids.reserve(container->count()); TerminalDisplay *activeview = qobject_cast(container->currentWidget()); for (int i = 0, end = container->count(); i < end; i++) { TerminalDisplay *view = qobject_cast(container->widget(i)); Q_ASSERT(view); Session *session = _sessionMap[view]; ids << SessionManager::instance()->getRestoreId(session); unique.insert(session); if (view == activeview) { group.writeEntry("Active", tab); } tab++; } // second: all other sessions, in random order // we don't want to have sessions restored that are not connected foreach (Session *session, _sessionMap) { if (!unique.contains(session)) { ids << SessionManager::instance()->getRestoreId(session); unique.insert(session); } } group.writeEntry("Sessions", ids); } void ViewManager::restoreSessions(const KConfigGroup &group) { QList ids = group.readEntry("Sessions", QList()); int activeTab = group.readEntry("Active", 0); TerminalDisplay *display = nullptr; int tab = 1; foreach (int id, ids) { Session *session = SessionManager::instance()->idToSession(id); if (session == nullptr) { qWarning() << "Unable to load session with id" << id; // Force a creation of a default session below ids.clear(); break; } createView(session); if (!session->isRunning()) { session->run(); } if (tab++ == activeTab) { display = qobject_cast(activeView()); } } if (display != nullptr) { _viewSplitter->activeContainer()->setCurrentWidget(display); display->setFocus(Qt::OtherFocusReason); } if (ids.isEmpty()) { // Session file is unusable, start default Profile Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); Session *session = SessionManager::instance()->createSession(profile); createView(session); if (!session->isRunning()) { session->run(); } } } int ViewManager::sessionCount() { return _sessionMap.size(); } QStringList ViewManager::sessionList() { QStringList ids; QHash::const_iterator i; for (i = _sessionMap.constBegin(); i != _sessionMap.constEnd(); ++i) { ids.append(QString::number(i.value()->sessionId())); } return ids; } int ViewManager::currentSession() { QHash::const_iterator i; for (i = _sessionMap.constBegin(); i != _sessionMap.constEnd(); ++i) { if (i.key()->isVisible()) { return i.value()->sessionId(); } } return -1; } void ViewManager::setCurrentSession(int sessionId) { QHash::const_iterator i; for (i = _sessionMap.constBegin(); i != _sessionMap.constEnd(); ++i) { if (i.value()->sessionId() == sessionId) { TabbedViewContainer *container = _viewSplitter->activeContainer(); if (container != nullptr) { container->setCurrentWidget(i.key()); } } } } int ViewManager::newSession() { Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); Session *session = SessionManager::instance()->createSession(profile); session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); createView(session); session->run(); return session->sessionId(); } int ViewManager::newSession(const QString &profile) { const QList profilelist = ProfileManager::instance()->allProfiles(); Profile::Ptr profileptr = ProfileManager::instance()->defaultProfile(); for (const auto &i : profilelist) { if (i->name() == profile) { profileptr = i; break; } } Session *session = SessionManager::instance()->createSession(profileptr); session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); createView(session); session->run(); return session->sessionId(); } int ViewManager::newSession(const QString &profile, const QString &directory) { const QList profilelist = ProfileManager::instance()->allProfiles(); Profile::Ptr profileptr = ProfileManager::instance()->defaultProfile(); for (const auto &i : profilelist) { if (i->name() == profile) { profileptr = i; break; } } Session *session = SessionManager::instance()->createSession(profileptr); session->setInitialWorkingDirectory(directory); session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); createView(session); session->run(); return session->sessionId(); } QString ViewManager::defaultProfile() { return ProfileManager::instance()->defaultProfile()->name(); } QStringList ViewManager::profileList() { return ProfileManager::instance()->availableProfileNames(); } void ViewManager::nextSession() { nextView(); } void ViewManager::prevSession() { previousView(); } void ViewManager::moveSessionLeft() { moveActiveViewLeft(); } void ViewManager::moveSessionRight() { moveActiveViewRight(); } void ViewManager::setTabWidthToText(bool setTabWidthToText) { for(auto container : _viewSplitter->containers()) { container->tabBar()->setExpanding(!setTabWidthToText); container->tabBar()->update(); } } void ViewManager::setNavigationVisibility(NavigationVisibility navigationVisibility) { if (_navigationVisibility != navigationVisibility) { _navigationVisibility = navigationVisibility; for(auto *container : _viewSplitter->containers()) { container->setNavigationVisibility(navigationVisibility); } } } void ViewManager::setNavigationBehavior(int behavior) { _newTabBehavior = static_cast(behavior); }