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 }