#include "vkeyboardlayoutmappingdialog.h" #include #include "vlineedit.h" #include "utils/vkeyboardlayoutmanager.h" #include "utils/vutils.h" #include "utils/viconutils.h" #include "vconfigmanager.h" extern VConfigManager *g_config; VKeyboardLayoutMappingDialog::VKeyboardLayoutMappingDialog(QWidget *p_parent) : QDialog(p_parent), m_mappingModified(false), m_listenIndex(-1) { setupUI(); loadAvailableMappings(); } void VKeyboardLayoutMappingDialog::setupUI() { QString info = tr("Manage keybaord layout mappings to used in shortcuts."); info += "\n"; info += tr("Double click an item to set mapping key."); QLabel *infoLabel = new QLabel(info, this); // Selector. m_selectorCombo = VUtils::getComboBox(this); connect(m_selectorCombo, static_cast(&QComboBox::currentIndexChanged), this, [this](int p_idx) { loadMappingInfo(m_selectorCombo->itemData(p_idx).toString()); }); // Add. m_addBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/add.svg"), "", this); m_addBtn->setToolTip(tr("New Mapping")); m_addBtn->setProperty("FlatBtn", true); connect(m_addBtn, &QPushButton::clicked, this, &VKeyboardLayoutMappingDialog::newMapping); // Delete. m_deleteBtn = new QPushButton(VIconUtils::buttonDangerIcon(":/resources/icons/delete.svg"), "", this); m_deleteBtn->setToolTip(tr("Delete Mapping")); m_deleteBtn->setProperty("FlatBtn", true); connect(m_deleteBtn, &QPushButton::clicked, this, &VKeyboardLayoutMappingDialog::deleteCurrentMapping); QHBoxLayout *selectLayout = new QHBoxLayout(); selectLayout->addWidget(new QLabel(tr("Keyboard layout mapping:"), this)); selectLayout->addWidget(m_selectorCombo); selectLayout->addWidget(m_addBtn); selectLayout->addWidget(m_deleteBtn); selectLayout->addStretch(); // Name. m_nameEdit = new VLineEdit(this); connect(m_nameEdit, &QLineEdit::textEdited, this, [this](const QString &p_text) { Q_UNUSED(p_text); setModified(true); }); QHBoxLayout *editLayout = new QHBoxLayout(); editLayout->addWidget(new QLabel(tr("Name:"), this)); editLayout->addWidget(m_nameEdit); editLayout->addStretch(); // Tree. m_contentTree = new QTreeWidget(this); m_contentTree->setProperty("ItemBorder", true); m_contentTree->setRootIsDecorated(false); m_contentTree->setColumnCount(2); m_contentTree->setSelectionBehavior(QAbstractItemView::SelectRows); QStringList headers; headers << tr("Key") << tr("New Key"); m_contentTree->setHeaderLabels(headers); m_contentTree->installEventFilter(this); connect(m_contentTree, &QTreeWidget::itemDoubleClicked, this, [this](QTreeWidgetItem *p_item, int p_column) { Q_UNUSED(p_column); int idx = m_contentTree->indexOfTopLevelItem(p_item); if (m_listenIndex == -1) { // Listen key for this item. setListeningKey(idx); } else if (idx == m_listenIndex) { // Cancel listening key for this item. cancelListeningKey(); } else { // Recover previous item. cancelListeningKey(); setListeningKey(idx); } }); connect(m_contentTree, &QTreeWidget::itemClicked, this, [this](QTreeWidgetItem *p_item, int p_column) { Q_UNUSED(p_column); int idx = m_contentTree->indexOfTopLevelItem(p_item); if (idx != m_listenIndex) { cancelListeningKey(); } }); QVBoxLayout *infoLayout = new QVBoxLayout(); infoLayout->addLayout(editLayout); infoLayout->addWidget(m_contentTree); QGroupBox *box = new QGroupBox(tr("Mapping Information")); box->setLayout(infoLayout); // Ok is the default button. QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel); connect(btnBox, &QDialogButtonBox::accepted, this, [this]() { if (applyChanges()) { QDialog::accept(); } }); connect(btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject); QPushButton *okBtn = btnBox->button(QDialogButtonBox::Ok); okBtn->setProperty("SpecialBtn", true); m_applyBtn = btnBox->button(QDialogButtonBox::Apply); connect(m_applyBtn, &QPushButton::clicked, this, &VKeyboardLayoutMappingDialog::applyChanges); QVBoxLayout *mainLayout = new QVBoxLayout(); mainLayout->addWidget(infoLabel); mainLayout->addLayout(selectLayout); mainLayout->addWidget(box); mainLayout->addWidget(btnBox); setLayout(mainLayout); setWindowTitle(tr("Keyboard Layout Mappings")); } void VKeyboardLayoutMappingDialog::newMapping() { QString name = getNewMappingName(); if (!VKeyboardLayoutManager::addLayout(name)) { VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to add mapping %2.") .arg(g_config->c_dataTextStyle) .arg(name), tr("Please check the configuration file and try again."), QMessageBox::Ok, QMessageBox::Ok, this); return; } loadAvailableMappings(); setCurrentMapping(name); } QString VKeyboardLayoutMappingDialog::getNewMappingName() const { QString name; QString baseName("layout_mapping"); int seq = 1; do { name = QString("%1_%2").arg(baseName).arg(QString::number(seq++), 3, '0'); } while (m_selectorCombo->findData(name) != -1); return name; } void VKeyboardLayoutMappingDialog::deleteCurrentMapping() { QString mapping = currentMapping(); if (mapping.isEmpty()) { return; } int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Are you sure to delete mapping %2?") .arg(g_config->c_dataTextStyle) .arg(mapping), "", QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok, this); if (ret != QMessageBox::Ok) { return; } if (!VKeyboardLayoutManager::removeLayout(mapping)) { VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to delete mapping %2.") .arg(g_config->c_dataTextStyle) .arg(mapping), tr("Please check the configuration file and try again."), QMessageBox::Ok, QMessageBox::Ok, this); } loadAvailableMappings(); } void VKeyboardLayoutMappingDialog::loadAvailableMappings() { m_selectorCombo->setCurrentIndex(-1); m_selectorCombo->clear(); QStringList layouts = VKeyboardLayoutManager::availableLayouts(); for (auto const & layout : layouts) { m_selectorCombo->addItem(layout, layout); } if (m_selectorCombo->count() > 0) { m_selectorCombo->setCurrentIndex(0); } } static QList keysNeededToMap() { QList keys; for (int i = Qt::Key_0; i <= Qt::Key_9; ++i) { keys.append(i); } for (int i = Qt::Key_A; i <= Qt::Key_Z; ++i) { keys.append(i); } QList addi = g_config->getKeyboardLayoutMappingKeys(); for (auto tmp : addi) { if (!keys.contains(tmp)) { keys.append(tmp); } } return keys; } static void recoverTreeItem(QTreeWidgetItem *p_item) { int key = p_item->data(0, Qt::UserRole).toInt(); QString text0 = QString("%1 (%2)").arg(VUtils::keyToChar(key, false)) .arg(key); p_item->setText(0, text0); int newKey = p_item->data(1, Qt::UserRole).toInt(); QString text1; if (newKey > 0) { text1 = QString("%1 (%2)").arg(VUtils::keyToChar(newKey, false)) .arg(newKey); } p_item->setText(1, text1); } // @p_newKey, 0 if there is no mapping. static void fillTreeItem(QTreeWidgetItem *p_item, int p_key, int p_newKey) { p_item->setData(0, Qt::UserRole, p_key); p_item->setData(1, Qt::UserRole, p_newKey); recoverTreeItem(p_item); } static void setTreeItemMapping(QTreeWidgetItem *p_item, int p_newKey) { p_item->setData(1, Qt::UserRole, p_newKey); } static void fillMappingTree(QTreeWidget *p_tree, const QHash &p_mappings) { QList keys = keysNeededToMap(); for (auto key : keys) { int val = 0; auto it = p_mappings.find(key); if (it != p_mappings.end()) { val = it.value(); } QTreeWidgetItem *item = new QTreeWidgetItem(p_tree); fillTreeItem(item, key, val); } } static QHash retrieveMappingFromTree(QTreeWidget *p_tree) { QHash mappings; int cnt = p_tree->topLevelItemCount(); for (int i = 0; i < cnt; ++i) { QTreeWidgetItem *item = p_tree->topLevelItem(i); int key = item->data(0, Qt::UserRole).toInt(); int newKey = item->data(1, Qt::UserRole).toInt(); if (newKey > 0) { mappings.insert(key, newKey); } } return mappings; } void VKeyboardLayoutMappingDialog::loadMappingInfo(const QString &p_layout) { setModified(false); if (p_layout.isEmpty()) { m_nameEdit->clear(); m_contentTree->clear(); m_nameEdit->setEnabled(false); m_contentTree->setEnabled(false); return; } m_nameEdit->setText(p_layout); m_nameEdit->setEnabled(true); m_contentTree->clear(); if (!p_layout.isEmpty()) { auto mappings = VKeyboardLayoutManager::readLayoutMapping(p_layout); fillMappingTree(m_contentTree, mappings); } m_contentTree->setEnabled(true); } void VKeyboardLayoutMappingDialog::updateButtons() { QString mapping = currentMapping(); m_deleteBtn->setEnabled(!mapping.isEmpty()); m_applyBtn->setEnabled(m_mappingModified); } QString VKeyboardLayoutMappingDialog::currentMapping() const { return m_selectorCombo->currentData().toString(); } void VKeyboardLayoutMappingDialog::setCurrentMapping(const QString &p_layout) { return m_selectorCombo->setCurrentIndex(m_selectorCombo->findData(p_layout)); } bool VKeyboardLayoutMappingDialog::applyChanges() { if (!m_mappingModified) { return true; } QString mapping = currentMapping(); if (mapping.isEmpty()) { setModified(false); return true; } // Check the name. QString newName = m_nameEdit->text(); if (newName.isEmpty() || newName.toLower() == "global") { // Set back the original name. m_nameEdit->setText(mapping); m_nameEdit->selectAll(); m_nameEdit->setFocus(); return false; } else if (newName != mapping) { // Rename the mapping. if (!VKeyboardLayoutManager::renameLayout(mapping, newName)) { VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to rename mapping %2.") .arg(g_config->c_dataTextStyle) .arg(mapping), tr("Please check the configuration file and try again."), QMessageBox::Ok, QMessageBox::Ok, this); m_nameEdit->setText(mapping); m_nameEdit->selectAll(); m_nameEdit->setFocus(); return false; } // Update the combobox. int idx = m_selectorCombo->currentIndex(); m_selectorCombo->setItemText(idx, newName); m_selectorCombo->setItemData(idx, newName); mapping = newName; } // Check the mappings. QHash mappings = retrieveMappingFromTree(m_contentTree); if (!VKeyboardLayoutManager::updateLayout(mapping, mappings)) { VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to update mapping %2.") .arg(g_config->c_dataTextStyle) .arg(mapping), tr("Please check the configuration file and try again."), QMessageBox::Ok, QMessageBox::Ok, this); return false; } setModified(false); return true; } bool VKeyboardLayoutMappingDialog::eventFilter(QObject *p_obj, QEvent *p_event) { if (p_obj == m_contentTree) { switch (p_event->type()) { case QEvent::FocusOut: cancelListeningKey(); break; case QEvent::KeyPress: if (listenKey(static_cast(p_event))) { return true; } break; default: break; } } return QDialog::eventFilter(p_obj, p_event); } bool VKeyboardLayoutMappingDialog::listenKey(QKeyEvent *p_event) { if (m_listenIndex == -1) { return false; } int key = p_event->key(); if (VUtils::isMetaKey(key)) { return false; } if (key == Qt::Key_Escape) { cancelListeningKey(); return true; } // Set the mapping. QTreeWidgetItem *item = m_contentTree->topLevelItem(m_listenIndex); setTreeItemMapping(item, key); setModified(true); // Try next item automatically. int nextIdx = m_listenIndex + 1; cancelListeningKey(); if (nextIdx < m_contentTree->topLevelItemCount()) { QTreeWidgetItem *item = m_contentTree->topLevelItem(nextIdx); m_contentTree->clearSelection(); m_contentTree->setCurrentItem(item); setListeningKey(nextIdx); } return true; } void VKeyboardLayoutMappingDialog::cancelListeningKey() { if (m_listenIndex > -1) { // Recover that item. recoverTreeItem(m_contentTree->topLevelItem(m_listenIndex)); m_listenIndex = -1; } } void VKeyboardLayoutMappingDialog::setListeningKey(int p_idx) { Q_ASSERT(m_listenIndex == -1 && p_idx > -1); m_listenIndex = p_idx; QTreeWidgetItem *item = m_contentTree->topLevelItem(m_listenIndex); item->setText(1, tr("Press key to set mapping")); }