diff --git a/src/EditProfileDialog.h b/src/EditProfileDialog.h --- a/src/EditProfileDialog.h +++ b/src/EditProfileDialog.h @@ -35,6 +35,7 @@ #include "Enumeration.h" #include "ColorScheme.h" #include "ColorSchemeEditor.h" +#include "KeyboardTranslatorManager.h" class QAbstractButton; class QItemSelectionModel; @@ -165,6 +166,7 @@ void newKeyBinding(); void keyBindingSelected(); void removeKeyBinding(); + void resetKeyBindings(); // mouse page void toggleUnderlineFiles(bool enable); @@ -220,12 +222,18 @@ void updateColorSchemeList(const QString &selectedColorSchemeName = QString()); void updateColorSchemeButtons(); - void updateKeyBindingsList(bool selectCurrentTranslator = false); + + // 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 showKeyBindingEditor(bool isNewTranslator); void preview(int property, const QVariant &value); void delayedPreview(int property, const QVariant &value); diff --git a/src/EditProfileDialog.cpp b/src/EditProfileDialog.cpp --- a/src/EditProfileDialog.cpp +++ b/src/EditProfileDialog.cpp @@ -694,48 +694,44 @@ } } -void EditProfileDialog::updateKeyBindingsList(bool selectCurrentTranslator) +void EditProfileDialog::updateKeyBindingsList(const QString &selectKeyBindingsName) { if (_ui->keyBindingList->model() == nullptr) { _ui->keyBindingList->setModel(new QStandardItemModel(this)); } - const QString &name = lookupProfile()->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 = nullptr; - QStringList translatorNames = keyManager->allTranslators(); - foreach (const QString &translatorName, translatorNames) { - const KeyboardTranslator *translator = keyManager->findTranslator(translatorName); + 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 (translator == currentTranslator) { + if (selectKeyBindingsName == translatorName) { selectedItem = item; } model->appendRow(item); } model->sort(0); - if (selectCurrentTranslator && (selectedItem != nullptr)) { + if (selectedItem != nullptr) { _ui->keyBindingList->selectionModel()->setCurrentIndex(selectedItem->index(), QItemSelectionModel::Select); } @@ -1064,8 +1060,21 @@ void EditProfileDialog::updateKeyBindingsButtons() { - enableIfNonEmptySelection(_ui->editKeyBindingsButton, _ui->keyBindingList->selectionModel()); - enableIfNonEmptySelection(_ui->removeKeyBindingsButton, _ui->keyBindingList->selectionModel()); + 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) @@ -1142,20 +1151,26 @@ void EditProfileDialog::setupKeyboardPage(const Profile::Ptr /* profile */) { // setup translator list - updateKeyBindingsList(true); + 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() @@ -1195,60 +1210,24 @@ if (!selected.isEmpty()) { translator = model->data(selected.first(), Qt::UserRole + 1).value(); } else { - translator = KeyboardTranslatorManager::instance()->defaultTranslator(); + translator = _keyManager->defaultTranslator(); } Q_ASSERT(translator); - QPointer dialog = new QDialog(this); - auto buttonBox = new QDialogButtonBox(dialog); - buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept); - connect(buttonBox, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject); - - if (isNewTranslator) { - dialog->setWindowTitle(i18n("New Key Binding List")); - } else { - dialog->setWindowTitle(i18n("Edit Key Binding List")); - } - - auto editor = new KeyBindingEditor; + auto editor = new KeyBindingEditor(this); if (translator != nullptr) { - editor->setup(translator); - } - - if (isNewTranslator) { - editor->setDescription(i18n("New Key Binding List")); + editor->setup(translator, lookupProfile()->keyBindings(), isNewTranslator); } - auto layout = new QVBoxLayout; - layout->addWidget(editor); - layout->addWidget(buttonBox); - dialog->setLayout(layout); - // see also the size set in the KeyBindingEditor constructor - dialog->setMinimumSize(480, 430); - dialog->resize(500, 500); - - if (dialog->exec() == QDialog::Accepted) { - auto newTranslator = new KeyboardTranslator(*editor->translator()); - - if (isNewTranslator) { - newTranslator->setName(newTranslator->description()); - } - - KeyboardTranslatorManager::instance()->addTranslator(newTranslator); + connect(editor, &Konsole::KeyBindingEditor::updateKeyBindingsListRequest, + this, &Konsole::EditProfileDialog::updateKeyBindingsList); - updateKeyBindingsList(); + connect(editor, &Konsole::KeyBindingEditor::updateTempProfileKeyBindingsRequest, + this, &Konsole::EditProfileDialog::updateTempProfileProperty); - const QString ¤tTranslator = lookupProfile() - ->property(Profile::KeyBindings); - - if (newTranslator->name() == currentTranslator) { - updateTempProfileProperty(Profile::KeyBindings, newTranslator->name()); - } - } - delete dialog; + editor->exec(); } void EditProfileDialog::newKeyBinding() @@ -1261,6 +1240,21 @@ 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(BooleanOption *options, const Profile::Ptr profile) { while (options->button != nullptr) { diff --git a/src/EditProfileDialog.ui b/src/EditProfileDialog.ui --- a/src/EditProfileDialog.ui +++ b/src/EditProfileDialog.ui @@ -843,7 +843,7 @@ - Create a new key bindings list based upon the selected bindings + Create a new key bindings scheme based upon the selected bindings New... @@ -853,7 +853,7 @@ - Edit the selected key bindings list + Edit the selected key bindings scheme Edit... @@ -863,7 +863,7 @@ - Delete the selected key bindings list + Delete the selected key bindings scheme Remove @@ -883,6 +883,16 @@ + + + + Reset the selected key bindings scheme to its default values + + + Defaults + + + diff --git a/src/KeyBindingEditor.h b/src/KeyBindingEditor.h --- a/src/KeyBindingEditor.h +++ b/src/KeyBindingEditor.h @@ -21,7 +21,10 @@ #define KEYBINDINGEDITOR_H // Qt -#include +#include + +// Konsole +#include "Profile.h" class QTableWidgetItem; @@ -33,19 +36,19 @@ class KeyboardTranslator; /** - * A dialog which allows the user to edit a key bindings list + * A dialog which allows the user to edit a key bindings scheme * which maps between key combinations input by the user and * the character sequence sent to the terminal when those * combinations are pressed. * * The dialog can be initialized with the settings of an - * existing key bindings list using the setup() method. + * existing key bindings scheme using the setup() method. * * The dialog creates a copy of the supplied keyboard translator * to which any changes are applied. The modified translator * can be retrieved using the translator() method. */ -class KeyBindingEditor : public QWidget +class KeyBindingEditor : public QDialog { Q_OBJECT @@ -57,8 +60,14 @@ /** * Initializes the dialog with the bindings and other settings * from the specified @p translator. + * @p currentProfileTranslator the name of the translator set in the + * current profile + * @p isNewTranslator specifies whether the translator being edited + * is an already existing one or a newly created + * one, defaults to false. */ - void setup(const KeyboardTranslator *translator); + void setup(const KeyboardTranslator *translator, const QString ¤tProfileTranslator, + bool isNewTranslator = false); /** * Returns the modified translator describing the changes to the bindings @@ -79,7 +88,31 @@ // reimplemented to handle test area input bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE; +Q_SIGNALS: + /** + * Emitted when the user clicks the OK button to save the changes. This + * signal is connected to EditProfileDialog::updateKeyBindingsList() + * to update the key bindings list on the Keyboard tab in the Edit + * Profile dialog. + * @p translatorName is the translator that has just been edited + */ + void updateKeyBindingsListRequest(const QString &translatorName); + + /** + * Emitted when the user clicks the OK button to save the changes to + * the translator that's set in the current profile; this signal is + * connected to EditProfileDialog::updateTempProfileProperty() to + * request applying the changes to the _tempProfile. + * @p newTranslatorName is the name of the translator, that has just + * been edited/saved, and which is also the translator + * set in the current Profile + */ + void updateTempProfileKeyBindingsRequest(Profile::Property, const QString &newTranslatorName); + private Q_SLOTS: + // reimplemented + void accept() Q_DECL_OVERRIDE; + void setTranslatorDescription(const QString &description); void bindingTableItemChanged(QTableWidgetItem *item); void removeSelectedEntry(); @@ -97,6 +130,19 @@ // this is initialized as a copy of the translator specified // when setup() is called KeyboardTranslator *_translator; + + // Show only the rows that match the text entered by the user in the + // filter search box + void filterRows(const QString &text); + + bool _isNewTranslator; + + // The translator set in the current profile + QString _currentProfileTranslator; + + // Sets the size hint of the dialog to be slightly smaller than the + // size of EditProfileDialog + QSize sizeHint() const Q_DECL_OVERRIDE; }; } diff --git a/src/KeyBindingEditor.cpp b/src/KeyBindingEditor.cpp --- a/src/KeyBindingEditor.cpp +++ b/src/KeyBindingEditor.cpp @@ -16,46 +16,68 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - // Own #include "KeyBindingEditor.h" // Qt +#include +#include #include #include // KDE #include +#include // Konsole #include "ui_KeyBindingEditor.h" +#include "EditProfileDialog.h" #include "KeyboardTranslator.h" +#include "KeyboardTranslatorManager.h" using namespace Konsole; KeyBindingEditor::KeyBindingEditor(QWidget *parent) : - QWidget(parent), + QDialog(parent), _ui(nullptr), - _translator(new KeyboardTranslator(QString())) + _translator(new KeyboardTranslator(QString())), + _isNewTranslator(false) { + auto layout = new QVBoxLayout; + + auto mainWidget = new QWidget(this); + layout->addWidget(mainWidget); + + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true); + layout->addWidget(buttonBox); + + setLayout(layout); + + connect(buttonBox, &QDialogButtonBox::accepted, this, &Konsole::KeyBindingEditor::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + setAttribute(Qt::WA_DeleteOnClose); + _ui = new Ui::KeyBindingEditor(); - _ui->setupUi(this); + _ui->setupUi(mainWidget); // description edit + _ui->descriptionEdit->setPlaceholderText(i18nc("@label:textbox", "Enter descriptive label")); connect(_ui->descriptionEdit, &QLineEdit::textChanged, this, &Konsole::KeyBindingEditor::setTranslatorDescription); + // filter edit + connect(_ui->filterEdit, &QLineEdit::textChanged, this, + &Konsole::KeyBindingEditor::filterRows); // key bindings table _ui->keyBindingTable->setColumnCount(2); QStringList labels; labels << i18n("Key Combination") << i18n("Output"); _ui->keyBindingTable->setHorizontalHeaderLabels(labels); - _ui->keyBindingTable->horizontalHeader()->setStretchLastSection(true); - - // see also the sizes set in EditProfileDialog::showKeyBindingEditor() - _ui->keyBindingTable->setColumnWidth(0, 300); + _ui->keyBindingTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); _ui->keyBindingTable->verticalHeader()->hide(); _ui->keyBindingTable->setSelectionBehavior(QAbstractItemView::SelectRows); @@ -79,6 +101,25 @@ delete _translator; } +void KeyBindingEditor::filterRows(const QString &text) +{ + const int rows = _ui->keyBindingTable->rowCount(); + + QList matchedRows; + + for (QTableWidgetItem *matchedItem : _ui->keyBindingTable->findItems(text, Qt::MatchContains)) { + matchedRows.append(matchedItem->row()); + } + + for (int i = 0; i < rows; i++) { + if (matchedRows.contains(i) && _ui->keyBindingTable->isRowHidden(i)) { + _ui->keyBindingTable->showRow(i); + } else if (!matchedRows.contains(i)) { + _ui->keyBindingTable->hideRow(i); + } + } +} + void KeyBindingEditor::removeSelectedEntry() { QList uniqueList; @@ -178,15 +219,29 @@ return _ui->descriptionEdit->text(); } -void KeyBindingEditor::setup(const KeyboardTranslator *translator) +void KeyBindingEditor::setup(const KeyboardTranslator *translator, + const QString ¤tProfileTranslator, bool isNewTranslator) { delete _translator; + _isNewTranslator = isNewTranslator; + + _currentProfileTranslator = currentProfileTranslator; + _translator = new KeyboardTranslator(*translator); - // setup description edit + // setup description edit line _ui->descriptionEdit->setClearButtonEnabled(true); - _ui->descriptionEdit->setText(translator->description()); + // setup filter edit line + _ui->filterEdit->setClearButtonEnabled(true); + + if (_isNewTranslator) { + setDescription(i18n("New Key Binding List")); + setWindowTitle(i18n("New Key Binding List")); + } else { + _ui->descriptionEdit->setText(translator->description()); + setWindowTitle(i18n("Edit Key Binding List")); + } // setup key binding table setupKeyBindingTable(translator); @@ -240,3 +295,43 @@ connect(_ui->keyBindingTable, &QTableWidget::itemChanged, this, &Konsole::KeyBindingEditor::bindingTableItemChanged); } + +void KeyBindingEditor::accept() +{ + if (_translator == nullptr) { + return; + } + + const auto newTranslator = new KeyboardTranslator(*_translator); + + if (newTranslator->description().isEmpty()) { + KMessageBox::sorry(this, i18n("A key bindings scheme cannot be saved with an empty description.")); + return; + } + + if (_isNewTranslator) { + newTranslator->setName(newTranslator->description()); + } + + KeyboardTranslatorManager::instance()->addTranslator(newTranslator); + + const QString ¤tTranslatorName = newTranslator->name(); + + emit updateKeyBindingsListRequest(currentTranslatorName); + + if (currentTranslatorName == _currentProfileTranslator) { + emit updateTempProfileKeyBindingsRequest(Profile::KeyBindings, currentTranslatorName); + } + + QDialog::accept(); +} + +QSize KeyBindingEditor::sizeHint() const +{ + const auto parent = parentWidget(); + if (parent != nullptr) { + return QSize(parent->width() * 0.9, parent->height() * 0.95); + } + + return QSize(); +} diff --git a/src/KeyBindingEditor.ui b/src/KeyBindingEditor.ui --- a/src/KeyBindingEditor.ui +++ b/src/KeyBindingEditor.ui @@ -24,17 +24,27 @@ 0 - - + + Description: - + + + + + Filter: + + + + + + diff --git a/src/KeyboardTranslatorManager.h b/src/KeyboardTranslatorManager.h --- a/src/KeyboardTranslatorManager.h +++ b/src/KeyboardTranslatorManager.h @@ -68,6 +68,29 @@ */ bool deleteTranslator(const QString &name); + /** + * Checks whether a translator can be deleted or not (by checking if + * the directory containing the .keytab file is writable, because one + * can still delete a file owned by a different user if the directory + * containing it is writable for the current user). + */ + bool isTranslatorDeletable(const QString &name) const; + + /** + * Checks whether a translator can be reset to its default values. + * This is only applicable for translators that exist in two different + * locations: + * - system-wide location which is read-only for the user (typically + * /usr/share/konsole/ on Linux) + * - writable user-specific location under the user's home directory + * (typically ~/.local/share/konsole on Linux) + * + * Reseting here basically means it deletes the translator from the + * location under the user's home directory, then "reloads" it from + * the system-wide location. + */ + bool isTranslatorResettable(const QString &name) const; + /** Returns the default translator for Konsole. */ const KeyboardTranslator *defaultTranslator(); @@ -85,20 +108,22 @@ * The first time this is called, a search for available * translators is started. */ - QStringList allTranslators(); + const QStringList allTranslators(); /** Returns the global KeyboardTranslatorManager instance. */ static KeyboardTranslatorManager *instance(); + /** Returns the translator path */ + const QString findTranslatorPath(const QString &name) const; + private: void findTranslators(); // locate all available translators // loads the translator with the given name KeyboardTranslator *loadTranslator(const QString &name); KeyboardTranslator *loadTranslator(QIODevice *source, const QString &name); bool saveTranslator(const KeyboardTranslator *translator); - QString findTranslatorPath(const QString &name); bool _haveLoadedAll; diff --git a/src/KeyboardTranslatorManager.cpp b/src/KeyboardTranslatorManager.cpp --- a/src/KeyboardTranslatorManager.cpp +++ b/src/KeyboardTranslatorManager.cpp @@ -77,7 +77,20 @@ } } -QString KeyboardTranslatorManager::findTranslatorPath(const QString &name) +bool KeyboardTranslatorManager::isTranslatorDeletable(const QString &name) const +{ + const QString &dir = QFileInfo(findTranslatorPath(name)).path(); + return QFileInfo(dir).isWritable(); +} + +bool KeyboardTranslatorManager::isTranslatorResettable(const QString &name) const +{ + const QStringList &paths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("konsole/") + name + QStringLiteral(".keytab")); + + return (paths.count() > 1); +} + +const QString KeyboardTranslatorManager::findTranslatorPath(const QString &name) const { return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("konsole/") + name + QStringLiteral(".keytab")); } @@ -204,7 +217,7 @@ return translator; } -QStringList KeyboardTranslatorManager::allTranslators() +const QStringList KeyboardTranslatorManager::allTranslators() { if (!_haveLoadedAll) { findTranslators();