1 | /* |
2 | SPDX-FileCopyrightText: KDE Developers |
3 | |
4 | SPDX-License-Identifier: LGPL-2.0-or-later |
5 | */ |
6 | |
7 | #include <QFileDialog> |
8 | #include <QTableWidgetItem> |
9 | #include <QWhatsThis> |
10 | |
11 | #include <KLocalizedString> |
12 | #include <KMessageBox> |
13 | |
14 | #include "ui_configwidget.h" |
15 | #include <utils/kateconfig.h> |
16 | #include <utils/kateglobal.h> |
17 | #include <vimode/config/configtab.h> |
18 | #include <vimode/keyparser.h> |
19 | |
20 | using namespace KateVi; |
21 | |
22 | ConfigTab::ConfigTab(QWidget *parent, Mappings *mappings) |
23 | : KateConfigPage(parent) |
24 | , m_mappings(mappings) |
25 | { |
26 | // This will let us have more separation between this page and |
27 | // the QTabWidget edge (ereslibre) |
28 | QVBoxLayout *layout = new QVBoxLayout(this); |
29 | QWidget *newWidget = new QWidget(this); |
30 | |
31 | ui = new Ui::ConfigWidget(); |
32 | ui->setupUi(newWidget); |
33 | |
34 | // Make the header take all the width in equal parts. |
35 | ui->tblNormalModeMappings->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); |
36 | ui->tblInsertModeMappings->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); |
37 | ui->tblVisualModeMappings->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); |
38 | |
39 | // What's This? help can be found in the ui file |
40 | reload(); |
41 | |
42 | // |
43 | // after initial reload, connect the stuff for the changed() signal |
44 | // |
45 | connect(sender: ui->chkViCommandsOverride, signal: &QCheckBox::toggled, context: this, slot: &ConfigTab::slotChanged); |
46 | connect(sender: ui->chkViRelLineNumbers, signal: &QCheckBox::toggled, context: this, slot: &ConfigTab::slotChanged); |
47 | connect(sender: ui->tblNormalModeMappings, signal: &QTableWidget::cellChanged, context: this, slot: &ConfigTab::slotChanged); |
48 | connect(sender: ui->btnAddNewRow, signal: &QPushButton::clicked, context: this, slot: &ConfigTab::addMappingRow); |
49 | connect(sender: ui->btnAddNewRow, signal: &QPushButton::clicked, context: this, slot: &ConfigTab::slotChanged); |
50 | connect(sender: ui->btnRemoveSelectedRows, signal: &QPushButton::clicked, context: this, slot: &ConfigTab::removeSelectedMappingRows); |
51 | connect(sender: ui->btnRemoveSelectedRows, signal: &QPushButton::clicked, context: this, slot: &ConfigTab::slotChanged); |
52 | connect(sender: ui->btnImportNormal, signal: &QPushButton::clicked, context: this, slot: &ConfigTab::importNormalMappingRow); |
53 | connect(sender: ui->btnImportNormal, signal: &QPushButton::clicked, context: this, slot: &ConfigTab::slotChanged); |
54 | |
55 | layout->addWidget(newWidget); |
56 | } |
57 | |
58 | ConfigTab::~ConfigTab() |
59 | { |
60 | delete ui; |
61 | } |
62 | |
63 | void ConfigTab::applyTab(QTableWidget *mappingsTable, Mappings::MappingMode mode) |
64 | { |
65 | m_mappings->clear(mode); |
66 | |
67 | for (int i = 0; i < mappingsTable->rowCount(); i++) { |
68 | QTableWidgetItem *from = mappingsTable->item(row: i, column: 0); |
69 | QTableWidgetItem *to = mappingsTable->item(row: i, column: 1); |
70 | QTableWidgetItem *recursive = mappingsTable->item(row: i, column: 2); |
71 | |
72 | if (from && to && recursive) { |
73 | const Mappings::MappingRecursion recursion = recursive->checkState() == Qt::Checked ? Mappings::Recursive : Mappings::NonRecursive; |
74 | m_mappings->add(mode, from: from->text(), to: to->text(), recursion); |
75 | } |
76 | } |
77 | } |
78 | |
79 | void ConfigTab::reloadTab(QTableWidget *mappingsTable, Mappings::MappingMode mode) |
80 | { |
81 | const QStringList l = m_mappings->getAll(mode); |
82 | mappingsTable->setRowCount(l.size()); |
83 | |
84 | int i = 0; |
85 | for (const QString &f : l) { |
86 | QTableWidgetItem *from = new QTableWidgetItem(KeyParser::self()->decodeKeySequence(keys: f)); |
87 | QString s = m_mappings->get(mode, from: f); |
88 | QTableWidgetItem *to = new QTableWidgetItem(KeyParser::self()->decodeKeySequence(keys: s)); |
89 | QTableWidgetItem *recursive = new QTableWidgetItem(); |
90 | recursive->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable); |
91 | const bool isRecursive = m_mappings->isRecursive(mode, from: f); |
92 | recursive->setCheckState(isRecursive ? Qt::Checked : Qt::Unchecked); |
93 | |
94 | mappingsTable->setItem(row: i, column: 0, item: from); |
95 | mappingsTable->setItem(row: i, column: 1, item: to); |
96 | mappingsTable->setItem(row: i, column: 2, item: recursive); |
97 | |
98 | i++; |
99 | } |
100 | } |
101 | |
102 | void ConfigTab::apply() |
103 | { |
104 | // nothing changed, no need to apply stuff |
105 | if (!hasChanged()) { |
106 | return; |
107 | } |
108 | m_changed = false; |
109 | |
110 | KateViewConfig::global()->configStart(); |
111 | |
112 | // General options. |
113 | KateViewConfig::global()->setValue(key: KateViewConfig::ViRelativeLineNumbers, value: ui->chkViRelLineNumbers->isChecked()); |
114 | KateViewConfig::global()->setValue(key: KateViewConfig::ViInputModeStealKeys, value: ui->chkViCommandsOverride->isChecked()); |
115 | |
116 | // Mappings. |
117 | applyTab(mappingsTable: ui->tblNormalModeMappings, mode: Mappings::NormalModeMapping); |
118 | applyTab(mappingsTable: ui->tblInsertModeMappings, mode: Mappings::InsertModeMapping); |
119 | applyTab(mappingsTable: ui->tblVisualModeMappings, mode: Mappings::VisualModeMapping); |
120 | |
121 | KateViewConfig::global()->configEnd(); |
122 | } |
123 | |
124 | void ConfigTab::reload() |
125 | { |
126 | // General options. |
127 | ui->chkViRelLineNumbers->setChecked(KateViewConfig::global()->viRelativeLineNumbers()); |
128 | ui->chkViCommandsOverride->setChecked(KateViewConfig::global()->viInputModeStealKeys()); |
129 | |
130 | // Mappings. |
131 | reloadTab(mappingsTable: ui->tblNormalModeMappings, mode: Mappings::NormalModeMapping); |
132 | reloadTab(mappingsTable: ui->tblInsertModeMappings, mode: Mappings::InsertModeMapping); |
133 | reloadTab(mappingsTable: ui->tblVisualModeMappings, mode: Mappings::VisualModeMapping); |
134 | } |
135 | |
136 | void ConfigTab::reset() |
137 | { |
138 | /* Do nothing. */ |
139 | } |
140 | |
141 | void ConfigTab::defaults() |
142 | { |
143 | /* Do nothing. */ |
144 | } |
145 | |
146 | void ConfigTab::showWhatsThis(const QString &text) |
147 | { |
148 | QWhatsThis::showText(pos: QCursor::pos(), text); |
149 | } |
150 | |
151 | void ConfigTab::addMappingRow() |
152 | { |
153 | // Pick the current widget. |
154 | QTableWidget *mappingsTable = ui->tblNormalModeMappings; |
155 | if (ui->tabMappingModes->currentIndex() == 1) { |
156 | mappingsTable = ui->tblInsertModeMappings; |
157 | } else if (ui->tabMappingModes->currentIndex() == 2) { |
158 | mappingsTable = ui->tblVisualModeMappings; |
159 | } |
160 | |
161 | // And add a new row. |
162 | int rows = mappingsTable->rowCount(); |
163 | mappingsTable->insertRow(row: rows); |
164 | QTableWidgetItem *recursive = new QTableWidgetItem(); |
165 | recursive->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable); |
166 | recursive->setCheckState(Qt::Unchecked); |
167 | mappingsTable->setItem(row: rows, column: 2, item: recursive); |
168 | mappingsTable->setCurrentCell(row: rows, column: 0); |
169 | mappingsTable->editItem(item: mappingsTable->currentItem()); |
170 | } |
171 | |
172 | void ConfigTab::removeSelectedMappingRows() |
173 | { |
174 | // Pick the current widget. |
175 | QTableWidget *mappingsTable = ui->tblNormalModeMappings; |
176 | if (ui->tabMappingModes->currentIndex() == 1) { |
177 | mappingsTable = ui->tblInsertModeMappings; |
178 | } else if (ui->tabMappingModes->currentIndex() == 2) { |
179 | mappingsTable = ui->tblVisualModeMappings; |
180 | } |
181 | |
182 | // And remove the selected rows. |
183 | const QList<QTableWidgetSelectionRange> l = mappingsTable->selectedRanges(); |
184 | for (const QTableWidgetSelectionRange &range : l) { |
185 | for (int i = 0; i < range.bottomRow() - range.topRow() + 1; i++) { |
186 | mappingsTable->removeRow(row: range.topRow()); |
187 | } |
188 | } |
189 | } |
190 | |
191 | void ConfigTab::importNormalMappingRow() |
192 | { |
193 | const QString &fileName = QFileDialog::getOpenFileName(parent: this); |
194 | |
195 | if (fileName.isEmpty()) { |
196 | return; |
197 | } |
198 | |
199 | QFile configFile(fileName); |
200 | if (!configFile.open(flags: QIODevice::ReadOnly | QIODevice::Text)) { |
201 | KMessageBox::error(parent: this, i18n("Unable to open the config file for reading." ), i18n("Unable to open file" )); |
202 | return; |
203 | } |
204 | |
205 | QTextStream stream(&configFile); |
206 | const QRegularExpression mapleader(QStringLiteral("(?:\\w:)?mapleader" )); |
207 | while (!stream.atEnd()) { |
208 | const QStringList line = stream.readLine().split(sep: QLatin1Char(' ')); |
209 | |
210 | // TODO - allow recursive mappings to be read. |
211 | if (line.size() > 2 |
212 | && (line[0] == QLatin1String("noremap" ) || line[0] == QLatin1String("no" ) || line[0] == QLatin1String("nnoremap" ) |
213 | || line[0] == QLatin1String("nn" ))) { |
214 | int rows = ui->tblNormalModeMappings->rowCount(); |
215 | ui->tblNormalModeMappings->insertRow(row: rows); |
216 | ui->tblNormalModeMappings->setItem(row: rows, column: 0, item: new QTableWidgetItem(line[1])); |
217 | ui->tblNormalModeMappings->setItem(row: rows, column: 1, item: new QTableWidgetItem(line[2])); |
218 | QTableWidgetItem *recursive = new QTableWidgetItem(); |
219 | recursive->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable); |
220 | recursive->setCheckState(Qt::Unchecked); |
221 | ui->tblNormalModeMappings->setItem(row: rows, column: 2, item: recursive); |
222 | } else if (line.size() == 4 && line[0] == QLatin1String("let" ) && line[2] == QLatin1String("=" ) && mapleader.match(subject: line[1]).hasMatch()) { |
223 | const QStringView leader = QStringView(line[3]).mid(pos: 1, n: line[3].length() - 2); |
224 | if (!leader.isEmpty()) { |
225 | m_mappings->setLeader(leader[0]); |
226 | } |
227 | } |
228 | } |
229 | } |
230 | |
231 | QString ConfigTab::name() const |
232 | { |
233 | return i18n("Vi Input Mode" ); |
234 | } |
235 | |