KeyBindingEditor.cpp 10.9 KB
Newer Older
1
/*
2
    Copyright 2008 by Robert Knight <robertknight@gmail.com>
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

    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.
*/
19 20 21
// Own
#include "KeyBindingEditor.h"

22
// Qt
23 24
#include <QDialog>
#include <QDialogButtonBox>
25 26
#include <QKeyEvent>
#include <QIcon>
Michal Humpula's avatar
Michal Humpula committed
27

Alex Richardson's avatar
Alex Richardson committed
28
// KDE
29
#include <KLocalizedString>
30
#include <KMessageBox>
31 32 33

// Konsole
#include "ui_KeyBindingEditor.h"
34
#include "EditProfileDialog.h"
35
#include "KeyboardTranslator.h"
36
#include "KeyboardTranslatorManager.h"
37 38 39

using namespace Konsole;

40
KeyBindingEditor::KeyBindingEditor(QWidget *parent) :
41
    QDialog(parent),
42
    _ui(nullptr),
43 44
    _translator(new KeyboardTranslator(QString())),
    _isNewTranslator(false)
45
{
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
    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);

62
    _ui = new Ui::KeyBindingEditor();
63
    _ui->setupUi(mainWidget);
64

65
    // description edit
66
    _ui->descriptionEdit->setPlaceholderText(i18nc("@label:textbox", "Enter descriptive label"));
67 68
    connect(_ui->descriptionEdit, &QLineEdit::textChanged, this,
            &Konsole::KeyBindingEditor::setTranslatorDescription);
69 70 71
    // filter edit
    connect(_ui->filterEdit, &QLineEdit::textChanged, this,
            &Konsole::KeyBindingEditor::filterRows);
72

73 74 75 76
    // key bindings table
    _ui->keyBindingTable->setColumnCount(2);

    QStringList labels;
77
    labels << i18n("Key Combination") << i18n("Output");
78 79

    _ui->keyBindingTable->setHorizontalHeaderLabels(labels);
80
    _ui->keyBindingTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
81

82
    _ui->keyBindingTable->verticalHeader()->hide();
83
    _ui->keyBindingTable->setSelectionBehavior(QAbstractItemView::SelectRows);
84

85
    // add and remove buttons
Michal Humpula's avatar
Michal Humpula committed
86 87
    _ui->addEntryButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
    _ui->removeEntryButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
88

89 90 91 92
    connect(_ui->removeEntryButton, &QPushButton::clicked, this,
            &Konsole::KeyBindingEditor::removeSelectedEntry);
    connect(_ui->addEntryButton, &QPushButton::clicked, this,
            &Konsole::KeyBindingEditor::addNewEntry);
93

94
    // test area
95
    _ui->testAreaInputEdit->installEventFilter(this);
96 97 98 99 100
}

KeyBindingEditor::~KeyBindingEditor()
{
    delete _ui;
Laurent Montel's avatar
Laurent Montel committed
101
    delete _translator;
102 103
}

104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
void KeyBindingEditor::filterRows(const QString &text)
{
    const int rows = _ui->keyBindingTable->rowCount();

    QList<int> 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);
        }
    }
}

123 124
void KeyBindingEditor::removeSelectedEntry()
{
125
    QList<QTableWidgetItem *> uniqueList;
126

127 128
    foreach (QTableWidgetItem *item, _ui->keyBindingTable->selectedItems()) {
        if (item->column() == 1) { //Select item at the first column
Kurt Hindenburg's avatar
Kurt Hindenburg committed
129
            item = _ui->keyBindingTable->item(item->row(), 0);
130
        }
131

132
        if (!uniqueList.contains(item)) {
133
            uniqueList.append(item);
134
        }
135
    }
136

137
    foreach (QTableWidgetItem *item, uniqueList) {
138 139 140
        // get the first item in the row which has the entry

        KeyboardTranslator::Entry existing = item->data(Qt::UserRole).
Kurt Hindenburg's avatar
Kurt Hindenburg committed
141
                                             value<KeyboardTranslator::Entry>();
142

143
        _translator->removeEntry(existing);
144

Kurt Hindenburg's avatar
Kurt Hindenburg committed
145
        _ui->keyBindingTable->removeRow(item->row());
146 147 148 149 150
    }
}

void KeyBindingEditor::addNewEntry()
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
151
    _ui->keyBindingTable->insertRow(_ui->keyBindingTable->rowCount());
152

Kurt Hindenburg's avatar
Kurt Hindenburg committed
153
    int newRowCount = _ui->keyBindingTable->rowCount();
154

Kurt Hindenburg's avatar
Kurt Hindenburg committed
155 156
    // block signals here to avoid triggering bindingTableItemChanged() slot call
    _ui->keyBindingTable->blockSignals(true);
157

Kurt Hindenburg's avatar
Kurt Hindenburg committed
158 159
    _ui->keyBindingTable->setItem(newRowCount - 1, 0, new QTableWidgetItem());
    _ui->keyBindingTable->setItem(newRowCount - 1, 1, new QTableWidgetItem());
160

Kurt Hindenburg's avatar
Kurt Hindenburg committed
161
    _ui->keyBindingTable->blockSignals(false);
162

Kurt Hindenburg's avatar
Kurt Hindenburg committed
163 164
    // make sure user can see new row
    _ui->keyBindingTable->scrollToItem(_ui->keyBindingTable->item(newRowCount - 1, 0));
165 166
}

167
bool KeyBindingEditor::eventFilter(QObject *watched, QEvent *event)
168
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
169 170
    if (watched == _ui->testAreaInputEdit) {
        if (event->type() == QEvent::KeyPress) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
171
            auto *keyEvent = static_cast<QKeyEvent *>(event);
172

Kurt Hindenburg's avatar
Kurt Hindenburg committed
173 174
            // The state here is currently set to the state that a newly started
            // terminal in Konsole will be in ( which is also the same as the
175
            // state just after a reset ), this has 'ANSI' turned on and all other
176 177
            // states off.
            //
Kurt Hindenburg's avatar
Kurt Hindenburg committed
178 179
            // TODO: It may be useful to be able to specify the state in the 'test input'
            // area, but preferably not in a way which clutters the UI with lots of
180 181
            // checkboxes.
            //
182
            const KeyboardTranslator::States states = KeyboardTranslator::AnsiState;
183

184 185 186
            KeyboardTranslator::Entry entry = _translator->findEntry(keyEvent->key(),
                                                                     keyEvent->modifiers(),
                                                                     states);
187

Kurt Hindenburg's avatar
Kurt Hindenburg committed
188
            if (!entry.isNull()) {
189
                _ui->testAreaInputEdit->setText(entry.conditionToString());
Kurt Hindenburg's avatar
Kurt Hindenburg committed
190 191
                _ui->testAreaOutputEdit->setText(entry.resultToString(true, keyEvent->modifiers()));
            } else {
192 193 194
                _ui->testAreaInputEdit->setText(keyEvent->text());
                _ui->testAreaOutputEdit->setText(keyEvent->text());
            }
195 196 197

            keyEvent->accept();
            return true;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
198
        }
199
    }
200
    return QDialog::eventFilter(watched, event);
201 202
}

203
void KeyBindingEditor::setDescription(const QString &description)
204
{
205
    _ui->descriptionEdit->setText(description);
206

207
    setTranslatorDescription(description);
208 209
}

210
void KeyBindingEditor::setTranslatorDescription(const QString &description)
211
{
212
    if (_translator != nullptr) {
213
        _translator->setDescription(description);
214
    }
215
}
216

217 218 219 220 221
QString KeyBindingEditor::description() const
{
    return _ui->descriptionEdit->text();
}

222 223
void KeyBindingEditor::setup(const KeyboardTranslator *translator,
                             const QString &currentProfileTranslator, bool isNewTranslator)
224
{
225
    delete _translator;
226

227 228 229 230
    _isNewTranslator = isNewTranslator;

    _currentProfileTranslator  = currentProfileTranslator;

231 232
    _translator = new KeyboardTranslator(*translator);

233
    // setup description edit line
Kurt Hindenburg's avatar
Kurt Hindenburg committed
234
    _ui->descriptionEdit->setClearButtonEnabled(true);
235 236 237 238 239 240 241 242 243 244
    // 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"));
    }
245 246 247 248 249

    // setup key binding table
    setupKeyBindingTable(translator);
}

250
KeyboardTranslator *KeyBindingEditor::translator() const
251 252 253 254
{
    return _translator;
}

255
void KeyBindingEditor::bindingTableItemChanged(QTableWidgetItem *item)
256
{
257
    QTableWidgetItem *key = _ui->keyBindingTable->item(item->row(), 0);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
258
    KeyboardTranslator::Entry existing = key->data(Qt::UserRole).value<KeyboardTranslator::Entry>();
259

Kurt Hindenburg's avatar
Kurt Hindenburg committed
260
    QString condition = key->text();
261
    QString result = _ui->keyBindingTable->item(item->row(), 1)->text();
262

Kurt Hindenburg's avatar
Kurt Hindenburg committed
263 264
    KeyboardTranslator::Entry entry = KeyboardTranslatorReader::createEntry(condition, result);
    _translator->replaceEntry(existing, entry);
265 266

    // block signals to prevent this slot from being called repeatedly
Kurt Hindenburg's avatar
Kurt Hindenburg committed
267
    _ui->keyBindingTable->blockSignals(true);
268

Kurt Hindenburg's avatar
Kurt Hindenburg committed
269
    key->setData(Qt::UserRole, QVariant::fromValue(entry));
270

Kurt Hindenburg's avatar
Kurt Hindenburg committed
271
    _ui->keyBindingTable->blockSignals(false);
272 273
}

274
void KeyBindingEditor::setupKeyBindingTable(const KeyboardTranslator *translator)
275
{
276
    disconnect(_ui->keyBindingTable, &QTableWidget::itemChanged, this,
277
               &Konsole::KeyBindingEditor::bindingTableItemChanged);
278

279 280
    QList<KeyboardTranslator::Entry> entries = translator->entries();
    _ui->keyBindingTable->setRowCount(entries.count());
281

282 283
    for (int row = 0; row < entries.count(); row++) {
        const KeyboardTranslator::Entry &entry = entries.at(row);
284

285 286
        QTableWidgetItem *keyItem = new QTableWidgetItem(entry.conditionToString());
        keyItem->setData(Qt::UserRole, QVariant::fromValue(entry));
287

Kurt Hindenburg's avatar
Kurt Hindenburg committed
288
        QTableWidgetItem *textItem = new QTableWidgetItem(entry.resultToString());
289

Kurt Hindenburg's avatar
Kurt Hindenburg committed
290 291
        _ui->keyBindingTable->setItem(row, 0, keyItem);
        _ui->keyBindingTable->setItem(row, 1, textItem);
292
    }
293
    _ui->keyBindingTable->sortItems(0);
294

295 296
    connect(_ui->keyBindingTable, &QTableWidget::itemChanged, this,
            &Konsole::KeyBindingEditor::bindingTableItemChanged);
297
}
298 299 300 301 302 303 304 305 306 307 308

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."));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
309
        delete newTranslator;
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
        return;
    }

    if (_isNewTranslator) {
        newTranslator->setName(newTranslator->description());
    }

    KeyboardTranslatorManager::instance()->addTranslator(newTranslator);

    const QString &currentTranslatorName = 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) {
334 335
        return {static_cast<int>(parent->width() * 0.9),
                static_cast<int>(parent->height() * 0.95)};
336 337
    }

338
    return {};
339
}