vkeyboardlayoutmappingdialog.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. #include "vkeyboardlayoutmappingdialog.h"
  2. #include <QtWidgets>
  3. #include "vlineedit.h"
  4. #include "utils/vkeyboardlayoutmanager.h"
  5. #include "utils/vutils.h"
  6. #include "utils/viconutils.h"
  7. #include "vconfigmanager.h"
  8. extern VConfigManager *g_config;
  9. VKeyboardLayoutMappingDialog::VKeyboardLayoutMappingDialog(QWidget *p_parent)
  10. : QDialog(p_parent),
  11. m_mappingModified(false),
  12. m_listenIndex(-1)
  13. {
  14. setupUI();
  15. loadAvailableMappings();
  16. }
  17. void VKeyboardLayoutMappingDialog::setupUI()
  18. {
  19. QString info = tr("Manage keybaord layout mappings to used in shortcuts.");
  20. info += "\n";
  21. info += tr("Double click an item to set mapping key.");
  22. QLabel *infoLabel = new QLabel(info, this);
  23. // Selector.
  24. m_selectorCombo = VUtils::getComboBox(this);
  25. connect(m_selectorCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
  26. this, [this](int p_idx) {
  27. loadMappingInfo(m_selectorCombo->itemData(p_idx).toString());
  28. });
  29. // Add.
  30. m_addBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/add.svg"), "", this);
  31. m_addBtn->setToolTip(tr("New Mapping"));
  32. m_addBtn->setProperty("FlatBtn", true);
  33. connect(m_addBtn, &QPushButton::clicked,
  34. this, &VKeyboardLayoutMappingDialog::newMapping);
  35. // Delete.
  36. m_deleteBtn = new QPushButton(VIconUtils::buttonDangerIcon(":/resources/icons/delete.svg"),
  37. "",
  38. this);
  39. m_deleteBtn->setToolTip(tr("Delete Mapping"));
  40. m_deleteBtn->setProperty("FlatBtn", true);
  41. connect(m_deleteBtn, &QPushButton::clicked,
  42. this, &VKeyboardLayoutMappingDialog::deleteCurrentMapping);
  43. QHBoxLayout *selectLayout = new QHBoxLayout();
  44. selectLayout->addWidget(new QLabel(tr("Keyboard layout mapping:"), this));
  45. selectLayout->addWidget(m_selectorCombo);
  46. selectLayout->addWidget(m_addBtn);
  47. selectLayout->addWidget(m_deleteBtn);
  48. selectLayout->addStretch();
  49. // Name.
  50. m_nameEdit = new VLineEdit(this);
  51. connect(m_nameEdit, &QLineEdit::textEdited,
  52. this, [this](const QString &p_text) {
  53. Q_UNUSED(p_text);
  54. setModified(true);
  55. });
  56. QHBoxLayout *editLayout = new QHBoxLayout();
  57. editLayout->addWidget(new QLabel(tr("Name:"), this));
  58. editLayout->addWidget(m_nameEdit);
  59. editLayout->addStretch();
  60. // Tree.
  61. m_contentTree = new QTreeWidget(this);
  62. m_contentTree->setProperty("ItemBorder", true);
  63. m_contentTree->setRootIsDecorated(false);
  64. m_contentTree->setColumnCount(2);
  65. m_contentTree->setSelectionBehavior(QAbstractItemView::SelectRows);
  66. QStringList headers;
  67. headers << tr("Key") << tr("New Key");
  68. m_contentTree->setHeaderLabels(headers);
  69. m_contentTree->installEventFilter(this);
  70. connect(m_contentTree, &QTreeWidget::itemDoubleClicked,
  71. this, [this](QTreeWidgetItem *p_item, int p_column) {
  72. Q_UNUSED(p_column);
  73. int idx = m_contentTree->indexOfTopLevelItem(p_item);
  74. if (m_listenIndex == -1) {
  75. // Listen key for this item.
  76. setListeningKey(idx);
  77. } else if (idx == m_listenIndex) {
  78. // Cancel listening key for this item.
  79. cancelListeningKey();
  80. } else {
  81. // Recover previous item.
  82. cancelListeningKey();
  83. setListeningKey(idx);
  84. }
  85. });
  86. connect(m_contentTree, &QTreeWidget::itemClicked,
  87. this, [this](QTreeWidgetItem *p_item, int p_column) {
  88. Q_UNUSED(p_column);
  89. int idx = m_contentTree->indexOfTopLevelItem(p_item);
  90. if (idx != m_listenIndex) {
  91. cancelListeningKey();
  92. }
  93. });
  94. QVBoxLayout *infoLayout = new QVBoxLayout();
  95. infoLayout->addLayout(editLayout);
  96. infoLayout->addWidget(m_contentTree);
  97. QGroupBox *box = new QGroupBox(tr("Mapping Information"));
  98. box->setLayout(infoLayout);
  99. // Ok is the default button.
  100. QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok
  101. | QDialogButtonBox::Apply
  102. | QDialogButtonBox::Cancel);
  103. connect(btnBox, &QDialogButtonBox::accepted,
  104. this, [this]() {
  105. if (applyChanges()) {
  106. QDialog::accept();
  107. }
  108. });
  109. connect(btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
  110. QPushButton *okBtn = btnBox->button(QDialogButtonBox::Ok);
  111. okBtn->setProperty("SpecialBtn", true);
  112. m_applyBtn = btnBox->button(QDialogButtonBox::Apply);
  113. connect(m_applyBtn, &QPushButton::clicked,
  114. this, &VKeyboardLayoutMappingDialog::applyChanges);
  115. QVBoxLayout *mainLayout = new QVBoxLayout();
  116. mainLayout->addWidget(infoLabel);
  117. mainLayout->addLayout(selectLayout);
  118. mainLayout->addWidget(box);
  119. mainLayout->addWidget(btnBox);
  120. setLayout(mainLayout);
  121. setWindowTitle(tr("Keyboard Layout Mappings"));
  122. }
  123. void VKeyboardLayoutMappingDialog::newMapping()
  124. {
  125. QString name = getNewMappingName();
  126. if (!VKeyboardLayoutManager::addLayout(name)) {
  127. VUtils::showMessage(QMessageBox::Warning,
  128. tr("Warning"),
  129. tr("Fail to add mapping <span style=\"%1\">%2</span>.")
  130. .arg(g_config->c_dataTextStyle)
  131. .arg(name),
  132. tr("Please check the configuration file and try again."),
  133. QMessageBox::Ok,
  134. QMessageBox::Ok,
  135. this);
  136. return;
  137. }
  138. loadAvailableMappings();
  139. setCurrentMapping(name);
  140. }
  141. QString VKeyboardLayoutMappingDialog::getNewMappingName() const
  142. {
  143. QString name;
  144. QString baseName("layout_mapping");
  145. int seq = 1;
  146. do {
  147. name = QString("%1_%2").arg(baseName).arg(QString::number(seq++), 3, '0');
  148. } while (m_selectorCombo->findData(name) != -1);
  149. return name;
  150. }
  151. void VKeyboardLayoutMappingDialog::deleteCurrentMapping()
  152. {
  153. QString mapping = currentMapping();
  154. if (mapping.isEmpty()) {
  155. return;
  156. }
  157. int ret = VUtils::showMessage(QMessageBox::Warning,
  158. tr("Warning"),
  159. tr("Are you sure to delete mapping <span style=\"%1\">%2</span>?")
  160. .arg(g_config->c_dataTextStyle)
  161. .arg(mapping),
  162. "",
  163. QMessageBox::Ok | QMessageBox::Cancel,
  164. QMessageBox::Ok,
  165. this);
  166. if (ret != QMessageBox::Ok) {
  167. return;
  168. }
  169. if (!VKeyboardLayoutManager::removeLayout(mapping)) {
  170. VUtils::showMessage(QMessageBox::Warning,
  171. tr("Warning"),
  172. tr("Fail to delete mapping <span style=\"%1\">%2</span>.")
  173. .arg(g_config->c_dataTextStyle)
  174. .arg(mapping),
  175. tr("Please check the configuration file and try again."),
  176. QMessageBox::Ok,
  177. QMessageBox::Ok,
  178. this);
  179. }
  180. loadAvailableMappings();
  181. }
  182. void VKeyboardLayoutMappingDialog::loadAvailableMappings()
  183. {
  184. m_selectorCombo->setCurrentIndex(-1);
  185. m_selectorCombo->clear();
  186. QStringList layouts = VKeyboardLayoutManager::availableLayouts();
  187. for (auto const & layout : layouts) {
  188. m_selectorCombo->addItem(layout, layout);
  189. }
  190. if (m_selectorCombo->count() > 0) {
  191. m_selectorCombo->setCurrentIndex(0);
  192. }
  193. }
  194. static QList<int> keysNeededToMap()
  195. {
  196. QList<int> keys;
  197. for (int i = Qt::Key_0; i <= Qt::Key_9; ++i) {
  198. keys.append(i);
  199. }
  200. for (int i = Qt::Key_A; i <= Qt::Key_Z; ++i) {
  201. keys.append(i);
  202. }
  203. QList<int> addi = g_config->getKeyboardLayoutMappingKeys();
  204. for (auto tmp : addi) {
  205. if (!keys.contains(tmp)) {
  206. keys.append(tmp);
  207. }
  208. }
  209. return keys;
  210. }
  211. static void recoverTreeItem(QTreeWidgetItem *p_item)
  212. {
  213. int key = p_item->data(0, Qt::UserRole).toInt();
  214. QString text0 = QString("%1 (%2)").arg(VUtils::keyToChar(key, false))
  215. .arg(key);
  216. p_item->setText(0, text0);
  217. int newKey = p_item->data(1, Qt::UserRole).toInt();
  218. QString text1;
  219. if (newKey > 0) {
  220. text1 = QString("%1 (%2)").arg(VUtils::keyToChar(newKey, false))
  221. .arg(newKey);
  222. }
  223. p_item->setText(1, text1);
  224. }
  225. // @p_newKey, 0 if there is no mapping.
  226. static void fillTreeItem(QTreeWidgetItem *p_item, int p_key, int p_newKey)
  227. {
  228. p_item->setData(0, Qt::UserRole, p_key);
  229. p_item->setData(1, Qt::UserRole, p_newKey);
  230. recoverTreeItem(p_item);
  231. }
  232. static void setTreeItemMapping(QTreeWidgetItem *p_item, int p_newKey)
  233. {
  234. p_item->setData(1, Qt::UserRole, p_newKey);
  235. }
  236. static void fillMappingTree(QTreeWidget *p_tree, const QHash<int, int> &p_mappings)
  237. {
  238. QList<int> keys = keysNeededToMap();
  239. for (auto key : keys) {
  240. int val = 0;
  241. auto it = p_mappings.find(key);
  242. if (it != p_mappings.end()) {
  243. val = it.value();
  244. }
  245. QTreeWidgetItem *item = new QTreeWidgetItem(p_tree);
  246. fillTreeItem(item, key, val);
  247. }
  248. }
  249. static QHash<int, int> retrieveMappingFromTree(QTreeWidget *p_tree)
  250. {
  251. QHash<int, int> mappings;
  252. int cnt = p_tree->topLevelItemCount();
  253. for (int i = 0; i < cnt; ++i) {
  254. QTreeWidgetItem *item = p_tree->topLevelItem(i);
  255. int key = item->data(0, Qt::UserRole).toInt();
  256. int newKey = item->data(1, Qt::UserRole).toInt();
  257. if (newKey > 0) {
  258. mappings.insert(key, newKey);
  259. }
  260. }
  261. return mappings;
  262. }
  263. void VKeyboardLayoutMappingDialog::loadMappingInfo(const QString &p_layout)
  264. {
  265. setModified(false);
  266. if (p_layout.isEmpty()) {
  267. m_nameEdit->clear();
  268. m_contentTree->clear();
  269. m_nameEdit->setEnabled(false);
  270. m_contentTree->setEnabled(false);
  271. return;
  272. }
  273. m_nameEdit->setText(p_layout);
  274. m_nameEdit->setEnabled(true);
  275. m_contentTree->clear();
  276. if (!p_layout.isEmpty()) {
  277. auto mappings = VKeyboardLayoutManager::readLayoutMapping(p_layout);
  278. fillMappingTree(m_contentTree, mappings);
  279. }
  280. m_contentTree->setEnabled(true);
  281. }
  282. void VKeyboardLayoutMappingDialog::updateButtons()
  283. {
  284. QString mapping = currentMapping();
  285. m_deleteBtn->setEnabled(!mapping.isEmpty());
  286. m_applyBtn->setEnabled(m_mappingModified);
  287. }
  288. QString VKeyboardLayoutMappingDialog::currentMapping() const
  289. {
  290. return m_selectorCombo->currentData().toString();
  291. }
  292. void VKeyboardLayoutMappingDialog::setCurrentMapping(const QString &p_layout)
  293. {
  294. return m_selectorCombo->setCurrentIndex(m_selectorCombo->findData(p_layout));
  295. }
  296. bool VKeyboardLayoutMappingDialog::applyChanges()
  297. {
  298. if (!m_mappingModified) {
  299. return true;
  300. }
  301. QString mapping = currentMapping();
  302. if (mapping.isEmpty()) {
  303. setModified(false);
  304. return true;
  305. }
  306. // Check the name.
  307. QString newName = m_nameEdit->text();
  308. if (newName.isEmpty() || newName.toLower() == "global") {
  309. // Set back the original name.
  310. m_nameEdit->setText(mapping);
  311. m_nameEdit->selectAll();
  312. m_nameEdit->setFocus();
  313. return false;
  314. } else if (newName != mapping) {
  315. // Rename the mapping.
  316. if (!VKeyboardLayoutManager::renameLayout(mapping, newName)) {
  317. VUtils::showMessage(QMessageBox::Warning,
  318. tr("Warning"),
  319. tr("Fail to rename mapping <span style=\"%1\">%2</span>.")
  320. .arg(g_config->c_dataTextStyle)
  321. .arg(mapping),
  322. tr("Please check the configuration file and try again."),
  323. QMessageBox::Ok,
  324. QMessageBox::Ok,
  325. this);
  326. m_nameEdit->setText(mapping);
  327. m_nameEdit->selectAll();
  328. m_nameEdit->setFocus();
  329. return false;
  330. }
  331. // Update the combobox.
  332. int idx = m_selectorCombo->currentIndex();
  333. m_selectorCombo->setItemText(idx, newName);
  334. m_selectorCombo->setItemData(idx, newName);
  335. mapping = newName;
  336. }
  337. // Check the mappings.
  338. QHash<int, int> mappings = retrieveMappingFromTree(m_contentTree);
  339. if (!VKeyboardLayoutManager::updateLayout(mapping, mappings)) {
  340. VUtils::showMessage(QMessageBox::Warning,
  341. tr("Warning"),
  342. tr("Fail to update mapping <span style=\"%1\">%2</span>.")
  343. .arg(g_config->c_dataTextStyle)
  344. .arg(mapping),
  345. tr("Please check the configuration file and try again."),
  346. QMessageBox::Ok,
  347. QMessageBox::Ok,
  348. this);
  349. return false;
  350. }
  351. setModified(false);
  352. return true;
  353. }
  354. bool VKeyboardLayoutMappingDialog::eventFilter(QObject *p_obj, QEvent *p_event)
  355. {
  356. if (p_obj == m_contentTree) {
  357. switch (p_event->type()) {
  358. case QEvent::FocusOut:
  359. cancelListeningKey();
  360. break;
  361. case QEvent::KeyPress:
  362. if (listenKey(static_cast<QKeyEvent *>(p_event))) {
  363. return true;
  364. }
  365. break;
  366. default:
  367. break;
  368. }
  369. }
  370. return QDialog::eventFilter(p_obj, p_event);
  371. }
  372. bool VKeyboardLayoutMappingDialog::listenKey(QKeyEvent *p_event)
  373. {
  374. if (m_listenIndex == -1) {
  375. return false;
  376. }
  377. int key = p_event->key();
  378. if (VUtils::isMetaKey(key)) {
  379. return false;
  380. }
  381. if (key == Qt::Key_Escape) {
  382. cancelListeningKey();
  383. return true;
  384. }
  385. // Set the mapping.
  386. QTreeWidgetItem *item = m_contentTree->topLevelItem(m_listenIndex);
  387. setTreeItemMapping(item, key);
  388. setModified(true);
  389. // Try next item automatically.
  390. int nextIdx = m_listenIndex + 1;
  391. cancelListeningKey();
  392. if (nextIdx < m_contentTree->topLevelItemCount()) {
  393. QTreeWidgetItem *item = m_contentTree->topLevelItem(nextIdx);
  394. m_contentTree->clearSelection();
  395. m_contentTree->setCurrentItem(item);
  396. setListeningKey(nextIdx);
  397. }
  398. return true;
  399. }
  400. void VKeyboardLayoutMappingDialog::cancelListeningKey()
  401. {
  402. if (m_listenIndex > -1) {
  403. // Recover that item.
  404. recoverTreeItem(m_contentTree->topLevelItem(m_listenIndex));
  405. m_listenIndex = -1;
  406. }
  407. }
  408. void VKeyboardLayoutMappingDialog::setListeningKey(int p_idx)
  409. {
  410. Q_ASSERT(m_listenIndex == -1 && p_idx > -1);
  411. m_listenIndex = p_idx;
  412. QTreeWidgetItem *item = m_contentTree->topLevelItem(m_listenIndex);
  413. item->setText(1, tr("Press key to set mapping"));
  414. }