File indexing completed on 2024-05-12 17:07:14
0001 /* 0002 SPDX-FileCopyrightText: 2010 Andriy Rysin <rysin@kde.org> 0003 SPDX-FileCopyrightText: 2021 Cyril Rossi <cyril.rossi@enioka.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "kcm_keyboard_widget.h" 0009 #include "debug.h" 0010 0011 #include <KAboutData> 0012 #include <KActionCollection> 0013 #include <KGlobalAccel> 0014 #include <KLocalizedString> 0015 #include <KWindowSystem> 0016 0017 #include <QCheckBox> 0018 #include <QDebug> 0019 #include <QMessageBox> 0020 #include <QPixmap> 0021 #include <QVBoxLayout> 0022 #include <QWidget> 0023 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0024 #include <QX11Info> 0025 #else 0026 #include <QtGui/private/qtx11extras_p.h> 0027 #endif 0028 0029 #include "workspace_options.h" 0030 #include "keyboardmiscsettings.h" 0031 0032 #include "bindings.h" 0033 #include "flags.h" 0034 #include "kcm_add_layout_dialog.h" 0035 #include "kcm_view_models.h" 0036 #include "tastenbrett.h" 0037 #include "x11_helper.h" 0038 #include "xkb_rules.h" 0039 0040 #include "kcmmisc.h" 0041 #include "ui_kcm_add_layout_dialog.h" 0042 0043 static const QString GROUP_SWITCH_GROUP_NAME(QStringLiteral("grp")); 0044 static const QString LV3_SWITCH_GROUP_NAME(QStringLiteral("lv3")); 0045 // static const QString RESET_XKB_OPTIONS("-option"); 0046 0047 static const int TAB_HARDWARE = 0; 0048 static const int TAB_LAYOUTS = 1; 0049 static const int TAB_ADVANCED = 2; 0050 0051 static const int MIN_LOOPING_COUNT = 2; 0052 0053 KCMKeyboardWidget::KCMKeyboardWidget(Rules *rules_, 0054 KeyboardConfig *keyboardConfig_, 0055 WorkspaceOptions &workspaceOptions, 0056 KCMiscKeyboardWidget *kcmMiscWidget, 0057 const QVariantList &args, 0058 QWidget * /*parent*/) 0059 : rules(rules_) 0060 , m_workspaceOptions(workspaceOptions) 0061 , actionCollection(nullptr) 0062 , uiUpdating(false) 0063 { 0064 flags = new Flags(); 0065 keyboardConfig = keyboardConfig_; 0066 0067 uiWidget = new Ui::TabWidget; 0068 uiWidget->setupUi(this); 0069 kcmMiscWidget->setParent(uiWidget->lowerHardwareWidget); 0070 uiWidget->lowerHardwareWidget->layout()->addWidget(kcmMiscWidget); 0071 0072 if (rules != nullptr) { 0073 initializeKeyboardModelUI(); 0074 initializeXkbOptionsUI(); 0075 initializeLayoutsUI(); 0076 } else { 0077 uiWidget->tabLayouts->setEnabled(false); 0078 uiWidget->tabAdvanced->setEnabled(false); 0079 uiWidget->keyboardModelComboBox->setEnabled(false); 0080 } 0081 0082 handleParameters(args); 0083 } 0084 0085 KCMKeyboardWidget::~KCMKeyboardWidget() 0086 { 0087 delete uiWidget; 0088 delete flags; 0089 } 0090 0091 void KCMKeyboardWidget::handleParameters(const QVariantList &args) 0092 { 0093 // TODO: improve parameter handling 0094 setCurrentIndex(TAB_HARDWARE); 0095 for (const auto &arg : args) { 0096 if (arg.type() == QVariant::String) { 0097 const QString str = arg.toString(); 0098 if (str == QLatin1String("--tab=layouts")) { 0099 setCurrentIndex(TAB_LAYOUTS); 0100 } else if (str == QLatin1String("--tab=advanced")) { 0101 setCurrentIndex(TAB_ADVANCED); 0102 } 0103 } 0104 } 0105 } 0106 0107 void KCMKeyboardWidget::save() 0108 { 0109 if (rules == nullptr) 0110 return; 0111 0112 keyboardConfig->setKeyboardModel(keyboardModelFromUI()); 0113 keyboardConfig->setSwitchingPolicy(switchingPolicyFromUI()); 0114 0115 saveXkbOptions(); 0116 } 0117 0118 void KCMKeyboardWidget::defaults() 0119 { 0120 updateHardwareUI(keyboardConfig->defaultKeyboardModelValue()); 0121 updateSwitchingPolicyUI(keyboardConfig->defaultSwitchingPolicyValue()); 0122 auto *xkbOptionModel = dynamic_cast<XkbOptionsTreeModel *>(uiWidget->xkbOptionsTreeView->model()); 0123 xkbOptionModel->setXkbOptions(keyboardConfig->defaultXkbOptionsValue()); 0124 keyboardConfig->setDefaults(); 0125 } 0126 0127 bool KCMKeyboardWidget::isSaveNeeded() const 0128 { 0129 return keyboardModelFromUI() != keyboardConfig->keyboardModel() || 0130 switchingPolicyFromUI() != keyboardConfig->switchingPolicy() || 0131 xkbOptionsFromUI() != keyboardConfig->xkbOptions() || 0132 keyboardConfig->layoutsSaveNeeded(); 0133 } 0134 0135 bool KCMKeyboardWidget::isDefault() const 0136 { 0137 return keyboardModelFromUI() == keyboardConfig->defaultKeyboardModelValue() && 0138 switchingPolicyFromUI() == keyboardConfig->defaultSwitchingPolicyValue() && 0139 xkbOptionsFromUI() == keyboardConfig->xkbOptions(); 0140 } 0141 0142 void KCMKeyboardWidget::setDefaultIndicator(bool visible) 0143 { 0144 m_highlightVisible = visible; 0145 updateUiDefaultIndicator(); 0146 } 0147 0148 void KCMKeyboardWidget::updateUiDefaultIndicator() 0149 { 0150 setDefaultIndicatorVisible(uiWidget->keyboardModelComboBox, m_highlightVisible && keyboardModelFromUI() != keyboardConfig->defaultKeyboardModelValue()); 0151 const auto isDefaultswitchingPolicy = switchingPolicyFromUI() == keyboardConfig->defaultSwitchingPolicyValue(); 0152 for (auto button : uiWidget->switchingPolicyButtonGroup->buttons()) { 0153 setDefaultIndicatorVisible(button, m_highlightVisible && !isDefaultswitchingPolicy && uiWidget->switchingPolicyButtonGroup->checkedButton() == button); 0154 } 0155 } 0156 0157 void KCMKeyboardWidget::updateUI() 0158 { 0159 if (rules == nullptr) 0160 return; 0161 0162 uiWidget->layoutsTableView->setModel(uiWidget->layoutsTableView->model()); 0163 layoutsTableModel->refresh(); 0164 uiWidget->layoutsTableView->resizeRowsToContents(); 0165 0166 uiUpdating = true; 0167 updateHardwareUI(keyboardConfig->keyboardModel()); 0168 updateSwitchingPolicyUI(keyboardConfig->switchingPolicy()); 0169 XkbOptionsTreeModel *model = dynamic_cast<XkbOptionsTreeModel *>(uiWidget->xkbOptionsTreeView->model()); 0170 model->setXkbOptions(keyboardConfig->xkbOptions()); 0171 updateLayoutsUI(); 0172 updateShortcutsUI(); 0173 layoutSelectionChanged(); 0174 uiUpdating = false; 0175 } 0176 0177 void KCMKeyboardWidget::uiChanged() 0178 { 0179 if (rules == nullptr) { 0180 return; 0181 } 0182 0183 ((LayoutsTableModel *)uiWidget->layoutsTableView->model())->refresh(); 0184 layoutSelectionChanged(); 0185 // this collapses the tree so use more fine-grained updates 0186 // ((LayoutsTableModel*)uiWidget->xkbOptionsTreeView->model())->refresh(); 0187 0188 if (uiUpdating) { 0189 return; 0190 } 0191 0192 updateXkbShortcutsButtons(); 0193 0194 updateLoopCount(); 0195 int loop = uiWidget->layoutLoopCountSpinBox->text().isEmpty() ? KeyboardConfig::NO_LOOPING : uiWidget->layoutLoopCountSpinBox->value(); 0196 keyboardConfig->setLayoutLoopCount(loop); 0197 0198 layoutsTableModel->refresh(); 0199 layoutSelectionChanged(); 0200 // Refresh layout shortcuts 0201 switchKeyboardShortcutChanged(); 0202 0203 Q_EMIT changed(true); 0204 } 0205 0206 void KCMKeyboardWidget::initializeKeyboardModelUI() 0207 { 0208 for (const ModelInfo *modelInfo : qAsConst(rules->modelInfos)) { 0209 QString vendor = modelInfo->vendor; 0210 if (vendor.isEmpty()) { 0211 vendor = i18nc("unknown keyboard model vendor", "Unknown"); 0212 } 0213 uiWidget->keyboardModelComboBox->addItem(i18nc("vendor | keyboard model", "%1 | %2", vendor, modelInfo->description), modelInfo->name); 0214 } 0215 uiWidget->keyboardModelComboBox->model()->sort(0); 0216 connect(uiWidget->keyboardModelComboBox, SIGNAL(activated(int)), this, SLOT(uiChanged())); 0217 connect(uiWidget->keyboardModelComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &KCMKeyboardWidget::updateUiDefaultIndicator); 0218 } 0219 0220 void KCMKeyboardWidget::addLayout() 0221 { 0222 AddLayoutDialog dialog(rules, 0223 flags, 0224 keyboardModelFromUI(), 0225 xkbOptionsFromUI(), 0226 false, 0227 this); 0228 dialog.setModal(true); 0229 if (dialog.exec() == QDialog::Accepted) { 0230 keyboardConfig->layouts.append(dialog.getSelectedLayoutUnit()); 0231 layoutsTableModel->refresh(); 0232 uiWidget->layoutsTableView->resizeRowsToContents(); 0233 uiChanged(); 0234 } 0235 0236 updateLoopCount(); 0237 } 0238 0239 void KCMKeyboardWidget::updateLoopCount() 0240 { 0241 int maxLoop = qMin(X11Helper::MAX_GROUP_COUNT, keyboardConfig->layouts.count() - 1); 0242 uiWidget->layoutLoopCountSpinBox->setMaximum(qMax(MIN_LOOPING_COUNT, maxLoop)); 0243 0244 if (maxLoop < MIN_LOOPING_COUNT) { 0245 uiWidget->layoutLoopingCheckBox->setEnabled(false); 0246 uiWidget->layoutLoopingCheckBox->setChecked(false); 0247 } else if (maxLoop >= X11Helper::MAX_GROUP_COUNT) { 0248 uiWidget->layoutLoopingCheckBox->setEnabled(false); 0249 uiWidget->layoutLoopingCheckBox->setChecked(true); 0250 } else { 0251 uiWidget->layoutLoopingCheckBox->setEnabled(keyboardConfig->configureLayouts()); 0252 } 0253 0254 uiWidget->layoutLoopingGroupBox->setEnabled(keyboardConfig->configureLayouts() && uiWidget->layoutLoopingCheckBox->isChecked()); 0255 0256 if (uiWidget->layoutLoopingCheckBox->isChecked()) { 0257 if (uiWidget->layoutLoopCountSpinBox->text().isEmpty()) { 0258 uiWidget->layoutLoopCountSpinBox->setValue(maxLoop); 0259 keyboardConfig->setLayoutLoopCount(maxLoop); 0260 } 0261 } else { 0262 uiWidget->layoutLoopCountSpinBox->clear(); 0263 keyboardConfig->setLayoutLoopCount(KeyboardConfig::NO_LOOPING); 0264 } 0265 } 0266 0267 void KCMKeyboardWidget::initializeLayoutsUI() 0268 { 0269 layoutsTableModel = new LayoutsTableModel(rules, flags, keyboardConfig, uiWidget->layoutsTableView); 0270 uiWidget->layoutsTableView->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed 0271 | QAbstractItemView::AnyKeyPressed); 0272 uiWidget->layoutsTableView->setModel(layoutsTableModel); 0273 uiWidget->layoutsTableView->setIconSize({22, 22}); 0274 0275 // TODO: do we need to delete this delegate or parent will take care of it? 0276 VariantComboDelegate *variantDelegate = new VariantComboDelegate(keyboardConfig, rules, uiWidget->layoutsTableView); 0277 uiWidget->layoutsTableView->setItemDelegateForColumn(LayoutsTableModel::VARIANT_COLUMN, variantDelegate); 0278 0279 LabelEditDelegate *labelDelegate = new LabelEditDelegate(keyboardConfig, uiWidget->layoutsTableView); 0280 uiWidget->layoutsTableView->setItemDelegateForColumn(LayoutsTableModel::DISPLAY_NAME_COLUMN, labelDelegate); 0281 0282 KKeySequenceWidgetDelegate *shortcutDelegate = new KKeySequenceWidgetDelegate(keyboardConfig, uiWidget->layoutsTableView); 0283 uiWidget->layoutsTableView->setItemDelegateForColumn(LayoutsTableModel::SHORTCUT_COLUMN, shortcutDelegate); 0284 0285 // TODO: is it ok to hardcode sizes? any better approach? 0286 uiWidget->layoutsTableView->setColumnWidth(LayoutsTableModel::MAP_COLUMN, 70); 0287 uiWidget->layoutsTableView->setColumnWidth(LayoutsTableModel::LAYOUT_COLUMN, 200); 0288 uiWidget->layoutsTableView->setColumnWidth(LayoutsTableModel::VARIANT_COLUMN, 200); 0289 uiWidget->layoutsTableView->setColumnWidth(LayoutsTableModel::DISPLAY_NAME_COLUMN, 50); 0290 uiWidget->layoutsTableView->setColumnWidth(LayoutsTableModel::SHORTCUT_COLUMN, 130); 0291 0292 connect(layoutsTableModel, &LayoutsTableModel::dataChanged, this, [this]() { 0293 Q_EMIT changed(true); 0294 }); 0295 0296 uiWidget->layoutLoopCountSpinBox->setMinimum(MIN_LOOPING_COUNT); 0297 0298 #ifdef DRAG_ENABLED 0299 uiWidget->layoutsTableView->setDragEnabled(true); 0300 uiWidget->layoutsTableView->setAcceptDrops(true); 0301 #endif 0302 0303 uiWidget->moveUpBtn->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up"))); 0304 uiWidget->moveDownBtn->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); 0305 uiWidget->addLayoutBtn->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); 0306 uiWidget->removeLayoutBtn->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); 0307 0308 QIcon clearIcon = 0309 qApp->isLeftToRight() ? QIcon::fromTheme(QStringLiteral("edit-clear-locationbar-rtl")) : QIcon::fromTheme(QStringLiteral("edit-clear-locationbar-ltr")); 0310 uiWidget->xkbGrpClearBtn->setIcon(clearIcon); 0311 uiWidget->xkb3rdLevelClearBtn->setIcon(clearIcon); 0312 0313 QIcon configIcon = QIcon::fromTheme(QStringLiteral("configure")); 0314 uiWidget->xkbGrpShortcutBtn->setIcon(configIcon); 0315 uiWidget->xkb3rdLevelShortcutBtn->setIcon(configIcon); 0316 0317 uiWidget->kdeKeySequence->setModifierlessAllowed(false); 0318 0319 uiWidget->kcfg_osdKbdLayoutChangedEnabled->setText(m_workspaceOptions.osdKbdLayoutChangedEnabledItem()->label()); 0320 uiWidget->kcfg_osdKbdLayoutChangedEnabled->setToolTip(m_workspaceOptions.osdKbdLayoutChangedEnabledItem()->toolTip()); 0321 0322 connect(uiWidget->addLayoutBtn, &QAbstractButton::clicked, this, &KCMKeyboardWidget::addLayout); 0323 connect(uiWidget->removeLayoutBtn, &QAbstractButton::clicked, this, &KCMKeyboardWidget::removeLayout); 0324 connect(uiWidget->layoutsTableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KCMKeyboardWidget::layoutSelectionChanged); 0325 connect(uiWidget->layoutsTableView->model(), qOverload<const QModelIndex &, const QModelIndex &, const QVector<int> &>(&QAbstractItemModel::dataChanged), 0326 this, &KCMKeyboardWidget::uiChanged); 0327 0328 connect(uiWidget->moveUpBtn, &QAbstractButton::clicked, this, &KCMKeyboardWidget::moveUp); 0329 connect(uiWidget->moveDownBtn, &QAbstractButton::clicked, this, &KCMKeyboardWidget::moveDown); 0330 0331 connect(uiWidget->previewButton, &QAbstractButton::clicked, this, &KCMKeyboardWidget::previewLayout); 0332 connect(uiWidget->xkbGrpClearBtn, &QAbstractButton::clicked, this, &KCMKeyboardWidget::clearGroupShortcuts); 0333 connect(uiWidget->xkb3rdLevelClearBtn, &QAbstractButton::clicked, this, &KCMKeyboardWidget::clear3rdLevelShortcuts); 0334 0335 connect(uiWidget->kdeKeySequence, &KKeySequenceWidget::keySequenceChanged, this, &KCMKeyboardWidget::alternativeShortcutChanged); 0336 connect(uiWidget->switchingPolicyButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(uiChanged())); 0337 connect(uiWidget->switchingPolicyButtonGroup, &QButtonGroup::idClicked, this, &KCMKeyboardWidget::updateUiDefaultIndicator); 0338 0339 connect(uiWidget->xkbGrpShortcutBtn, &QAbstractButton::clicked, this, &KCMKeyboardWidget::scrollToGroupShortcut); 0340 connect(uiWidget->xkb3rdLevelShortcutBtn, &QAbstractButton::clicked, this, &KCMKeyboardWidget::scrollTo3rdLevelShortcut); 0341 0342 connect(uiWidget->kcfg_configureLayouts, &QGroupBox::toggled, this, &KCMKeyboardWidget::configureLayoutsChanged); 0343 0344 connect(uiWidget->layoutLoopingCheckBox, &QAbstractButton::clicked, this, &KCMKeyboardWidget::uiChanged); 0345 connect(uiWidget->layoutLoopCountSpinBox, SIGNAL(valueChanged(int)), this, SLOT(uiChanged())); 0346 } 0347 0348 void KCMKeyboardWidget::previewLayout() 0349 { 0350 QModelIndex index = uiWidget->layoutsTableView->currentIndex(); 0351 0352 QModelIndex idcountry = index.sibling(index.row(), 0); 0353 const QString country = uiWidget->layoutsTableView->model()->data(idcountry).toString(); 0354 const QModelIndex idvariant = index.sibling(index.row(), 2); 0355 QString variant = uiWidget->layoutsTableView->model()->data(idvariant).toString(); 0356 const QString model = keyboardModelFromUI(); 0357 const QStringList options = xkbOptionsFromUI(); 0358 0359 const LayoutInfo *layoutInfo = rules->getLayoutInfo(country); 0360 if (!layoutInfo) { 0361 return; 0362 } 0363 0364 for (const VariantInfo *variantInfo : layoutInfo->variantInfos) { 0365 if (variant == variantInfo->description) { 0366 variant = variantInfo->name; 0367 break; 0368 } 0369 } 0370 0371 const QString title = Flags::getLongText(LayoutUnit(country, variant), rules); 0372 Tastenbrett::launch(model, country, variant, options.join(','), title); 0373 } 0374 0375 void KCMKeyboardWidget::alternativeShortcutChanged(const QKeySequence &seq) 0376 { 0377 Q_UNUSED(seq) 0378 0379 if (rules == nullptr) { 0380 return; 0381 } 0382 0383 if (actionCollection == nullptr) { 0384 actionCollection = new KeyboardLayoutActionCollection(this, true); 0385 } 0386 actionCollection->setToggleShortcut(uiWidget->kdeKeySequence->keySequence()); 0387 } 0388 0389 void KCMKeyboardWidget::switchKeyboardShortcutChanged() 0390 { 0391 if (rules == nullptr) { 0392 return; 0393 } 0394 0395 if (actionCollection == nullptr) { 0396 actionCollection = new KeyboardLayoutActionCollection(this, true); 0397 } 0398 actionCollection->resetLayoutShortcuts(); 0399 actionCollection->setLayoutShortcuts(keyboardConfig->layouts, rules); 0400 } 0401 0402 void KCMKeyboardWidget::configureLayoutsChanged() 0403 { 0404 if (uiWidget->kcfg_configureLayouts->isChecked() && keyboardConfig->layouts.isEmpty()) { 0405 populateWithCurrentLayouts(); 0406 } else { 0407 keyboardConfig->layouts.clear(); 0408 } 0409 uiChanged(); 0410 } 0411 0412 static QPair<int, int> getSelectedRowRange(const QModelIndexList &selected) 0413 { 0414 if (selected.isEmpty()) { 0415 return QPair<int, int>(-1, -1); 0416 } 0417 0418 QList<int> rows; 0419 for (const auto &index : selected) { 0420 rows << index.row(); 0421 } 0422 std::sort(rows.begin(), rows.end()); 0423 return QPair<int, int>(rows[0], rows[rows.size() - 1]); 0424 } 0425 0426 void KCMKeyboardWidget::layoutSelectionChanged() 0427 { 0428 QModelIndexList selected = uiWidget->layoutsTableView->selectionModel()->selectedIndexes(); 0429 uiWidget->removeLayoutBtn->setEnabled(!selected.isEmpty()); 0430 QPair<int, int> rowsRange(getSelectedRowRange(selected)); 0431 uiWidget->moveUpBtn->setEnabled(!selected.isEmpty() && rowsRange.first > 0); 0432 uiWidget->previewButton->setEnabled(uiWidget->layoutsTableView->selectionModel()->selectedRows().size() == 1); 0433 uiWidget->previewButton->setVisible(Tastenbrett::exists()); 0434 uiWidget->moveDownBtn->setEnabled(!selected.isEmpty() && rowsRange.second < keyboardConfig->layouts.size() - 1); 0435 } 0436 0437 void KCMKeyboardWidget::removeLayout() 0438 { 0439 if (!uiWidget->layoutsTableView->selectionModel()->hasSelection()) 0440 return; 0441 0442 const QModelIndexList selected = uiWidget->layoutsTableView->selectionModel()->selectedIndexes(); 0443 QPair<int, int> rowsRange(getSelectedRowRange(selected)); 0444 for (const auto &idx : selected) { 0445 if (idx.column() == 0) { 0446 keyboardConfig->layouts.removeAt(rowsRange.first); 0447 } 0448 } 0449 layoutsTableModel->refresh(); 0450 uiChanged(); 0451 0452 if (keyboardConfig->layouts.size() > 0) { 0453 int rowToSelect = rowsRange.first; 0454 if (rowToSelect >= keyboardConfig->layouts.size()) { 0455 rowToSelect--; 0456 } 0457 0458 QModelIndex topLeft = layoutsTableModel->index(rowToSelect, 0, QModelIndex()); 0459 QModelIndex bottomRight = layoutsTableModel->index(rowToSelect, layoutsTableModel->columnCount(topLeft) - 1, QModelIndex()); 0460 QItemSelection selection(topLeft, bottomRight); 0461 uiWidget->layoutsTableView->selectionModel()->select(selection, QItemSelectionModel::SelectCurrent); 0462 uiWidget->layoutsTableView->setFocus(); 0463 } 0464 0465 layoutSelectionChanged(); 0466 0467 updateLoopCount(); 0468 } 0469 0470 void KCMKeyboardWidget::moveUp() 0471 { 0472 moveSelectedLayouts(-1); 0473 } 0474 0475 void KCMKeyboardWidget::moveDown() 0476 { 0477 moveSelectedLayouts(1); 0478 } 0479 0480 void KCMKeyboardWidget::moveSelectedLayouts(int shift) 0481 { 0482 QItemSelectionModel *selectionModel = uiWidget->layoutsTableView->selectionModel(); 0483 if (selectionModel == nullptr || !selectionModel->hasSelection()) 0484 return; 0485 0486 const QModelIndexList selected = selectionModel->selectedRows(); 0487 if (selected.count() < 1) 0488 return; 0489 0490 int newFirstRow = selected[0].row() + shift; 0491 int newLastRow = selected[selected.size() - 1].row() + shift; 0492 0493 if (newFirstRow >= 0 && newLastRow <= keyboardConfig->layouts.size() - 1) { 0494 QList<int> selectionRows; 0495 for (const QModelIndex &index : selected) { 0496 int newRowIndex = index.row() + shift; 0497 keyboardConfig->layouts.move(index.row(), newRowIndex); 0498 selectionRows << newRowIndex; 0499 } 0500 uiChanged(); 0501 0502 QItemSelection selection; 0503 for (const int row : qAsConst(selectionRows)) { 0504 QModelIndex topLeft = layoutsTableModel->index(row, 0, QModelIndex()); 0505 QModelIndex bottomRight = layoutsTableModel->index(row, layoutsTableModel->columnCount(topLeft) - 1, QModelIndex()); 0506 selection << QItemSelectionRange(topLeft, bottomRight); 0507 } 0508 uiWidget->layoutsTableView->selectionModel()->select(selection, QItemSelectionModel::SelectCurrent); 0509 uiWidget->layoutsTableView->setFocus(); 0510 } 0511 } 0512 0513 void KCMKeyboardWidget::scrollToGroupShortcut() 0514 { 0515 this->setCurrentIndex(TAB_ADVANCED); 0516 if (!uiWidget->kcfg_resetOldXkbOptions->isChecked()) { 0517 uiWidget->kcfg_resetOldXkbOptions->setChecked(true); 0518 } 0519 ((XkbOptionsTreeModel *)uiWidget->xkbOptionsTreeView->model())->gotoGroup(GROUP_SWITCH_GROUP_NAME, uiWidget->xkbOptionsTreeView); 0520 } 0521 0522 void KCMKeyboardWidget::scrollTo3rdLevelShortcut() 0523 { 0524 this->setCurrentIndex(TAB_ADVANCED); 0525 if (!uiWidget->kcfg_resetOldXkbOptions->isChecked()) { 0526 uiWidget->kcfg_resetOldXkbOptions->setChecked(true); 0527 } 0528 ((XkbOptionsTreeModel *)uiWidget->xkbOptionsTreeView->model())->gotoGroup(LV3_SWITCH_GROUP_NAME, uiWidget->xkbOptionsTreeView); 0529 } 0530 0531 void KCMKeyboardWidget::clearGroupShortcuts() 0532 { 0533 clearXkbGroup(GROUP_SWITCH_GROUP_NAME); 0534 } 0535 0536 void KCMKeyboardWidget::clear3rdLevelShortcuts() 0537 { 0538 clearXkbGroup(LV3_SWITCH_GROUP_NAME); 0539 } 0540 0541 void KCMKeyboardWidget::clearXkbGroup(const QString &groupName) 0542 { 0543 auto *xkbOptionModel = dynamic_cast<XkbOptionsTreeModel *>(uiWidget->xkbOptionsTreeView->model()); 0544 QStringList xkbOptions = xkbOptionModel->xkbOptions(); 0545 for (int ii = xkbOptions.count() - 1; ii >= 0; ii--) { 0546 if (xkbOptions.at(ii).startsWith(groupName + Rules::XKB_OPTION_GROUP_SEPARATOR)) { 0547 xkbOptions.removeAt(ii); 0548 } 0549 } 0550 xkbOptionModel->setXkbOptions(xkbOptions); 0551 0552 xkbOptionModel->reset(); 0553 uiWidget->xkbOptionsTreeView->update(); 0554 updateXkbShortcutsButtons(); 0555 Q_EMIT changed(true); 0556 } 0557 0558 static bool xkbOptionGroupLessThan(const OptionGroupInfo *og1, const OptionGroupInfo *og2) 0559 { 0560 return og1->description.toLower() < og2->description.toLower(); 0561 } 0562 static bool xkbOptionLessThan(const OptionInfo *o1, const OptionInfo *o2) 0563 { 0564 return o1->description.toLower() < o2->description.toLower(); 0565 } 0566 0567 void KCMKeyboardWidget::initializeXkbOptionsUI() 0568 { 0569 std::sort(rules->optionGroupInfos.begin(), rules->optionGroupInfos.end(), xkbOptionGroupLessThan); 0570 for (OptionGroupInfo *optionGroupInfo : qAsConst(rules->optionGroupInfos)) { 0571 std::sort(optionGroupInfo->optionInfos.begin(), optionGroupInfo->optionInfos.end(), xkbOptionLessThan); 0572 } 0573 0574 XkbOptionsTreeModel *model = new XkbOptionsTreeModel(rules, uiWidget->xkbOptionsTreeView); 0575 uiWidget->xkbOptionsTreeView->setModel(model); 0576 connect(model, &QAbstractItemModel::dataChanged, this, &KCMKeyboardWidget::uiChanged); 0577 0578 connect(uiWidget->kcfg_resetOldXkbOptions, &QAbstractButton::toggled, this, &KCMKeyboardWidget::configureXkbOptionsChanged); 0579 connect(uiWidget->kcfg_resetOldXkbOptions, &QAbstractButton::toggled, uiWidget->xkbOptionsTreeView, &QWidget::setEnabled); 0580 } 0581 0582 void KCMKeyboardWidget::configureXkbOptionsChanged() 0583 { 0584 if (uiWidget->kcfg_resetOldXkbOptions->isChecked() && keyboardConfig->xkbOptions().isEmpty()) { 0585 populateWithCurrentXkbOptions(); 0586 } 0587 ((XkbOptionsTreeModel *)uiWidget->xkbOptionsTreeView->model())->reset(); 0588 uiChanged(); 0589 } 0590 0591 void KCMKeyboardWidget::saveXkbOptions() 0592 { 0593 QStringList options; 0594 0595 if(uiWidget->kcfg_resetOldXkbOptions->isChecked()) { 0596 options = xkbOptionsFromUI(); 0597 0598 // QStringLists with a single empty string are serialized as "\\0", avoid that 0599 // by saving them as an empty list instead. This way it can be passed as-is to 0600 // libxkbcommon/setxkbmap. Before KConfigXT it used QStringList::join(","). 0601 if (options.size() == 1 && options.constFirst().isEmpty()) { 0602 options.clear(); 0603 } 0604 } 0605 0606 keyboardConfig->setXkbOptions(options); 0607 } 0608 0609 QStringList KCMKeyboardWidget::xkbOptionsFromUI() const 0610 { 0611 XkbOptionsTreeModel *model = dynamic_cast<XkbOptionsTreeModel *>(uiWidget->xkbOptionsTreeView->model()); 0612 return model->xkbOptions(); 0613 } 0614 0615 void KCMKeyboardWidget::updateSwitchingPolicyUI(KeyboardConfig::SwitchingPolicy policy) 0616 { 0617 switch (policy) { 0618 case KeyboardConfig::SWITCH_POLICY_DESKTOP: 0619 uiWidget->switchByDesktopRadioBtn->setChecked(true); 0620 break; 0621 case KeyboardConfig::SWITCH_POLICY_APPLICATION: 0622 uiWidget->switchByApplicationRadioBtn->setChecked(true); 0623 break; 0624 case KeyboardConfig::SWITCH_POLICY_WINDOW: 0625 uiWidget->switchByWindowRadioBtn->setChecked(true); 0626 break; 0627 default: 0628 case KeyboardConfig::SWITCH_POLICY_GLOBAL: 0629 uiWidget->switchByGlobalRadioBtn->setChecked(true); 0630 } 0631 } 0632 0633 void KCMKeyboardWidget::updateXkbShortcutButton(const QString &groupName, QPushButton *button) 0634 { 0635 QStringList grpOptions; 0636 if (uiWidget->kcfg_resetOldXkbOptions->isChecked()) { 0637 QRegularExpression regexp("^" + groupName + Rules::XKB_OPTION_GROUP_SEPARATOR); 0638 XkbOptionsTreeModel *model = dynamic_cast<XkbOptionsTreeModel *>(uiWidget->xkbOptionsTreeView->model()); 0639 grpOptions = model->xkbOptions().filter(regexp); 0640 } 0641 switch (grpOptions.size()) { 0642 case 0: 0643 button->setText(i18nc("no shortcuts defined", "None")); 0644 break; 0645 case 1: { 0646 const QString &option = grpOptions.first(); 0647 const OptionGroupInfo *optionGroupInfo = rules->getOptionGroupInfo(groupName); 0648 const OptionInfo *optionInfo = optionGroupInfo->getOptionInfo(option); 0649 if (optionInfo == nullptr || optionInfo->description == nullptr) { 0650 qCDebug(KCM_KEYBOARD) << "Could not find option info for " << option; 0651 button->setText(grpOptions.first()); 0652 } else { 0653 button->setText(optionInfo->description); 0654 } 0655 } break; 0656 default: 0657 button->setText(i18np("%1 shortcut", "%1 shortcuts", grpOptions.size())); 0658 } 0659 } 0660 0661 void KCMKeyboardWidget::updateXkbShortcutsButtons() 0662 { 0663 updateXkbShortcutButton(GROUP_SWITCH_GROUP_NAME, uiWidget->xkbGrpShortcutBtn); 0664 updateXkbShortcutButton(LV3_SWITCH_GROUP_NAME, uiWidget->xkb3rdLevelShortcutBtn); 0665 } 0666 0667 void KCMKeyboardWidget::updateShortcutsUI() 0668 { 0669 updateXkbShortcutsButtons(); 0670 0671 delete actionCollection; 0672 actionCollection = new KeyboardLayoutActionCollection(this, true); 0673 QAction *toggleAction = actionCollection->getToggleAction(); 0674 const auto shortcuts = KGlobalAccel::self()->shortcut(toggleAction); 0675 uiWidget->kdeKeySequence->setKeySequence(shortcuts.isEmpty() ? QKeySequence() : shortcuts.first()); 0676 0677 actionCollection->loadLayoutShortcuts(keyboardConfig->layouts, rules); 0678 layoutsTableModel->refresh(); 0679 } 0680 0681 void KCMKeyboardWidget::updateLayoutsUI() 0682 { 0683 bool loopingOn = keyboardConfig->configureLayouts() && keyboardConfig->layoutLoopCount() != KeyboardConfig::NO_LOOPING; 0684 uiWidget->layoutLoopingCheckBox->setChecked(loopingOn); 0685 uiWidget->layoutLoopingGroupBox->setEnabled(loopingOn); 0686 if (loopingOn) { 0687 // Set maximum to 99 to make sure following setValue succeeds 0688 // Correct maximum value will be set in updateLoopCount() 0689 uiWidget->layoutLoopCountSpinBox->setMaximum(99); 0690 uiWidget->layoutLoopCountSpinBox->setValue(keyboardConfig->layoutLoopCount()); 0691 } else { 0692 uiWidget->layoutLoopCountSpinBox->clear(); 0693 } 0694 0695 updateLoopCount(); 0696 } 0697 0698 void KCMKeyboardWidget::updateHardwareUI(const QString &model) 0699 { 0700 int idx = uiWidget->keyboardModelComboBox->findData(model); 0701 if (idx != -1) { 0702 uiWidget->keyboardModelComboBox->setCurrentIndex(idx); 0703 } 0704 } 0705 0706 void KCMKeyboardWidget::populateWithCurrentLayouts() 0707 { 0708 const QList<LayoutUnit> layouts = X11Helper::getLayoutsList(); 0709 for (const auto &layoutUnit : layouts) { 0710 keyboardConfig->layouts.append(layoutUnit); 0711 } 0712 } 0713 0714 void KCMKeyboardWidget::populateWithCurrentXkbOptions() 0715 { 0716 if (!KWindowSystem::isPlatformX11()) { 0717 // TODO: implement for Wayland - query dbus maybe? 0718 return; 0719 } 0720 XkbConfig xkbConfig; 0721 QStringList xkbOptions; 0722 if (X11Helper::getGroupNames(QX11Info::display(), &xkbConfig, X11Helper::ALL)) { 0723 xkbOptions = xkbConfig.options; 0724 } 0725 auto *xkbOptionModel = dynamic_cast<XkbOptionsTreeModel *>(uiWidget->xkbOptionsTreeView->model()); 0726 xkbOptionModel->setXkbOptions(xkbOptions); 0727 keyboardConfig->setXkbOptions(xkbOptions); 0728 } 0729 0730 QString KCMKeyboardWidget::keyboardModelFromUI() const 0731 { 0732 return uiWidget->keyboardModelComboBox->itemData(uiWidget->keyboardModelComboBox->currentIndex()).toString(); 0733 } 0734 0735 KeyboardConfig::SwitchingPolicy KCMKeyboardWidget::switchingPolicyFromUI() const 0736 { 0737 if (uiWidget->switchByDesktopRadioBtn->isChecked()) { 0738 return KeyboardConfig::SWITCH_POLICY_DESKTOP; 0739 } else if (uiWidget->switchByApplicationRadioBtn->isChecked()) { 0740 return KeyboardConfig::SWITCH_POLICY_APPLICATION; 0741 } else if (uiWidget->switchByWindowRadioBtn->isChecked()) { 0742 return KeyboardConfig::SWITCH_POLICY_WINDOW; 0743 } else { 0744 return KeyboardConfig::SWITCH_POLICY_GLOBAL; 0745 } 0746 } 0747 0748 void KCMKeyboardWidget::setDefaultIndicatorVisible(QWidget *widget, bool visible) 0749 { 0750 widget->setProperty("_kde_highlight_neutral", visible); 0751 widget->update(); 0752 }