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