1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "shortcutsettingwidget.h"
6#include "common/common.h"
7
8#include <QtWidgets/QTableView>
9#include <QtWidgets/QLabel>
10#include <QtWidgets/QLineEdit>
11#include <QtWidgets/QPushButton>
12#include <QHeaderView>
13#include <QtWidgets/QVBoxLayout>
14#include <QtWidgets/QHBoxLayout>
15#include <QDebug>
16#include <QDir>
17#include <QFileDialog>
18
19#define BTN_WIDTH (180)
20
21class ShortcutTableModelPrivate
22{
23 QMap<QString, QStringList> shortcutItemMap;
24 QMap<QString, QStringList> shortcutItemShadowMap;
25 QString configFilePath;
26
27 friend class ShortcutTableModel;
28};
29
30ShortcutTableModel::ShortcutTableModel(QObject *parent)
31 : QAbstractTableModel(parent)
32 , d(new ShortcutTableModelPrivate())
33{
34 d->configFilePath = (CustomPaths::user(CustomPaths::Flags::Configures)
35 + QDir::separator() + QString("shortcut.support"));
36}
37
38ShortcutTableModel::~ShortcutTableModel()
39{
40
41}
42
43int ShortcutTableModel::rowCount(const QModelIndex &parent) const
44{
45 Q_UNUSED(parent)
46
47 return d->shortcutItemMap.size();
48}
49
50int ShortcutTableModel::columnCount(const QModelIndex &parent) const
51{
52 Q_UNUSED(parent)
53
54 return ColumnID::_KCount;
55}
56
57
58bool ShortcutTableModel::keySequenceIsInvalid(const QKeySequence &sequence) const
59{
60 for (uint i = 0; i < static_cast<uint>(sequence.count()); i++) {
61 if (Qt::Key_unknown == sequence[i]) {
62 return true;
63 }
64 }
65
66 return false;
67}
68
69bool ShortcutTableModel::shortcutRepeat(const QString &text) const
70{
71 int count = 0;
72 foreach (QString key, d->shortcutItemMap.keys()) {
73 QStringList valueList = d->shortcutItemMap.value(key);
74 if (0 == text.compare(valueList.last(), Qt::CaseInsensitive)) {
75 if (count++ > 0)
76 return true;
77 }
78 }
79
80 return false;
81}
82
83QVariant ShortcutTableModel::data(const QModelIndex &index, int role) const
84{
85 if (!index.isValid())
86 return QVariant();
87
88 if (role != Qt::DisplayRole && role != Qt::ForegroundRole)
89 return QVariant();
90
91 if (index.row() >= d->shortcutItemMap.keys().size())
92 return QVariant();
93
94 QString id = d->shortcutItemMap.keys()[index.row()];
95 QStringList valueList = d->shortcutItemMap.value(id);
96
97 QString description = valueList.first();
98 QString shortcut = valueList.last();
99
100 if(role == Qt::ForegroundRole && index.column() == ColumnID::kShortcut) {
101 if(shortcutRepeat(shortcut) || keySequenceIsInvalid(QKeySequence(shortcut))) {
102 return QColor(Qt::darkRed);
103 }
104 return QVariant();
105 }
106
107 switch (index.column()) {
108 case ColumnID::kID:
109 return id;
110 case ColumnID::kDescriptions:
111 return description;
112 case ColumnID::kShortcut:
113 return shortcut;
114 default:
115 return QVariant();
116 }
117}
118
119QVariant ShortcutTableModel::headerData(int section, Qt::Orientation orientation, int role) const
120{
121 if (orientation == Qt::Vertical)
122 return QVariant();
123
124 if (role != Qt::DisplayRole)
125 return QVariant();
126
127 switch (section) {
128 case ColumnID::kID:
129 return tr("ID");
130 case ColumnID::kDescriptions:
131 return tr("Description");
132 case ColumnID::kShortcut:
133 return tr("Shortcut");
134 default:
135 return QVariant();
136 }
137}
138
139void ShortcutTableModel::updateShortcut(QString id, QString shortcut)
140{
141 if (d->shortcutItemMap.keys().contains(id))
142 {
143 QStringList valueList = d->shortcutItemMap.value(id);
144 QStringList newValueList = {valueList.first(), shortcut};
145 d->shortcutItemMap[id] = newValueList;
146 }
147}
148
149void ShortcutTableModel::resetShortcut(QString id)
150{
151 if (d->shortcutItemMap.keys().contains(id) && d->shortcutItemShadowMap.keys().contains(id))
152 {
153 QStringList shadowValueList = d->shortcutItemShadowMap.value(id);
154 d->shortcutItemMap[id] = shadowValueList;
155 }
156}
157
158void ShortcutTableModel::resetAllShortcut()
159{
160 d->shortcutItemMap = d->shortcutItemShadowMap;
161}
162
163void ShortcutTableModel::saveShortcut()
164{
165 ShortcutUtil::writeToJson(d->configFilePath, d->shortcutItemMap);
166
167 QList<Command *> commandsList = ActionManager::getInstance()->commands();
168 QList<Command *>::iterator iter = commandsList.begin();
169 for (; iter != commandsList.end(); ++iter)
170 {
171 Action * action = dynamic_cast<Action *>(*iter);
172 QString id = action->id();
173
174 if (d->shortcutItemMap.contains(id)) {
175 QStringList valueList = d->shortcutItemMap[id];
176 action->setKeySequence(QKeySequence(valueList.last()));
177 }
178 }
179}
180
181void ShortcutTableModel::readShortcut()
182{
183 beginResetModel();
184
185 QList<Command *> commandsList = ActionManager::getInstance()->commands();
186 QList<Command *>::iterator iter = commandsList.begin();
187 for (; iter != commandsList.end(); ++iter)
188 {
189 Action * action = dynamic_cast<Action *>(*iter);
190 QString id = action->id();
191 QStringList valueList = QStringList{action->description(), action->keySequence().toString()};
192 d->shortcutItemMap[id] = valueList;
193 }
194
195 QMap<QString, QStringList> shortcutItemMap;
196 ShortcutUtil::readFromJson(d->configFilePath, shortcutItemMap);
197 foreach (const QString key, shortcutItemMap.keys()) {
198 d->shortcutItemMap[key] = shortcutItemMap.value(key);
199 }
200
201 d->shortcutItemShadowMap = d->shortcutItemMap;
202
203 endResetModel();
204}
205
206void ShortcutTableModel::importExternalJson(const QString &filePath)
207{
208 beginResetModel();
209
210 QMap<QString, QStringList> shortcutItemMap;
211 ShortcutUtil::readFromJson(filePath, shortcutItemMap);
212 foreach (QString key, shortcutItemMap.keys()) {
213 d->shortcutItemMap[key] = shortcutItemMap.value(key);
214 }
215
216 d->shortcutItemShadowMap = d->shortcutItemMap;
217
218 endResetModel();
219}
220
221void ShortcutTableModel::exportExternalJson(const QString &filePath)
222{
223 ShortcutUtil::writeToJson(filePath, d->shortcutItemMap);
224}
225
226class ShortcutTableModel;
227class ShortcutSettingWidgetPrivate
228{
229 ShortcutSettingWidgetPrivate();
230 QTableView *tableView;
231 HotkeyLineEdit *editShortCut;
232 ShortcutTableModel *model;
233 QPushButton *btnRecord;
234 QLabel *tipLabel;
235
236 friend class ShortcutSettingWidget;
237};
238
239ShortcutSettingWidgetPrivate::ShortcutSettingWidgetPrivate()
240 : tableView(nullptr)
241 , editShortCut(nullptr)
242 , model(nullptr)
243 , btnRecord(nullptr)
244 , tipLabel(nullptr)
245{
246
247}
248
249ShortcutSettingWidget::ShortcutSettingWidget(QWidget *parent)
250 : PageWidget(parent)
251 , d(new ShortcutSettingWidgetPrivate())
252{
253 setupUi();
254 readConfig();
255}
256
257ShortcutSettingWidget::~ShortcutSettingWidget()
258{
259
260}
261
262void ShortcutSettingWidget::setupUi()
263{
264 QVBoxLayout *vLayout = new QVBoxLayout();
265 setLayout(vLayout);
266
267 d->tableView = new QTableView();
268 d->tableView->setShowGrid(false);
269 d->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
270 d->tableView->verticalHeader()->hide();
271 d->tableView->setSelectionMode(QAbstractItemView::SingleSelection);
272 d->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
273 d->model = new ShortcutTableModel();
274 d->tableView->setModel(d->model);
275 vLayout->addWidget(d->tableView);
276
277 QWidget *widgetOperate = new QWidget();
278 vLayout->addWidget(widgetOperate);
279 QHBoxLayout *hLayoutOperate = new QHBoxLayout();
280 widgetOperate->setLayout(hLayoutOperate);
281 QPushButton *btnResetAll = new QPushButton();
282 btnResetAll->setText(tr("Reset All"));
283 btnResetAll->setFixedWidth(BTN_WIDTH);
284 QPushButton *btnImport = new QPushButton();
285 btnImport->setText(tr("Import"));
286 btnImport->setFixedWidth(BTN_WIDTH);
287 QPushButton *btnExport = new QPushButton();
288 btnExport->setText(tr("Export"));
289 btnExport->setFixedWidth(BTN_WIDTH);
290 hLayoutOperate->addWidget(btnResetAll);
291 hLayoutOperate->addStretch();
292 hLayoutOperate->addWidget(btnImport);
293 hLayoutOperate->addWidget(btnExport);
294
295 QWidget *widgetShortcut = new QWidget();
296 vLayout->addWidget(widgetShortcut);
297 QHBoxLayout *hLayoutShortcut = new QHBoxLayout();
298 widgetShortcut->setLayout(hLayoutShortcut);
299 QLabel *labelTip = new QLabel(tr("Shortcut:"));
300 d->editShortCut = new HotkeyLineEdit();
301 d->btnRecord = new QPushButton(tr("Record"));
302 d->btnRecord->setFixedWidth(BTN_WIDTH);
303 QPushButton *btnReset = new QPushButton(tr("Reset"));
304 btnReset->setFixedWidth(BTN_WIDTH);
305 hLayoutShortcut->addWidget(labelTip);
306 hLayoutShortcut->addWidget(d->editShortCut);
307 hLayoutShortcut->addWidget(d->btnRecord);
308 hLayoutShortcut->addWidget(btnReset);
309
310 d->tipLabel = new QLabel();
311 d->tipLabel->setMargin(10);
312 d->tipLabel->setStyleSheet("color:darkred;");
313 vLayout->addWidget(d->tipLabel);
314
315 connect(d->tableView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(onTableViewClicked(const QModelIndex &)));
316 connect(btnResetAll, SIGNAL(clicked()), this, SLOT(onBtnResetAllClicked()));
317 connect(d->editShortCut, SIGNAL(textChanged(const QString &)), this, SLOT(onShortcutEditTextChanged(const QString &)));
318 connect(btnImport, SIGNAL(clicked()), this, SLOT(onBtnImportClicked()));
319 connect(btnExport, SIGNAL(clicked()), this, SLOT(onBtnExportClicked()));
320 connect(btnReset, SIGNAL(clicked()), this, SLOT(onBtnResetClicked()));
321 connect(d->btnRecord, SIGNAL(clicked()), this, SLOT(onBtnRecordClicked()));
322}
323
324void ShortcutSettingWidget::setSelectedShortcut()
325{
326 int row = d->tableView->currentIndex().row();
327 QModelIndex index = d->model->index(row, ColumnID::kShortcut);
328 QString shortcut = d->model->data(index, Qt::DisplayRole).toString();
329 d->editShortCut->setText(shortcut);
330
331 checkShortcutValidity(row, shortcut);
332}
333
334bool ShortcutSettingWidget::shortcutIsRepeat(const int row, const QString &text)
335{
336 for (int i = 0; i < d->model->rowCount(); i++) {
337 if (row == i)
338 continue;
339 QModelIndex index = d->model->index(i, ColumnID::kShortcut);
340 QString shortcut = d->model->data(index, Qt::DisplayRole).toString();
341 if (text == shortcut) {
342 return true;
343 }
344 }
345
346 return false;
347}
348
349void ShortcutSettingWidget::checkShortcutValidity(const int row, const QString &shortcut)
350{
351 if (d->model->keySequenceIsInvalid(QKeySequence(shortcut))) {
352 d->tipLabel->setText(tr("Invalid shortcut!"));
353 } else if (shortcutIsRepeat(row, shortcut)){
354 d->tipLabel->setText(tr("shortcut Repeated!"));
355 } else {
356 d->tipLabel->setText("");
357 }
358}
359
360void ShortcutSettingWidget::onTableViewClicked(const QModelIndex &)
361{
362 setSelectedShortcut();
363}
364
365void ShortcutSettingWidget::onBtnResetAllClicked()
366{
367 d->model->resetAllShortcut();
368 d->tableView->update();
369}
370
371void ShortcutSettingWidget::onBtnResetClicked()
372{
373 int row = d->tableView->currentIndex().row();
374 QModelIndex indexID = d->model->index(row, ColumnID::kID);
375 QString qsID = d->model->data(indexID, Qt::DisplayRole).toString();
376 d->model->resetShortcut(qsID);
377
378 setSelectedShortcut();
379}
380
381void ShortcutSettingWidget::onShortcutEditTextChanged(const QString &text)
382{
383 QString shortcut = text.trimmed();
384 int row = d->tableView->currentIndex().row();
385 QModelIndex indexID = d->model->index(row, ColumnID::kID);
386 QString qsID = d->model->data(indexID, Qt::DisplayRole).toString();
387 d->model->updateShortcut(qsID, shortcut);
388
389 QModelIndex indexShortcut = d->model->index(row, ColumnID::kShortcut);
390 d->tableView->update(indexShortcut);
391
392 checkShortcutValidity(row, shortcut);
393}
394
395void ShortcutSettingWidget::saveConfig()
396{
397 d->model->saveShortcut();
398}
399
400void ShortcutSettingWidget::readConfig()
401{
402 d->model->readShortcut();
403}
404
405void ShortcutSettingWidget::onBtnImportClicked()
406{
407 QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), tr(""), tr("Json File(*.json)"));
408 if (!fileName.isEmpty()) {
409 d->model->importExternalJson(fileName);
410 }
411}
412
413void ShortcutSettingWidget::onBtnExportClicked()
414{
415 QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), tr(""), tr("Json File(*.json)"));
416 if (!fileName.isEmpty()) {
417 d->model->exportExternalJson(fileName);
418 }
419}
420
421void ShortcutSettingWidget::onBtnRecordClicked()
422{
423 bool bRet = d->editShortCut->isHotkeyMode();
424 if (bRet) {
425 d->btnRecord->setText(tr("Record"));
426 d->editShortCut->setHotkeyMode(false);
427 } else {
428 d->btnRecord->setText(tr("Stop Recording"));
429 d->editShortCut->setHotkeyMode(true);
430 }
431}
432