File indexing completed on 2024-05-12 05:35:43
0001 /* 0002 SPDX-FileCopyrightText: 2010 Andriy Rysin <rysin@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kcm_view_models.h" 0008 0009 #include <KKeySequenceWidget> 0010 #include <KLocalizedString> 0011 0012 #include <QComboBox> 0013 #include <QKeySequence> 0014 #include <QLineEdit> 0015 #include <QPainter> 0016 #include <QTreeView> 0017 0018 #ifdef DRAG_ENABLED 0019 #include <QMimeData> 0020 #endif 0021 0022 #include "bindings.h" 0023 #include "flags.h" 0024 #include "keyboard_config.h" 0025 #include "x11_helper.h" 0026 #include "xkb_rules.h" 0027 0028 const int LayoutsTableModel::MAP_COLUMN = 0; 0029 const int LayoutsTableModel::LAYOUT_COLUMN = 1; 0030 const int LayoutsTableModel::VARIANT_COLUMN = 2; 0031 const int LayoutsTableModel::DISPLAY_NAME_COLUMN = 3; 0032 const int LayoutsTableModel::SHORTCUT_COLUMN = 4; 0033 static const int COLUMN_COUNT = 5; 0034 0035 LayoutsTableModel::LayoutsTableModel(Rules *rules_, Flags *flags_, KeyboardConfig *keyboardConfig_, QObject *parent) 0036 : QAbstractTableModel(parent) 0037 , keyboardConfig(keyboardConfig_) 0038 , rules(rules_) 0039 , countryFlags(flags_) 0040 { 0041 } 0042 0043 void LayoutsTableModel::refresh() 0044 { 0045 beginResetModel(); 0046 endResetModel(); 0047 } 0048 0049 int LayoutsTableModel::rowCount(const QModelIndex & /*parent*/) const 0050 { 0051 return keyboardConfig->layouts.count(); 0052 } 0053 0054 int LayoutsTableModel::columnCount(const QModelIndex &) const 0055 { 0056 return COLUMN_COUNT; 0057 } 0058 0059 Qt::ItemFlags LayoutsTableModel::flags(const QModelIndex &index) const 0060 { 0061 if (!index.isValid()) 0062 return Qt::ItemFlags(); 0063 0064 Qt::ItemFlags flags = QAbstractTableModel::flags(index); 0065 0066 if (index.column() == DISPLAY_NAME_COLUMN || index.column() == VARIANT_COLUMN || index.column() == SHORTCUT_COLUMN) { 0067 flags |= Qt::ItemIsEditable; 0068 } 0069 0070 #ifdef DRAG_ENABLED 0071 flags |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; 0072 #endif 0073 0074 return flags; 0075 } 0076 0077 #ifdef DRAG_ENABLED 0078 QStringList LayoutsTableModel::mimeTypes() const 0079 { 0080 QStringList types; 0081 types << "application/keyboard-layout-item"; 0082 return types; 0083 } 0084 0085 QMimeData *LayoutsTableModel::mimeData(const QModelIndexList &indexes) const 0086 { 0087 QMimeData *mimeData = new QMimeData(); 0088 QByteArray encodedData; 0089 0090 QDataStream stream(&encodedData, QIODevice::WriteOnly); 0091 0092 QSet<int> rows; 0093 for (const QModelIndex &index : indexes) { 0094 if (index.isValid()) { 0095 rows << index.row(); 0096 } 0097 } 0098 for (int row : std::as_const(rows)) { 0099 stream << row; 0100 } 0101 0102 mimeData->setData("application/keyboard-layout-item", encodedData); 0103 return mimeData; 0104 } 0105 #endif 0106 0107 QVariant LayoutsTableModel::data(const QModelIndex &index, int role) const 0108 { 0109 if (!index.isValid()) 0110 return QVariant(); 0111 0112 if (index.row() >= keyboardConfig->layouts.size()) 0113 return QVariant(); 0114 0115 const LayoutUnit &layoutUnit = keyboardConfig->layouts.at(index.row()); 0116 0117 if (role == Qt::DecorationRole) { 0118 switch (index.column()) { 0119 case DISPLAY_NAME_COLUMN: { 0120 // if(keyboardConfig->isFlagShown()) { 0121 return countryFlags->getIcon(layoutUnit.layout()); 0122 // } 0123 } 0124 // TODO: show the cells are editable 0125 // case VARIANT_COLUMN: { 0126 // case DISPLAY_NAME_COLUMN: { 0127 // int sz = 5; 0128 // QPixmap pm = QPixmap(sz, sz+5); 0129 // pm.fill(Qt::transparent); 0130 // QPainter p(&pm); 0131 // QPoint points[] = { QPoint(0, 0), QPoint(0, sz), QPoint(sz, 0) }; 0132 // p.drawPolygon(points, 3); 0133 // return pm; 0134 // } 0135 break; 0136 } 0137 } else if (role == Qt::BackgroundRole) { 0138 if (keyboardConfig->layoutLoopCount() != KeyboardConfig::NO_LOOPING && index.row() >= keyboardConfig->layoutLoopCount()) { 0139 return QBrush(Qt::lightGray); 0140 } 0141 } else if (role == Qt::DisplayRole) { 0142 switch (index.column()) { 0143 case MAP_COLUMN: 0144 return layoutUnit.layout(); 0145 break; 0146 case LAYOUT_COLUMN: { 0147 const LayoutInfo *layoutInfo = rules->getLayoutInfo(layoutUnit.layout()); 0148 return layoutInfo != nullptr ? layoutInfo->description : layoutUnit.layout(); 0149 } 0150 case VARIANT_COLUMN: { 0151 if (layoutUnit.variant().isEmpty()) 0152 return QVariant(); 0153 const LayoutInfo *layoutInfo = rules->getLayoutInfo(layoutUnit.layout()); 0154 if (layoutInfo == nullptr) 0155 return QVariant(); 0156 const VariantInfo *variantInfo = layoutInfo->getVariantInfo(layoutUnit.variant()); 0157 return variantInfo != nullptr ? variantInfo->description : layoutUnit.variant(); 0158 } break; 0159 case DISPLAY_NAME_COLUMN: 0160 // if( keyboardConfig->indicatorType == KeyboardConfig::SHOW_LABEL ) { 0161 // return layoutUnit.getDisplayName(); 0162 // } 0163 break; 0164 case SHORTCUT_COLUMN: { 0165 return layoutUnit.getShortcut().toString(); 0166 } break; 0167 } 0168 } else if (role == Qt::EditRole) { 0169 switch (index.column()) { 0170 case DISPLAY_NAME_COLUMN: 0171 return layoutUnit.getDisplayName(); 0172 break; 0173 case VARIANT_COLUMN: 0174 return layoutUnit.variant(); 0175 break; 0176 case SHORTCUT_COLUMN: 0177 return layoutUnit.getShortcut().toString(); 0178 break; 0179 default:; 0180 } 0181 } else if (role == Qt::TextAlignmentRole) { 0182 switch (index.column()) { 0183 case MAP_COLUMN: 0184 case DISPLAY_NAME_COLUMN: 0185 case SHORTCUT_COLUMN: 0186 return Qt::AlignCenter; 0187 break; 0188 default:; 0189 } 0190 } 0191 return QVariant(); 0192 } 0193 0194 QVariant LayoutsTableModel::headerData(int section, Qt::Orientation orientation, int role) const 0195 { 0196 if (role != Qt::DisplayRole) 0197 return QVariant(); 0198 0199 if (orientation == Qt::Horizontal) { 0200 const QString headers[] = {i18nc("layout map name", "Map"), i18n("Layout"), i18n("Variant"), i18n("Label"), i18n("Shortcut")}; 0201 return headers[section]; 0202 } 0203 0204 return QVariant(); 0205 } 0206 0207 bool LayoutsTableModel::setData(const QModelIndex &index, const QVariant &value, int role) 0208 { 0209 if (role != Qt::EditRole || (index.column() != DISPLAY_NAME_COLUMN && index.column() != VARIANT_COLUMN && index.column() != SHORTCUT_COLUMN)) 0210 return false; 0211 0212 if (index.row() >= keyboardConfig->layouts.size() || index.data(role) == value) 0213 return false; 0214 0215 LayoutUnit &layoutUnit = keyboardConfig->layouts[index.row()]; 0216 0217 switch (index.column()) { 0218 case DISPLAY_NAME_COLUMN: { 0219 QString displayText = value.toString().left(3); 0220 layoutUnit.setDisplayName(displayText); 0221 } break; 0222 case VARIANT_COLUMN: { 0223 layoutUnit.setVariant(value.toString()); 0224 } break; 0225 case SHORTCUT_COLUMN: { 0226 layoutUnit.setShortcut(QKeySequence(value.toString())); 0227 } break; 0228 } 0229 Q_EMIT dataChanged(index, index); 0230 0231 return true; 0232 } 0233 0234 // 0235 // LabelEditDelegate 0236 // 0237 LabelEditDelegate::LabelEditDelegate(const KeyboardConfig *keyboardConfig_, QObject *parent) 0238 : QStyledItemDelegate(parent) 0239 , keyboardConfig(keyboardConfig_) 0240 { 0241 } 0242 0243 QWidget *LabelEditDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const 0244 { 0245 QWidget *widget = QStyledItemDelegate::createEditor(parent, option, index); 0246 QLineEdit *lineEdit = static_cast<QLineEdit *>(widget); 0247 if (lineEdit != nullptr) { 0248 lineEdit->setMaxLength(LayoutUnit::MAX_LABEL_LENGTH); 0249 connect(lineEdit, &QLineEdit::editingFinished, this, [this, lineEdit]() { 0250 Q_EMIT const_cast<LabelEditDelegate *>(this)->commitData(lineEdit); 0251 }); 0252 } 0253 return widget; 0254 } 0255 0256 void LabelEditDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex & /* index */) const 0257 { 0258 editor->setGeometry(option.rect); 0259 } 0260 0261 // 0262 // VariantComboDelegate 0263 // 0264 // TODO: reuse this function in kcm_add_layout_dialog.cpp 0265 static void populateComboWithVariants(QComboBox *combo, const QString &layout, const Rules *rules) 0266 { 0267 combo->clear(); 0268 const LayoutInfo *layoutInfo = rules->getLayoutInfo(layout); 0269 for (const VariantInfo *variantInfo : layoutInfo->variantInfos) { 0270 combo->addItem(variantInfo->description, variantInfo->name); 0271 } 0272 combo->model()->sort(0); 0273 combo->insertItem(0, i18nc("variant", "Default"), ""); 0274 combo->setCurrentIndex(0); 0275 } 0276 0277 VariantComboDelegate::VariantComboDelegate(const KeyboardConfig *keyboardConfig_, const Rules *rules_, QObject *parent) 0278 : QStyledItemDelegate(parent) 0279 , keyboardConfig(keyboardConfig_) 0280 , rules(rules_) 0281 { 0282 } 0283 0284 QWidget *VariantComboDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, const QModelIndex &index) const 0285 { 0286 QComboBox *editor = new QComboBox(parent); 0287 const LayoutUnit &layoutUnit = keyboardConfig->layouts[index.row()]; 0288 populateComboWithVariants(editor, layoutUnit.layout(), rules); 0289 connect(editor, &QComboBox::currentTextChanged, this, [this, editor]() { 0290 Q_EMIT const_cast<VariantComboDelegate *>(this)->commitData(editor); 0291 }); 0292 return editor; 0293 } 0294 0295 void VariantComboDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const 0296 { 0297 QComboBox *combo = static_cast<QComboBox *>(editor); 0298 QString variant = index.model()->data(index, Qt::EditRole).toString(); 0299 int itemIndex = combo->findData(variant); 0300 if (itemIndex == -1) { 0301 itemIndex = 0; 0302 } 0303 combo->setCurrentIndex(itemIndex); 0304 } 0305 0306 void VariantComboDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const 0307 { 0308 QComboBox *combo = static_cast<QComboBox *>(editor); 0309 QString variant = combo->itemData(combo->currentIndex()).toString(); 0310 model->setData(index, variant, Qt::EditRole); 0311 } 0312 0313 void VariantComboDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex & /* index */) const 0314 { 0315 editor->setGeometry(option.rect); 0316 } 0317 0318 // 0319 // KKeySequenceWidgetDelegate 0320 // 0321 KKeySequenceWidgetDelegate::KKeySequenceWidgetDelegate(const KeyboardConfig *keyboardConfig_, QObject *parent) 0322 : QStyledItemDelegate(parent) 0323 , keyboardConfig(keyboardConfig_) 0324 { 0325 } 0326 0327 QWidget *KKeySequenceWidgetDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /*option*/, const QModelIndex &index) const 0328 { 0329 itemsBeingEdited.insert(index); 0330 0331 KKeySequenceWidget *editor = new KKeySequenceWidget(parent); 0332 editor->setFocusPolicy(Qt::StrongFocus); 0333 editor->setModifierlessAllowed(false); 0334 0335 const LayoutUnit &layoutUnit = keyboardConfig->layouts[index.row()]; 0336 editor->setKeySequence(layoutUnit.getShortcut()); 0337 0338 editor->captureKeySequence(); 0339 connect(editor, &KKeySequenceWidget::keySequenceChanged, this, [this, editor]() { 0340 Q_EMIT const_cast<KKeySequenceWidgetDelegate *>(this)->commitData(editor); 0341 }); 0342 0343 return editor; 0344 } 0345 0346 void KKeySequenceWidgetDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const 0347 { 0348 KKeySequenceWidget *kkeysequencewidget = static_cast<KKeySequenceWidget *>(editor); 0349 QString shortcut = kkeysequencewidget->keySequence().toString(); 0350 model->setData(index, shortcut, Qt::EditRole); 0351 itemsBeingEdited.remove(index); 0352 } 0353 0354 void KKeySequenceWidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 0355 { 0356 if (itemsBeingEdited.contains(index)) { 0357 // StyledBackgroundPainter::drawBackground(painter,option,index); 0358 } else { 0359 QStyledItemDelegate::paint(painter, option, index); 0360 } 0361 } 0362 // 0363 // Xkb Options Tree View 0364 // 0365 0366 XkbOptionsTreeModel::XkbOptionsTreeModel(Rules *rules_, QObject *parent) 0367 : QAbstractItemModel(parent) 0368 , rules(rules_) 0369 { 0370 } 0371 0372 void XkbOptionsTreeModel::setXkbOptions(const QStringList &options) 0373 { 0374 beginResetModel(); 0375 m_xkbOptions = options; 0376 endResetModel(); 0377 } 0378 0379 QStringList XkbOptionsTreeModel::xkbOptions() const 0380 { 0381 return m_xkbOptions; 0382 } 0383 0384 int XkbOptionsTreeModel::rowCount(const QModelIndex &parent) const 0385 { 0386 if (!parent.isValid()) 0387 return rules->optionGroupInfos.count(); 0388 if (!parent.parent().isValid()) 0389 return rules->optionGroupInfos[parent.row()]->optionInfos.count(); 0390 return 0; 0391 } 0392 0393 QVariant XkbOptionsTreeModel::data(const QModelIndex &index, int role) const 0394 { 0395 if (!index.isValid()) 0396 return QVariant(); 0397 0398 int row = index.row(); 0399 0400 if (role == Qt::DisplayRole) { 0401 if (!index.parent().isValid()) { 0402 return rules->optionGroupInfos[row]->description; 0403 } else { 0404 int groupRow = index.parent().row(); 0405 const OptionGroupInfo *xkbGroup = rules->optionGroupInfos[groupRow]; 0406 return xkbGroup->optionInfos[row]->description; 0407 } 0408 } else if (role == Qt::CheckStateRole) { 0409 if (index.parent().isValid()) { 0410 int groupRow = index.parent().row(); 0411 const OptionGroupInfo *xkbGroup = rules->optionGroupInfos[groupRow]; 0412 const QString &xkbOptionName = xkbGroup->optionInfos[row]->name; 0413 return m_xkbOptions.indexOf(xkbOptionName) == -1 ? Qt::Unchecked : Qt::Checked; 0414 } else { 0415 int groupRow = index.row(); 0416 const OptionGroupInfo *xkbGroup = rules->optionGroupInfos[groupRow]; 0417 for (const OptionInfo *optionInfo : xkbGroup->optionInfos) { 0418 if (m_xkbOptions.indexOf(optionInfo->name) != -1) 0419 return Qt::PartiallyChecked; 0420 } 0421 return Qt::Unchecked; 0422 } 0423 } 0424 return QVariant(); 0425 } 0426 0427 bool XkbOptionsTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) 0428 { 0429 int groupRow = index.parent().row(); 0430 if (groupRow < 0) 0431 return false; 0432 0433 const OptionGroupInfo *xkbGroup = rules->optionGroupInfos[groupRow]; 0434 const OptionInfo *option = xkbGroup->optionInfos[index.row()]; 0435 0436 if (value.toInt() == Qt::Checked) { 0437 if (xkbGroup->exclusive) { 0438 // clear if exclusive (TODO: radiobutton) 0439 int idx = m_xkbOptions.indexOf(QRegularExpression(xkbGroup->name + ".*")); 0440 if (idx >= 0) { 0441 for (int i = 0; i < xkbGroup->optionInfos.count(); i++) 0442 if (xkbGroup->optionInfos[i]->name == m_xkbOptions.at(idx)) { 0443 setData(createIndex(i, index.column(), static_cast<quint32>(index.internalId()) - index.row() + i), Qt::Unchecked, role); 0444 break; 0445 } 0446 } 0447 } 0448 if (m_xkbOptions.indexOf(option->name) < 0) { 0449 m_xkbOptions.append(option->name); 0450 } 0451 } else { 0452 m_xkbOptions.removeAll(option->name); 0453 } 0454 0455 Q_EMIT dataChanged(index, index); 0456 Q_EMIT dataChanged(index.parent(), index.parent()); 0457 return true; 0458 } 0459 0460 void XkbOptionsTreeModel::gotoGroup(const QString &groupName, QTreeView *view) 0461 { 0462 const OptionGroupInfo *optionGroupInfo = rules->getOptionGroupInfo(groupName); 0463 int index = rules->optionGroupInfos.indexOf(const_cast<OptionGroupInfo *>(optionGroupInfo)); 0464 if (index != -1) { 0465 QModelIndex modelIdx = createIndex(index, 0); 0466 // view->selectionModel()->setCurrentIndex(createIndex(index,0), QItemSelectionModel::NoUpdate); 0467 view->setExpanded(modelIdx, true); 0468 view->scrollTo(modelIdx, QAbstractItemView::PositionAtTop); 0469 view->selectionModel()->setCurrentIndex(modelIdx, QItemSelectionModel::Current); 0470 view->setFocus(Qt::OtherFocusReason); 0471 } 0472 }