/* SPDX-FileCopyrightText: 2010 Andriy Rysin SPDX-License-Identifier: GPL-2.0-or-later */ #include "kcm_view_models.h" #include #include #include #include #include #include #include #ifdef DRAG_ENABLED #include #endif #include "bindings.h" #include "flags.h" #include "keyboard_config.h" #include "x11_helper.h" #include "xkb_rules.h" const int LayoutsTableModel::MAP_COLUMN = 0; const int LayoutsTableModel::LAYOUT_COLUMN = 1; const int LayoutsTableModel::VARIANT_COLUMN = 2; const int LayoutsTableModel::DISPLAY_NAME_COLUMN = 3; const int LayoutsTableModel::SHORTCUT_COLUMN = 4; static const int COLUMN_COUNT = 5; LayoutsTableModel::LayoutsTableModel(Rules *rules_, Flags *flags_, KeyboardConfig *keyboardConfig_, QObject *parent) : QAbstractTableModel(parent) , keyboardConfig(keyboardConfig_) , rules(rules_) , countryFlags(flags_) { } void LayoutsTableModel::refresh() { beginResetModel(); endResetModel(); } int LayoutsTableModel::rowCount(const QModelIndex & /*parent*/) const { return keyboardConfig->layouts.count(); } int LayoutsTableModel::columnCount(const QModelIndex &) const { return COLUMN_COUNT; } Qt::ItemFlags LayoutsTableModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemFlags(); Qt::ItemFlags flags = QAbstractTableModel::flags(index); if (index.column() == DISPLAY_NAME_COLUMN || index.column() == VARIANT_COLUMN || index.column() == SHORTCUT_COLUMN) { flags |= Qt::ItemIsEditable; } #ifdef DRAG_ENABLED flags |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; #endif return flags; } #ifdef DRAG_ENABLED QStringList LayoutsTableModel::mimeTypes() const { QStringList types; types << "application/keyboard-layout-item"; return types; } QMimeData *LayoutsTableModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); QSet rows; for (const QModelIndex &index : indexes) { if (index.isValid()) { rows << index.row(); } } for (int row : std::as_const(rows)) { stream << row; } mimeData->setData("application/keyboard-layout-item", encodedData); return mimeData; } #endif QVariant LayoutsTableModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row() >= keyboardConfig->layouts.size()) return QVariant(); const LayoutUnit &layoutUnit = keyboardConfig->layouts.at(index.row()); if (role == Qt::DecorationRole) { switch (index.column()) { case DISPLAY_NAME_COLUMN: { // if(keyboardConfig->isFlagShown()) { return countryFlags->getIcon(layoutUnit.layout()); // } } // TODO: show the cells are editable // case VARIANT_COLUMN: { // case DISPLAY_NAME_COLUMN: { // int sz = 5; // QPixmap pm = QPixmap(sz, sz+5); // pm.fill(Qt::transparent); // QPainter p(&pm); // QPoint points[] = { QPoint(0, 0), QPoint(0, sz), QPoint(sz, 0) }; // p.drawPolygon(points, 3); // return pm; // } break; } } else if (role == Qt::BackgroundRole) { if (keyboardConfig->layoutLoopCount() != KeyboardConfig::NO_LOOPING && index.row() >= keyboardConfig->layoutLoopCount()) { return QBrush(Qt::lightGray); } } else if (role == Qt::DisplayRole) { switch (index.column()) { case MAP_COLUMN: return layoutUnit.layout(); break; case LAYOUT_COLUMN: { const LayoutInfo *layoutInfo = rules->getLayoutInfo(layoutUnit.layout()); return layoutInfo != nullptr ? layoutInfo->description : layoutUnit.layout(); } case VARIANT_COLUMN: { if (layoutUnit.variant().isEmpty()) return QVariant(); const LayoutInfo *layoutInfo = rules->getLayoutInfo(layoutUnit.layout()); if (layoutInfo == nullptr) return QVariant(); const VariantInfo *variantInfo = layoutInfo->getVariantInfo(layoutUnit.variant()); return variantInfo != nullptr ? variantInfo->description : layoutUnit.variant(); } break; case DISPLAY_NAME_COLUMN: // if( keyboardConfig->indicatorType == KeyboardConfig::SHOW_LABEL ) { // return layoutUnit.getDisplayName(); // } break; case SHORTCUT_COLUMN: { return layoutUnit.getShortcut().toString(); } break; } } else if (role == Qt::EditRole) { switch (index.column()) { case DISPLAY_NAME_COLUMN: return layoutUnit.getDisplayName(); break; case VARIANT_COLUMN: return layoutUnit.variant(); break; case SHORTCUT_COLUMN: return layoutUnit.getShortcut().toString(); break; default:; } } else if (role == Qt::TextAlignmentRole) { switch (index.column()) { case MAP_COLUMN: case DISPLAY_NAME_COLUMN: case SHORTCUT_COLUMN: return Qt::AlignCenter; break; default:; } } return QVariant(); } QVariant LayoutsTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) { const QString headers[] = {i18nc("layout map name", "Map"), i18n("Layout"), i18n("Variant"), i18n("Label"), i18n("Shortcut")}; return headers[section]; } return QVariant(); } bool LayoutsTableModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role != Qt::EditRole || (index.column() != DISPLAY_NAME_COLUMN && index.column() != VARIANT_COLUMN && index.column() != SHORTCUT_COLUMN)) return false; if (index.row() >= keyboardConfig->layouts.size() || index.data(role) == value) return false; LayoutUnit &layoutUnit = keyboardConfig->layouts[index.row()]; switch (index.column()) { case DISPLAY_NAME_COLUMN: { QString displayText = value.toString().left(3); layoutUnit.setDisplayName(displayText); } break; case VARIANT_COLUMN: { layoutUnit.setVariant(value.toString()); } break; case SHORTCUT_COLUMN: { layoutUnit.setShortcut(QKeySequence(value.toString())); } break; } Q_EMIT dataChanged(index, index); return true; } // // LabelEditDelegate // LabelEditDelegate::LabelEditDelegate(const KeyboardConfig *keyboardConfig_, QObject *parent) : QStyledItemDelegate(parent) , keyboardConfig(keyboardConfig_) { } QWidget *LabelEditDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { QWidget *widget = QStyledItemDelegate::createEditor(parent, option, index); QLineEdit *lineEdit = static_cast(widget); if (lineEdit != nullptr) { lineEdit->setMaxLength(LayoutUnit::MAX_LABEL_LENGTH); connect(lineEdit, &QLineEdit::editingFinished, this, [this, lineEdit]() { Q_EMIT const_cast(this)->commitData(lineEdit); }); } return widget; } void LabelEditDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex & /* index */) const { editor->setGeometry(option.rect); } // // VariantComboDelegate // // TODO: reuse this function in kcm_add_layout_dialog.cpp static void populateComboWithVariants(QComboBox *combo, const QString &layout, const Rules *rules) { combo->clear(); const LayoutInfo *layoutInfo = rules->getLayoutInfo(layout); for (const VariantInfo *variantInfo : layoutInfo->variantInfos) { combo->addItem(variantInfo->description, variantInfo->name); } combo->model()->sort(0); combo->insertItem(0, i18nc("variant", "Default"), ""); combo->setCurrentIndex(0); } VariantComboDelegate::VariantComboDelegate(const KeyboardConfig *keyboardConfig_, const Rules *rules_, QObject *parent) : QStyledItemDelegate(parent) , keyboardConfig(keyboardConfig_) , rules(rules_) { } QWidget *VariantComboDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, const QModelIndex &index) const { QComboBox *editor = new QComboBox(parent); const LayoutUnit &layoutUnit = keyboardConfig->layouts[index.row()]; populateComboWithVariants(editor, layoutUnit.layout(), rules); connect(editor, &QComboBox::currentTextChanged, this, [this, editor]() { Q_EMIT const_cast(this)->commitData(editor); }); return editor; } void VariantComboDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QComboBox *combo = static_cast(editor); QString variant = index.model()->data(index, Qt::EditRole).toString(); int itemIndex = combo->findData(variant); if (itemIndex == -1) { itemIndex = 0; } combo->setCurrentIndex(itemIndex); } void VariantComboDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QComboBox *combo = static_cast(editor); QString variant = combo->itemData(combo->currentIndex()).toString(); model->setData(index, variant, Qt::EditRole); } void VariantComboDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex & /* index */) const { editor->setGeometry(option.rect); } // // KKeySequenceWidgetDelegate // KKeySequenceWidgetDelegate::KKeySequenceWidgetDelegate(const KeyboardConfig *keyboardConfig_, QObject *parent) : QStyledItemDelegate(parent) , keyboardConfig(keyboardConfig_) { } QWidget *KKeySequenceWidgetDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /*option*/, const QModelIndex &index) const { itemsBeingEdited.insert(index); KKeySequenceWidget *editor = new KKeySequenceWidget(parent); editor->setFocusPolicy(Qt::StrongFocus); editor->setModifierlessAllowed(false); const LayoutUnit &layoutUnit = keyboardConfig->layouts[index.row()]; editor->setKeySequence(layoutUnit.getShortcut()); editor->captureKeySequence(); connect(editor, &KKeySequenceWidget::keySequenceChanged, this, [this, editor]() { Q_EMIT const_cast(this)->commitData(editor); }); return editor; } void KKeySequenceWidgetDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { KKeySequenceWidget *kkeysequencewidget = static_cast(editor); QString shortcut = kkeysequencewidget->keySequence().toString(); model->setData(index, shortcut, Qt::EditRole); itemsBeingEdited.remove(index); } void KKeySequenceWidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (itemsBeingEdited.contains(index)) { // StyledBackgroundPainter::drawBackground(painter,option,index); } else { QStyledItemDelegate::paint(painter, option, index); } } // // Xkb Options Tree View // XkbOptionsTreeModel::XkbOptionsTreeModel(Rules *rules_, QObject *parent) : QAbstractItemModel(parent) , rules(rules_) { } void XkbOptionsTreeModel::setXkbOptions(const QStringList &options) { beginResetModel(); m_xkbOptions = options; endResetModel(); } QStringList XkbOptionsTreeModel::xkbOptions() const { return m_xkbOptions; } int XkbOptionsTreeModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) return rules->optionGroupInfos.count(); if (!parent.parent().isValid()) return rules->optionGroupInfos[parent.row()]->optionInfos.count(); return 0; } QVariant XkbOptionsTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); int row = index.row(); if (role == Qt::DisplayRole) { if (!index.parent().isValid()) { return rules->optionGroupInfos[row]->description; } else { int groupRow = index.parent().row(); const OptionGroupInfo *xkbGroup = rules->optionGroupInfos[groupRow]; return xkbGroup->optionInfos[row]->description; } } else if (role == Qt::CheckStateRole) { if (index.parent().isValid()) { int groupRow = index.parent().row(); const OptionGroupInfo *xkbGroup = rules->optionGroupInfos[groupRow]; const QString &xkbOptionName = xkbGroup->optionInfos[row]->name; return m_xkbOptions.indexOf(xkbOptionName) == -1 ? Qt::Unchecked : Qt::Checked; } else { int groupRow = index.row(); const OptionGroupInfo *xkbGroup = rules->optionGroupInfos[groupRow]; for (const OptionInfo *optionInfo : xkbGroup->optionInfos) { if (m_xkbOptions.indexOf(optionInfo->name) != -1) return Qt::PartiallyChecked; } return Qt::Unchecked; } } return QVariant(); } bool XkbOptionsTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { int groupRow = index.parent().row(); if (groupRow < 0) return false; const OptionGroupInfo *xkbGroup = rules->optionGroupInfos[groupRow]; const OptionInfo *option = xkbGroup->optionInfos[index.row()]; if (value.toInt() == Qt::Checked) { if (xkbGroup->exclusive) { // clear if exclusive (TODO: radiobutton) int idx = m_xkbOptions.indexOf(QRegExp(xkbGroup->name + ".*")); if (idx >= 0) { for (int i = 0; i < xkbGroup->optionInfos.count(); i++) if (xkbGroup->optionInfos[i]->name == m_xkbOptions.at(idx)) { setData(createIndex(i, index.column(), static_cast(index.internalId()) - index.row() + i), Qt::Unchecked, role); break; } } } if (m_xkbOptions.indexOf(option->name) < 0) { m_xkbOptions.append(option->name); } } else { m_xkbOptions.removeAll(option->name); } Q_EMIT dataChanged(index, index); Q_EMIT dataChanged(index.parent(), index.parent()); return true; } void XkbOptionsTreeModel::gotoGroup(const QString &groupName, QTreeView *view) { const OptionGroupInfo *optionGroupInfo = rules->getOptionGroupInfo(groupName); int index = rules->optionGroupInfos.indexOf(const_cast(optionGroupInfo)); if (index != -1) { QModelIndex modelIdx = createIndex(index, 0); // view->selectionModel()->setCurrentIndex(createIndex(index,0), QItemSelectionModel::NoUpdate); view->setExpanded(modelIdx, true); view->scrollTo(modelIdx, QAbstractItemView::PositionAtTop); view->selectionModel()->setCurrentIndex(modelIdx, QItemSelectionModel::Current); view->setFocus(Qt::OtherFocusReason); } }