/* 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. */ // Own #include "EditProfileDialog.h" // Standard #include // Qt #include #include #include #include #include #include #include #include // KDE #include #include #include #include #include #include #include #include #include // Konsole #include "ColorScheme.h" #include "ColorSchemeEditor.h" #include "ui_EditProfileDialog.h" #include "KeyBindingEditor.h" #include "KeyboardTranslator.h" #include "SessionManager.h" #include "ShellCommand.h" #include "TabTitleFormatAction.h" // FIXME: it feels bad to include a big header file just // in order to use one of its static members. #include "TerminalDisplay.h" using namespace Konsole; EditProfileDialog::EditProfileDialog(QWidget* parent) : KDialog(parent) , _colorSchemeAnimationTimeLine(0) , _delayedPreviewTimer(new QTimer(this)) { setCaption(i18n("Edit Profile")); setButtons(KDialog::Ok | KDialog::Cancel | KDialog::Apply); // disable the apply button , since no modification has been made enableButtonApply(false); connect(this, SIGNAL(applyClicked()), this, SLOT(save())); connect(_delayedPreviewTimer, SIGNAL(timeout()), this, SLOT(delayedPreviewActivate())); _ui = new Ui::EditProfileDialog(); _ui->setupUi(mainWidget()); // TODO: Re-enable in a later KDE 4.x release when this feature works again _ui->enableResizeWindowButton->setVisible(false); // 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, SIGNAL(currentChanged(int)), this, SLOT(preparePage(int))); createTempProfile(); } EditProfileDialog::~EditProfileDialog() { delete _ui; } void EditProfileDialog::save() { if (_tempProfile->isEmpty()) return; SessionManager::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(); enableButtonApply(false); } void EditProfileDialog::reject() { unpreviewAll(); KDialog::reject(); } void EditProfileDialog::accept() { Q_ASSERT(_profile); Q_ASSERT(_tempProfile); 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.

")); return; } save(); unpreviewAll(); KDialog::accept(); } 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 += ','; // limit caption length to prevent very long window titles if (maxLength > 0 && caption.length() > maxLength) { caption += "..."; 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); setCaption(i18np("Editing profile: %2", "Editing %1 profiles: %2", group->profiles().count(), caption)); } else { setCaption(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; } 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->advancedTab) setupAdvancedPage(profile); else Q_ASSERT(false); _pageNeedsUpdate[page] = false; } // start page entry animation for color schemes if (pageWidget == _ui->appearanceTab) _colorSchemeAnimationTimeLine->start(); } void EditProfileDialog::selectProfileName() { _ui->profileNameEdit->setFocus(); _ui->profileNameEdit->selectAll(); } void EditProfileDialog::setupGeneralPage(const Profile::Ptr profile) { // basic profile options { ProfileGroup::Ptr group = profile->asGroup(); if (!group || group->profiles().count() < 2) { _ui->profileNameEdit->setText(profile->name()); _ui->profileNameEdit->setClearButtonShown(true); _ui->emptyNameWarningWidget->setVisible(profile->name().isEmpty()); _ui->emptyNameWarningWidget->setText(i18n("Profile name is empty.")); } else { _ui->profileNameEdit->setText(groupProfileNames(group, -1)); _ui->profileNameEdit->setEnabled(false); _ui->profileNameLabel->setEnabled(false); _ui->emptyNameWarningWidget->setVisible(false); } } ShellCommand command(profile->command() , profile->arguments()); _ui->commandEdit->setText(command.fullCommand()); KUrlCompletion* exeCompletion = new KUrlCompletion(KUrlCompletion::ExeCompletion); exeCompletion->setParent(this); exeCompletion->setDir(QString()); _ui->commandEdit->setCompletionObject(exeCompletion); _ui->initialDirEdit->setText(profile->defaultWorkingDirectory()); KUrlCompletion* dirCompletion = new KUrlCompletion(KUrlCompletion::DirCompletion); dirCompletion->setParent(this); _ui->initialDirEdit->setCompletionObject(dirCompletion); _ui->initialDirEdit->setClearButtonShown(true); _ui->dirSelectButton->setIcon(KIcon("folder-open")); _ui->iconSelectButton->setIcon(KIcon(profile->icon())); _ui->startInSameDirButton->setChecked(profile->property(Profile::StartInCurrentSessionDir)); // window options _ui->showTerminalSizeHintButton->setChecked(profile->property(Profile::ShowTerminalSizeHint)); _ui->saveGeometryOnExitButton->setChecked(profile->property(Profile::SaveGeometryOnExit)); // signals and slots connect(_ui->dirSelectButton, SIGNAL(clicked()), this, SLOT(selectInitialDir())); connect(_ui->iconSelectButton, SIGNAL(clicked()), this, SLOT(selectIcon())); connect(_ui->startInSameDirButton, SIGNAL(toggled(bool)), this , SLOT(startInSameDir(bool))); connect(_ui->profileNameEdit, SIGNAL(textChanged(QString)), this, SLOT(profileNameChanged(QString))); connect(_ui->initialDirEdit, SIGNAL(textChanged(QString)), this, SLOT(initialDirChanged(QString))); connect(_ui->commandEdit, SIGNAL(textChanged(QString)), this, SLOT(commandChanged(QString))); connect(_ui->environmentEditButton , SIGNAL(clicked()), this, SLOT(showEnvironmentEditor())); connect(_ui->saveGeometryOnExitButton, SIGNAL(toggled(bool)), this, SLOT(saveGeometryOnExit(bool))); connect(_ui->showTerminalSizeHintButton, SIGNAL(toggled(bool)), this, SLOT(showTerminalSizeHint(bool))); } void EditProfileDialog::showEnvironmentEditor() { const Profile::Ptr profile = lookupProfile(); QWeakPointer dialog = new KDialog(this); KTextEdit* edit = new KTextEdit(dialog.data()); QStringList currentEnvironment = profile->property(Profile::Environment); edit->setPlainText(currentEnvironment.join("\n")); edit->setToolTip(i18n("One environment variable per line")); dialog.data()->setPlainCaption(i18n("Edit Environment")); dialog.data()->setMainWidget(edit); if (dialog.data()->exec() == QDialog::Accepted) { QStringList newEnvironment = edit->toPlainText().split('\n'); updateTempProfileProperty(Profile::Environment, newEnvironment); } delete dialog.data(); } void EditProfileDialog::setupTabsPage(const Profile::Ptr profile) { // tab title format _ui->tabTitleEdit->setClearButtonShown(true); _ui->remoteTabTitleEdit->setClearButtonShown(true); _ui->tabTitleEdit->setText(profile->property(Profile::LocalTabTitleFormat)); _ui->remoteTabTitleEdit->setText( profile->property(Profile::RemoteTabTitleFormat)); // tab monitoring int silenceSeconds = profile->property(Profile::SilenceSeconds); _ui->silenceSecondsSpinner->setValue(silenceSeconds); _ui->silenceSecondsSpinner->setSuffix(ki18ncp("Unit of time", " second", " seconds")); connect(_ui->tabTitleEdit, SIGNAL(textChanged(QString)), this, SLOT(tabTitleFormatChanged(QString))); connect(_ui->remoteTabTitleEdit, SIGNAL(textChanged(QString)), this, SLOT(remoteTabTitleFormatChanged(QString))); connect(_ui->silenceSecondsSpinner, SIGNAL(valueChanged(int)), this, SLOT(silenceSecondsChanged(int))); // menus for local and remote tab title dynamic elements TabTitleFormatAction* localTabTitleAction = new TabTitleFormatAction(this); localTabTitleAction->setContext(Session::LocalTabTitle); _ui->tabTitleEditButton->setMenu(localTabTitleAction->menu()); connect(localTabTitleAction, SIGNAL(dynamicElementSelected(QString)), this, SLOT(insertTabTitleText(QString))); TabTitleFormatAction* remoteTabTitleAction = new TabTitleFormatAction(this); remoteTabTitleAction->setContext(Session::RemoteTabTitle); _ui->remoteTabTitleEditButton->setMenu(remoteTabTitleAction->menu()); connect(remoteTabTitleAction, SIGNAL(dynamicElementSelected(QString)), this, SLOT(insertRemoteTabTitleText(QString))); } void EditProfileDialog::insertTabTitleText(const QString& text) { _ui->tabTitleEdit->insert(text); } void EditProfileDialog::insertRemoteTabTitleText(const QString& text) { _ui->remoteTabTitleEdit->insert(text); } void EditProfileDialog::saveGeometryOnExit(bool save) { updateTempProfileProperty(Profile::SaveGeometryOnExit, save); } void EditProfileDialog::showTerminalSizeHint(bool show) { updateTempProfileProperty(Profile::ShowTerminalSizeHint, show); } 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(KIcon(icon)); updateTempProfileProperty(Profile::Icon, icon); } } void EditProfileDialog::profileNameChanged(const QString& text) { _ui->emptyNameWarningWidget->setVisible(text.isEmpty()); updateTempProfileProperty(Profile::Name, text); 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 KUrl url = KFileDialog::getExistingDirectoryUrl(_ui->initialDirEdit->text(), this, i18n("Select Initial Directory")); if (!url.isEmpty()) _ui->initialDirEdit->setText(url.path()); } void EditProfileDialog::setupAppearancePage(const Profile::Ptr profile) { ColorSchemeViewDelegate* delegate = new ColorSchemeViewDelegate(this); _ui->colorSchemeList->setItemDelegate(delegate); _colorSchemeAnimationTimeLine = new QTimeLine(500 , this); delegate->setEntryTimeLine(_colorSchemeAnimationTimeLine); connect(_colorSchemeAnimationTimeLine, SIGNAL(valueChanged(qreal)), this, SLOT(colorSchemeAnimationUpdate())); _ui->transparencyWarningWidget->setVisible(false); _ui->editColorSchemeButton->setEnabled(false); _ui->removeColorSchemeButton->setEnabled(false); // setup color list updateColorSchemeList(true); _ui->colorSchemeList->setMouseTracking(true); _ui->colorSchemeList->installEventFilter(this); _ui->colorSchemeList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); connect(_ui->colorSchemeList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(colorSchemeSelected())); connect(_ui->colorSchemeList, SIGNAL(entered(QModelIndex)), this, SLOT(previewColorScheme(QModelIndex))); updateColorSchemeButtons(); connect(_ui->editColorSchemeButton, SIGNAL(clicked()), this, SLOT(editColorScheme())); connect(_ui->removeColorSchemeButton, SIGNAL(clicked()), this, SLOT(removeColorScheme())); connect(_ui->newColorSchemeButton, SIGNAL(clicked()), this, SLOT(newColorScheme())); // setup font preview bool antialias = profile->property(Profile::AntiAliasFonts); QFont font = profile->font(); font.setStyleStrategy(antialias ? QFont::PreferAntialias : QFont::NoAntialias); _ui->fontPreviewLabel->installEventFilter(this); _ui->fontPreviewLabel->setFont(font); setFontSliderRange(font); setFontSliderValue(font); connect(_ui->fontSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(setFontSize(int))); connect(_ui->editFontButton, SIGNAL(clicked()), this, SLOT(showFontDialog())); // setup font smoothing _ui->antialiasTextButton->setChecked(antialias); connect(_ui->antialiasTextButton, SIGNAL(toggled(bool)), this, SLOT(setAntialiasText(bool))); bool boldIntense = profile->property(Profile::BoldIntense); _ui->boldIntenseButton->setChecked(boldIntense); connect(_ui->boldIntenseButton, SIGNAL(toggled(bool)), this, SLOT(setBoldIntense(bool))); } void EditProfileDialog::setAntialiasText(bool enable) { QFont font = _ui->fontPreviewLabel->font(); font.setStyleStrategy(enable ? QFont::PreferAntialias : QFont::NoAntialias); // update preview to reflect text smoothing state fontSelected(font); updateTempProfileProperty(Profile::AntiAliasFonts, enable); } void EditProfileDialog::setBoldIntense(bool enable) { preview(Profile::BoldIntense, enable); updateTempProfileProperty(Profile::BoldIntense, enable); } void EditProfileDialog::colorSchemeAnimationUpdate() { QAbstractItemModel* model = _ui->colorSchemeList->model(); for (int i = model->rowCount() ; i >= 0 ; i--) _ui->colorSchemeList->update(model->index(i, 0)); } void EditProfileDialog::updateColorSchemeList(bool selectCurrentScheme) { if (!_ui->colorSchemeList->model()) _ui->colorSchemeList->setModel(new QStandardItemModel(this)); const QString& name = lookupProfile()->colorScheme(); const ColorScheme* currentScheme = ColorSchemeManager::instance()->findColorScheme(name); QStandardItemModel* model = qobject_cast(_ui->colorSchemeList->model()); Q_ASSERT(model); model->clear(); QStandardItem* selectedItem = 0; 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->setFlags(item->flags()); if (currentScheme == scheme) selectedItem = item; model->appendRow(item); } model->sort(0); if (selectCurrentScheme && selectedItem) { _ui->colorSchemeList->updateGeometry(); _ui->colorSchemeList->selectionModel()->setCurrentIndex(selectedItem->index() , QItemSelectionModel::Select); // update transparency warning label updateTransparencyWarning(); } } void EditProfileDialog::updateKeyBindingsList(bool selectCurrentTranslator) { if (!_ui->keyBindingList->model()) _ui->keyBindingList->setModel(new QStandardItemModel(this)); const QString& name = lookupProfile() ->property(Profile::KeyBindings); KeyboardTranslatorManager* keyManager = KeyboardTranslatorManager::instance(); const KeyboardTranslator* currentTranslator = keyManager->findTranslator(name); QStandardItemModel* model = qobject_cast(_ui->keyBindingList->model()); Q_ASSERT(model); model->clear(); QStandardItem* selectedItem = 0; QList translatorNames = keyManager->allTranslators(); foreach ( const QString& name, translatorNames) { const KeyboardTranslator* translator = keyManager->findTranslator(name); QStandardItem* item = new QStandardItem(translator->description()); item->setData(QVariant::fromValue(translator), Qt::UserRole + 1); item->setIcon(KIcon("preferences-desktop-keyboard")); if (translator == currentTranslator) selectedItem = item; model->appendRow(item); } model->sort(0); if (selectCurrentTranslator && selectedItem) { _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(); qreal size = labelFont.pointSizeF(); QString fontSize = KGlobal::locale()->formatNumber(size, size == floor(size) ? 0 : 1); _ui->fontPreviewLabel->setText(i18n("%1, size %2", labelFont.family(), fontSize)); } return KDialog::eventFilter(watched, event); } void EditProfileDialog::unpreviewAll() { _delayedPreviewTimer->stop(); _delayedPreviewProperties.clear(); QHash map; QHashIterator iter(_previewedProperties); while (iter.hasNext()) { iter.next(); map.insert((Profile::Property)iter.key(), iter.value()); } // undo any preview changes if (!map.isEmpty()) SessionManager::instance()->changeProfile(_profile, map, false); } void EditProfileDialog::unpreview(int property) { _delayedPreviewProperties.remove(property); if (!_previewedProperties.contains(property)) return; QHash map; map.insert((Profile::Property)property, _previewedProperties[property]); SessionManager::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((Profile::Property)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((Profile::Property)property).isNull()) return; if (!_previewedProperties.contains(property)) { _previewedProperties.insert(property , original->property((Profile::Property)property)); } // temporary change to color scheme SessionManager::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()) { const QString& name = selected.first().data(Qt::UserRole + 1).value()->name(); if (ColorSchemeManager::instance()->deleteColorScheme(name)) _ui->colorSchemeList->model()->removeRow(selected.first().row()); } } void EditProfileDialog::showColorSchemeEditor(bool isNewScheme) { QModelIndexList selected = _ui->colorSchemeList->selectionModel()->selectedIndexes(); QAbstractItemModel* model = _ui->colorSchemeList->model(); const ColorScheme* colors = 0; if (!selected.isEmpty()) colors = model->data(selected.first(), Qt::UserRole + 1).value(); else colors = ColorSchemeManager::instance()->defaultColorScheme(); Q_ASSERT(colors); QWeakPointer dialog = new KDialog(this); if (isNewScheme) dialog.data()->setCaption(i18n("New Color Scheme")); else dialog.data()->setCaption(i18n("Edit Color Scheme")); ColorSchemeEditor* editor = new ColorSchemeEditor; dialog.data()->setMainWidget(editor); editor->setup(colors); if (isNewScheme) editor->setDescription(i18n("New Color Scheme")); if (dialog.data()->exec() == QDialog::Accepted) { ColorScheme* newScheme = new ColorScheme(*editor->colorScheme()); // if this is a new color scheme, pick a name based on the description if (isNewScheme) newScheme->setName(newScheme->description()); ColorSchemeManager::instance()->addColorScheme(newScheme); updateColorSchemeList(true); preview(Profile::ColorScheme, newScheme->name()); } delete dialog.data(); } void EditProfileDialog::newColorScheme() { showColorSchemeEditor(true); } void EditProfileDialog::editColorScheme() { showColorSchemeEditor(false); } 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(); updateTempProfileProperty(Profile::ColorScheme, colors->name()); previewColorScheme(selected.first()); updateTransparencyWarning(); } updateColorSchemeButtons(); } void EditProfileDialog::updateColorSchemeButtons() { enableIfNonEmptySelection(_ui->editColorSchemeButton, _ui->colorSchemeList->selectionModel()); enableIfNonEmptySelection(_ui->removeColorSchemeButton, _ui->colorSchemeList->selectionModel()); } void EditProfileDialog::updateKeyBindingsButtons() { enableIfNonEmptySelection(_ui->editKeyBindingsButton, _ui->keyBindingList->selectionModel()); enableIfNonEmptySelection(_ui->removeKeyBindingsButton, _ui->keyBindingList->selectionModel()); } 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 (! TerminalDisplay::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(int(property))) { if (value != _previewedProperties.value(int(property))) { userModified = true ; break ; } } // for not-previewed property else if ((value != _profile->property(property))) { userModified = true ; break ; } } enableButtonApply(userModified); } void EditProfileDialog::setupKeyboardPage(const Profile::Ptr /* profile */) { // setup translator list updateKeyBindingsList(true); connect(_ui->keyBindingList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(keyBindingSelected())); connect(_ui->newKeyBindingsButton, SIGNAL(clicked()), this, SLOT(newKeyBinding())); updateKeyBindingsButtons(); connect(_ui->editKeyBindingsButton, SIGNAL(clicked()), this, SLOT(editKeyBinding())); connect(_ui->removeKeyBindingsButton, SIGNAL(clicked()), this, SLOT(removeKeyBinding())); } 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(); 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 = 0; if (!selected.isEmpty()) translator = model->data(selected.first(), Qt::UserRole + 1).value(); else translator = KeyboardTranslatorManager::instance()->defaultTranslator(); Q_ASSERT(translator); QWeakPointer dialog = new KDialog(this); if (isNewTranslator) dialog.data()->setCaption(i18n("New Key Binding List")); else dialog.data()->setCaption(i18n("Edit Key Binding List")); KeyBindingEditor* editor = new KeyBindingEditor; dialog.data()->setMainWidget(editor); if (translator) editor->setup(translator); if (isNewTranslator) editor->setDescription(i18n("New Key Binding List")); if (dialog.data()->exec() == QDialog::Accepted) { KeyboardTranslator* newTranslator = new KeyboardTranslator(*editor->translator()); if (isNewTranslator) newTranslator->setName(newTranslator->description()); KeyboardTranslatorManager::instance()->addTranslator(newTranslator); updateKeyBindingsList(); const QString& currentTranslator = lookupProfile() ->property(Profile::KeyBindings); if (newTranslator->name() == currentTranslator) { updateTempProfileProperty(Profile::KeyBindings, newTranslator->name()); } } delete dialog.data(); } void EditProfileDialog::newKeyBinding() { showKeyBindingEditor(true); } void EditProfileDialog::editKeyBinding() { showKeyBindingEditor(false); } void EditProfileDialog::setupCheckBoxes(BooleanOption* options , const Profile::Ptr profile) { while (options->button != 0) { options->button->setChecked(profile->property(options->property)); connect(options->button, SIGNAL(toggled(bool)), this, options->slot); ++options; } } void EditProfileDialog::setupRadio(RadioOption* possibilities , int actual) { while (possibilities->button != 0) { if (possibilities->value == actual) possibilities->button->setChecked(true); else possibilities->button->setChecked(false); connect(possibilities->button, SIGNAL(clicked()), this, possibilities->slot); ++possibilities; } } void EditProfileDialog::setupScrollingPage(const Profile::Ptr profile) { // setup scrollbar radio int scrollBarPosition = profile->property(Profile::ScrollBarPosition); RadioOption positions[] = { {_ui->scrollBarHiddenButton, Profile::ScrollBarHidden, SLOT(hideScrollBar())}, {_ui->scrollBarLeftButton, Profile::ScrollBarLeft, SLOT(showScrollBarLeft())}, {_ui->scrollBarRightButton, Profile::ScrollBarRight, SLOT(showScrollBarRight())}, {0, 0, 0} }; setupRadio(positions , scrollBarPosition); // setup scrollback type radio int scrollBackType = profile->property(Profile::HistoryMode); RadioOption types[] = { {_ui->disableScrollbackButton, Profile::DisableHistory, SLOT(noScrollBack())}, {_ui->fixedScrollbackButton, Profile::FixedSizeHistory, SLOT(fixedScrollBack())}, {_ui->unlimitedScrollbackButton, Profile::UnlimitedHistory, SLOT(unlimitedScrollBack())}, {0, 0, 0} }; setupRadio(types , scrollBackType); // setup scrollback line count spinner int historySize = profile->property(Profile::HistorySize); _ui->scrollBackLinesSpinner->setValue(historySize); _ui->scrollBackLinesSpinner->setSingleStep(historySize / 10); _ui->scrollBackLinesSpinner->setSuffix(ki18ncp("Unit of scrollback", " line", " lines")); // signals and slots connect(_ui->scrollBackLinesSpinner, SIGNAL(valueChanged(int)), this, SLOT(scrollBackLinesChanged(int))); } void EditProfileDialog::scrollBackLinesChanged(int lineCount) { updateTempProfileProperty(Profile::HistorySize , lineCount); } void EditProfileDialog::noScrollBack() { updateTempProfileProperty(Profile::HistoryMode , Profile::DisableHistory); } void EditProfileDialog::fixedScrollBack() { updateTempProfileProperty(Profile::HistoryMode , Profile::FixedSizeHistory); } void EditProfileDialog::unlimitedScrollBack() { updateTempProfileProperty(Profile::HistoryMode , Profile::UnlimitedHistory); } void EditProfileDialog::hideScrollBar() { updateTempProfileProperty(Profile::ScrollBarPosition , Profile::ScrollBarHidden); } void EditProfileDialog::showScrollBarLeft() { updateTempProfileProperty(Profile::ScrollBarPosition , Profile::ScrollBarLeft); } void EditProfileDialog::showScrollBarRight() { updateTempProfileProperty(Profile::ScrollBarPosition , Profile::ScrollBarRight); } void EditProfileDialog::setupAdvancedPage(const Profile::Ptr profile) { BooleanOption options[] = { { _ui->enableBlinkingTextButton , Profile::BlinkingTextEnabled , SLOT(toggleBlinkingText(bool)) }, { _ui->enableFlowControlButton , Profile::FlowControlEnabled , SLOT(toggleFlowControl(bool)) }, { _ui->enableResizeWindowButton , Profile::AllowProgramsToResizeWindow , SLOT(toggleResizeWindow(bool)) }, { _ui->enableBlinkingCursorButton , Profile::BlinkingCursorEnabled , SLOT(toggleBlinkingCursor(bool)) }, { _ui->underlineLinksButton , Profile::UnderlineLinksEnabled, SLOT(toggleUnderlineLinks(bool)) }, { _ui->enableBidiRenderingButton , Profile::BidiRenderingEnabled , SLOT(togglebidiRendering(bool)) }, { 0 , Profile::Property(0) , 0 } }; setupCheckBoxes(options , profile); // interaction options _ui->wordCharacterEdit->setText(profile->property(Profile::WordCharacters)); connect(_ui->wordCharacterEdit, SIGNAL(textChanged(QString)), this, SLOT(wordCharactersChanged(QString))); int tripleClickMode = profile->property(Profile::TripleClickMode); _ui->tripleClickModeCombo->setCurrentIndex(tripleClickMode); connect(_ui->tripleClickModeCombo, SIGNAL(activated(int)), this, SLOT(TripleClickModeChanged(int))); // cursor options if (profile->property(Profile::UseCustomCursorColor)) _ui->customCursorColorButton->setChecked(true); else _ui->autoCursorColorButton->setChecked(true); _ui->customColorSelectButton->setColor(profile->property(Profile::CustomCursorColor)); connect(_ui->customCursorColorButton, SIGNAL(clicked()), this, SLOT(customCursorColor())); connect(_ui->autoCursorColorButton, SIGNAL(clicked()), this, SLOT(autoCursorColor())); connect(_ui->customColorSelectButton, SIGNAL(changed(QColor)), SLOT(customCursorColorChanged(QColor))); int shape = profile->property(Profile::CursorShape); _ui->cursorShapeCombo->setCurrentIndex(shape); connect(_ui->cursorShapeCombo, SIGNAL(activated(int)), this, SLOT(setCursorShape(int))); // encoding options QAction* codecAction = new KCodecAction(this); _ui->selectEncodingButton->setMenu(codecAction->menu()); connect(codecAction, SIGNAL(triggered(QTextCodec*)), this, SLOT(setDefaultCodec(QTextCodec*))); _ui->characterEncodingLabel->setText(profile->property(Profile::DefaultEncoding)); } void EditProfileDialog::setDefaultCodec(QTextCodec* codec) { QString name = QString(codec->name()); updateTempProfileProperty(Profile::DefaultEncoding, name); _ui->characterEncodingLabel->setText(codec->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::toggleBlinkingCursor(bool enable) { updateTempProfileProperty(Profile::BlinkingCursorEnabled, enable); } void EditProfileDialog::toggleUnderlineLinks(bool enable) { updateTempProfileProperty(Profile::UnderlineLinksEnabled, enable); } void EditProfileDialog::TripleClickModeChanged(int newValue) { updateTempProfileProperty(Profile::TripleClickMode, newValue); } void EditProfileDialog::toggleBlinkingText(bool enable) { updateTempProfileProperty(Profile::BlinkingTextEnabled, enable); } void EditProfileDialog::toggleFlowControl(bool enable) { updateTempProfileProperty(Profile::FlowControlEnabled, enable); } void EditProfileDialog::toggleResizeWindow(bool enable) { updateTempProfileProperty(Profile::AllowProgramsToResizeWindow, enable); } void EditProfileDialog::fontSelected(const QFont& font) { QFont previewFont = font; setFontSliderRange(font); setFontSliderValue(font); _ui->fontPreviewLabel->setFont(previewFont); preview(Profile::Font, font); updateTempProfileProperty(Profile::Font, font); } void EditProfileDialog::showFontDialog() { QString sampleText = QString("ell 'lL', one '1', little eye 'i', big eye"); sampleText += QString("'I', lL1iI, Zero '0', little oh 'o', big oh 'O', 0oO"); sampleText += QString("`~!@#$%^&*()_+-=[]\{}|:\";'<>?,./"); sampleText += QString("0123456789"); sampleText += QString("\nThe Quick Brown Fox Jumps Over The Lazy Dog\n"); sampleText += i18n("--- Type anything in this box ---"); QFont currentFont = _ui->fontPreviewLabel->font(); QWeakPointer dialog = new KFontDialog(this, KFontChooser::FixedFontsOnly); dialog.data()->setFont(currentFont, true); // TODO (hindenburg): When https://git.reviewboard.kde.org/r/103357 is // committed, change the below. // Use text more fitting to show font differences in a terminal QList chooserList = dialog.data()->findChildren(); if (!chooserList.isEmpty()) chooserList.at(0)->setSampleText(sampleText); connect(dialog.data(), SIGNAL(fontSelected(QFont)), this, SLOT(fontSelected(QFont))); if (dialog.data()->exec() == QDialog::Rejected) fontSelected(currentFont); delete dialog.data(); } void EditProfileDialog::setFontSize(int pointSize) { QFont newFont = _ui->fontPreviewLabel->font(); newFont.setPointSizeF(pointSize / 10.0); _ui->fontPreviewLabel->setFont(newFont); preview(Profile::Font, newFont); updateTempProfileProperty(Profile::Font, newFont); } void EditProfileDialog::setFontSliderRange(const QFont& font) { QSlider* slider = _ui->fontSizeSlider; // Minimum on the slider is 4, // Maximum is the greater of 2 times the current size and 14 slider->setRange(qMin(4 * 10, qRound(font.pointSizeF() * 10)), qMax(14 * 10, 2 * qRound(font.pointSize() * 10))); } void EditProfileDialog::setFontSliderValue(const QFont& font) { _ui->fontSizeSlider->setValue(qRound(font.pointSize() * 10)); } ColorSchemeViewDelegate::ColorSchemeViewDelegate(QObject* parent) : QAbstractItemDelegate(parent) { } void ColorSchemeViewDelegate::setEntryTimeLine(QTimeLine* timeLine) { _entryTimeLine = timeLine; } void ColorSchemeViewDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { // entry animation // // note that the translation occurs for each item drawn, but the // painter is not reset between painting items. this means that when // the items are painted in order ( as occurs when the list is first // shown ), there is a visually pleasing staggering of items as they // enter. if (_entryTimeLine != 0) { qreal value = 1.0 - _entryTimeLine->currentValue(); painter->translate(value * option.rect.width() , 0); painter->setOpacity(_entryTimeLine->currentValue()); } const ColorScheme* scheme = index.data(Qt::UserRole + 1).value(); Q_ASSERT(scheme); bool transparencyAvailable = KWindowSystem::compositingActive(); painter->setRenderHint(QPainter::Antialiasing); // draw background painter->setPen(QPen(scheme->foregroundColor() , 1)); // radial gradient for background // from a lightened version of the scheme's background color in the center to // a darker version at the outer edge QColor color = scheme->backgroundColor(); QRectF backgroundRect = QRectF(option.rect).adjusted(1.5, 1.5, -1.5, -1.5); QRadialGradient backgroundGradient(backgroundRect.center() , backgroundRect.width() / 2); backgroundGradient.setColorAt(0 , color.lighter(105)); backgroundGradient.setColorAt(1 , color.darker(115)); const int backgroundRectXRoundness = 4; const int backgroundRectYRoundness = 30; QPainterPath backgroundRectPath(backgroundRect.topLeft()); backgroundRectPath.addRoundRect(backgroundRect , backgroundRectXRoundness , backgroundRectYRoundness); if (transparencyAvailable) { painter->save(); color.setAlphaF(scheme->opacity()); painter->setCompositionMode(QPainter::CompositionMode_Source); painter->setBrush(backgroundGradient); painter->drawPath(backgroundRectPath); painter->restore(); } else { painter->setBrush(backgroundGradient); painter->drawPath(backgroundRectPath); } // draw stripe at the side using scheme's foreground color painter->setPen(QPen(Qt::NoPen)); QPainterPath path(option.rect.topLeft()); path.lineTo(option.rect.width() / 10.0 , option.rect.top()); path.lineTo(option.rect.bottomLeft()); path.lineTo(option.rect.topLeft()); painter->setBrush(scheme->foregroundColor()); painter->drawPath(path.intersected(backgroundRectPath)); // draw highlight // with a linear gradient going from translucent white to transparent QLinearGradient gradient(option.rect.topLeft() , option.rect.bottomLeft()); gradient.setColorAt(0 , QColor(255, 255, 255, 90)); gradient.setColorAt(1 , Qt::transparent); painter->setBrush(gradient); painter->drawRoundRect(backgroundRect , 4 , 30); //const bool isChecked = index.data(Qt::CheckStateRole) == Qt::Checked; const bool isSelected = option.state & QStyle::State_Selected; // draw border on selected items if (isSelected) { //|| isChecked ) static const int selectedBorderWidth = 6; painter->setBrush(QBrush(Qt::NoBrush)); QPen pen; QColor highlightColor = option.palette.highlight().color(); if (isSelected) highlightColor.setAlphaF(1.0); else highlightColor.setAlphaF(0.7); pen.setBrush(highlightColor); pen.setWidth(selectedBorderWidth); pen.setJoinStyle(Qt::MiterJoin); painter->setPen(pen); painter->drawRect(option.rect.adjusted(selectedBorderWidth / 2, selectedBorderWidth / 2, -selectedBorderWidth / 2, -selectedBorderWidth / 2)); } // draw color scheme name using scheme's foreground color QPen pen(scheme->foregroundColor()); painter->setPen(pen); painter->drawText(option.rect , Qt::AlignCenter , index.data(Qt::DisplayRole).value()); } QSize ColorSchemeViewDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const { const int width = 200; qreal colorWidth = (qreal)width / TABLE_COLORS; int margin = 5; qreal heightForWidth = (colorWidth * 2) + option.fontMetrics.height() + margin; // temporary return QSize(width, (int)heightForWidth); } #include "EditProfileDialog.moc"