Commit c7a1f606 authored by Harald Sitter's avatar Harald Sitter 🏳🌈
Browse files

replace bespoke xkb geometry parser with xkb

Summary:
the bespoke parser was a horrendous drain on the build time of
plasma-desktop due to recursive templates.
it's been replaced with a new standalone binary that previews any
model/layout/variant/options combination as requested and a qtquick UI
which simplifies the actual rendering substantially.

the new code is better in that it:
- builds in a fraction of the time with a fraction of the power use
- renders complex models (such as the tm2020 or the kinesis) correctly
- because it entirely relies on xkb to figure out keysyms belonging to
  a given key, it's layout representation is not only substantially more
  complete it also correctly obeys options like eurosign:2
- renders numlock and the like (not that this is in fact very useful ^^)
- is following the system palette for coloring
- the paint code should be easier to graps and more robust because xkb
  provides completely consistent geometry and layout data meaning we can
  model this verbatim in qtq...
parent ff60bb0d
add_definitions(-DTRANSLATION_DOMAIN=\"kcmkeyboard\")
add_subdirectory(tastenbrett)
option(HAVE_XINPUT "X11 XInput" OFF)
option(HAVE_UDEV "UDev" OFF)
option(NEW_GEOMETRY "Keyboard geometry preview" OFF)
if (X11_Xinput_FOUND)
set(HAVE_XINPUT ON)
......@@ -12,7 +13,6 @@ else()
message(STATUS "X11 XInput and UDev is not found, either is required to keep layouts with keyboard hotplugging")
endif()
### kded daemon ###
set( kded_keyboard_SRCS
......@@ -79,37 +79,6 @@ endif()
install( TARGETS kded_keyboard DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kded )
### kcm keyboard ###
include_directories("preview")
find_package(Boost)
if(Boost_FOUND)
set(NEW_GEOMETRY ON)
set(preview_SRCS
preview/geometry_components.cpp
preview/geometry_parser.cpp
preview/kbpreviewframe.cpp
preview/keyboardlayout.cpp
preview/symbol_parser.cpp
preview/keyaliases.cpp
preview/keyboardlayout.cpp
preview/keyboardpainter.cpp
preview/keysymhelper.cpp
preview/keysym2ucs.cpp
)
set_source_files_properties(preview/geometry_parser.cpp preview/symbol_parser.cpp PROPERTIES COMPILE_FLAGS "-fexceptions")
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
# the default maximum template expansion depth (256) is not enough
set_property(SOURCE preview/geometry_parser.cpp APPEND_STRING PROPERTY COMPILE_FLAGS " -ftemplate-depth=512")
endif()
else(Boost_FOUND)
message("Boost not found, install Boost libraries to enable keyboard geometry preview")
endif(Boost_FOUND)
set(kcm_keyboard_PART_SRCS
kcm_keyboard.cpp
......@@ -124,7 +93,7 @@ set(kcm_keyboard_PART_SRCS
iso_codes.cpp
kcmmisc.cpp
bindings.cpp
${preview_SRCS} )
tastenbrett.cpp)
ecm_qt_declare_logging_category(kcm_keyboard_PART_SRCS
HEADER debug.h
......@@ -173,3 +142,5 @@ if(BUILD_TESTING)
find_package(Qt5Test ${QT_MIN_VERSION} CONFIG REQUIRED)
add_subdirectory( tests )
endif()
add_dependencies(kcm_keyboard tastenbrett)
......@@ -4,6 +4,4 @@
#cmakedefine HAVE_XINPUT
#cmakedefine HAVE_UDEV
#cmakedefine NEW_GEOMETRY
#endif // CONFIG_KEYBOARD_H
......@@ -23,17 +23,19 @@
#include "xkb_rules.h"
#include "flags.h"
#include "iso_codes.h"
#include "tastenbrett.h"
#include "ui_kcm_add_layout_dialog.h"
#include <config-keyboard.h>
AddLayoutDialog::AddLayoutDialog(const Rules* rules_, Flags* flags_, const QString& model_, bool showLabel, QWidget* parent):
AddLayoutDialog::AddLayoutDialog(const Rules* rules_, Flags* flags_, const QString& model_,
const QStringList &options_, bool showLabel, QWidget* parent):
QDialog(parent),
rules(rules_),
flags(flags_),
model(model_),
model(model_),
options(options_),
selectedLanguage(QStringLiteral("no_language"))
{
layoutDialogUi = new Ui_AddLayoutDialog();
......@@ -69,11 +71,9 @@ AddLayoutDialog::AddLayoutDialog(const Rules* rules_, Flags* flags_, const QStri
languageChanged(0);
connect(layoutDialogUi->languageComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &AddLayoutDialog::languageChanged);
connect(layoutDialogUi->layoutComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &AddLayoutDialog::layoutChanged);
#ifdef NEW_GEOMETRY
connect(layoutDialogUi->prevbutton, &QPushButton::clicked, this, &AddLayoutDialog::preview);
#else
layoutDialogUi->prevbutton->setVisible(false);
#endif
layoutDialogUi->prevbutton->setVisible(Tastenbrett::exists());
}
void AddLayoutDialog::languageChanged(int langIdx)
......@@ -160,19 +160,9 @@ void AddLayoutDialog::accept()
QDialog::accept();
}
#ifdef NEW_GEOMETRY
void AddLayoutDialog::preview()
{
int index = layoutDialogUi->variantComboBox->currentIndex();
QString variant = layoutDialogUi->variantComboBox->itemData(index).toString();
KeyboardPainter* layoutPreview = new KeyboardPainter();
QString title = Flags::getLongText(LayoutUnit(selectedLayout, variant), rules);
layoutPreview->generateKeyboardLayout(selectedLayout, variant, model, title);
layoutPreview->setModal(true);
layoutPreview->exec();
delete layoutPreview;
Tastenbrett::launch(model, selectedLayout, variant, options.join(','));
}
#endif
......@@ -23,7 +23,6 @@
#include <QDialog>
#include "keyboard_config.h"
#include "preview/keyboardpainter.h"
#include <config-keyboard.h>
......@@ -36,7 +35,8 @@ class AddLayoutDialog: public QDialog
Q_OBJECT
public:
AddLayoutDialog(const Rules* rules, Flags* flags, const QString& model, bool showLabel, QWidget* parent=nullptr);
AddLayoutDialog(const Rules* rules, Flags* flags, const QString& model,
const QStringList &options, bool showLabel, QWidget* parent=nullptr);
LayoutUnit getSelectedLayoutUnit() { return selectedLayoutUnit; }
QString getvariant(QString variant);
......@@ -44,15 +44,14 @@ public:
public Q_SLOTS:
void languageChanged(int langIdx);
void layoutChanged(int layoutIdx);
#ifdef NEW_GEOMETRY
void preview();
#endif
void layoutChanged(int layoutIdx);
void preview();
private:
const Rules* rules;
Flags* flags;
const QString& model;
const QStringList &options;
Ui_AddLayoutDialog* layoutDialogUi;
QString selectedLanguage;
QString selectedLayout;
......
......@@ -34,15 +34,13 @@
#include <QDebug>
#include "keyboard_config.h"
#ifdef NEW_GEOMETRY
#include "preview/keyboardpainter.h"
#endif
#include "xkb_rules.h"
#include "flags.h"
#include "x11_helper.h"
#include "kcm_view_models.h"
#include "kcm_add_layout_dialog.h"
#include "bindings.h"
#include "tastenbrett.h"
#include "kcmmisc.h"
#include "ui_kcm_add_layout_dialog.h"
......@@ -231,7 +229,12 @@ void KCMKeyboardWidget::addLayout()
return;
}
AddLayoutDialog dialog(rules, keyboardConfig->isFlagShown() ? flags : nullptr, keyboardConfig->keyboardModel, keyboardConfig->isLabelShown(), this);
AddLayoutDialog dialog(rules,
keyboardConfig->isFlagShown() ? flags : nullptr,
keyboardConfig->keyboardModel,
keyboardConfig->xkbOptions,
keyboardConfig->isLabelShown(),
this);
dialog.setModal(true);
if( dialog.exec() == QDialog::Accepted ) {
keyboardConfig->layouts.append( dialog.getSelectedLayoutUnit() );
......@@ -335,9 +338,7 @@ void KCMKeyboardWidget::initializeLayoutsUI()
connect(uiWidget->moveUpBtn, &QAbstractButton::clicked, this, &KCMKeyboardWidget::moveUp);
connect(uiWidget->moveDownBtn, &QAbstractButton::clicked, this, &KCMKeyboardWidget::moveDown);
#ifdef NEW_GEOMETRY
connect(uiWidget->previewButton, &QAbstractButton::clicked, this, &KCMKeyboardWidget::previewLayout);
#endif
connect(uiWidget->xkbGrpClearBtn, &QAbstractButton::clicked, this, &KCMKeyboardWidget::clearGroupShortcuts);
connect(uiWidget->xkb3rdLevelClearBtn, &QAbstractButton::clicked, this, &KCMKeyboardWidget::clear3rdLevelShortcuts);
......@@ -362,35 +363,32 @@ void KCMKeyboardWidget::initializeLayoutsUI()
connect(uiWidget->layoutLoopCountSpinBox, SIGNAL(valueChanged(int)), this, SLOT(uiChanged()));
}
#ifdef NEW_GEOMETRY
void KCMKeyboardWidget::previewLayout(){
void KCMKeyboardWidget::previewLayout()
{
QModelIndex index = uiWidget->layoutsTableView->currentIndex();
QModelIndex idcountry = index.sibling(index.row(),0) ;
QString country=uiWidget->layoutsTableView->model()->data(idcountry).toString();
QModelIndex idvariant = index.sibling(index.row(),2) ;
const QString country=uiWidget->layoutsTableView->model()->data(idcountry).toString();
const QModelIndex idvariant = index.sibling(index.row(),2) ;
QString variant=uiWidget->layoutsTableView->model()->data(idvariant).toString();
QString model = keyboardConfig->keyboardModel;
const QString model = keyboardConfig->keyboardModel;
const QStringList options = keyboardConfig->xkbOptions;
KeyboardPainter layoutPreview;
const LayoutInfo* layoutInfo = rules->getLayoutInfo(country);
if (!layoutInfo) {
return;
}
foreach(const VariantInfo* variantInfo, layoutInfo->variantInfos) {
if(variant==variantInfo->description){
variant=variantInfo->name;
for (const VariantInfo *variantInfo : layoutInfo->variantInfos) {
if (variant == variantInfo->description) {
variant = variantInfo->name;
break;
}
}
QString title = Flags::getLongText( LayoutUnit(country, variant), rules );
layoutPreview.generateKeyboardLayout(country,variant, model, title);
layoutPreview.setModal(true);
layoutPreview.exec();
const QString title = Flags::getLongText( LayoutUnit(country, variant), rules );
Tastenbrett::launch(model, country, variant, options.join(','), title);
}
#endif
void KCMKeyboardWidget::configureLayoutsChanged()
{
......@@ -420,11 +418,8 @@ void KCMKeyboardWidget::layoutSelectionChanged()
uiWidget->removeLayoutBtn->setEnabled( ! selected.isEmpty() );
QPair<int, int> rowsRange( getSelectedRowRange(selected) );
uiWidget->moveUpBtn->setEnabled( ! selected.isEmpty() && rowsRange.first > 0);
#ifdef NEW_GEOMETRY
uiWidget->previewButton->setEnabled( uiWidget->layoutsTableView->selectionModel()->selectedRows().size() == 1 );
#else
uiWidget->previewButton->setVisible(false);
#endif
uiWidget->previewButton->setVisible(Tastenbrett::exists());
uiWidget->moveDownBtn->setEnabled( ! selected.isEmpty() && rowsRange.second < keyboardConfig->layouts.size()-1 );
}
......
......@@ -68,9 +68,7 @@ private Q_SLOTS:
void moveDown();
void configureLayoutsChanged();
void configureXkbOptionsChanged();
#ifdef NEW_GEOMETRY
void previewLayout();
#endif
private:
Rules *rules;
......
Important:
Good to have:
* replace symkey2ucs with something more reliable (e.g. XLookupString?)
Cleanup:
* clean up the code (especially kbpreviewframe.cpp)
/*
* Copyright (C) 2012 Shivam Makkar (amourphious1992@gmail.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "geometry_components.h"
#include <QString>
#include <QDebug>
#include <QPoint>
Q_LOGGING_CATEGORY(KEYBOARD_PREVIEW, "keyboard_preview")
GShape::GShape()
{
cordi_count = 0;
}
void GShape::setCordinate(double a, double b)
{
cordii << QPoint(a, b);
cordi_count++;
}
void GShape::setApprox(double a, double b)
{
a -= approx.x();
b -= approx.y();
approx = QPoint(a, b);
}
QPoint GShape :: getCordii(int i) const
{
if (i < cordi_count) {
return cordii[i];
}
return QPoint();
}
void GShape::display()
{
qCDebug(KEYBOARD_PREVIEW) << "shape: " << sname << "\n";
qCDebug(KEYBOARD_PREVIEW) << "(" << approx.x() << "," << approx.y() << ");";
for (int i = 0; i < cordi_count; i++) {
qCDebug(KEYBOARD_PREVIEW) << cordii[i];
}
}
double GShape::size(int vertical) const
{
if (!cordii.isEmpty()) {
if (vertical == 0) {
if (approx.x() == 0 && approx.y() == 0) {
int max = 0;
for (int i = 0; i < cordi_count; i++) {
if (max < cordii[i].x()) {
max = cordii[i].x();
}
}
return max;
} else {
return approx.x();
}
} else {
if (approx.x() == 0 && approx.y() == 0) {
int max = 0;
for (int i = 0; i < cordi_count; i++) {
if (max < cordii[i].y()) {
max = cordii[i].y();
}
}
return max;
}
return approx.y();
}
}
return 0;
}
Key::Key()
{
offset = 0;
}
void Key::setKeyPosition(double x, double y)
{
position = QPoint(x, y);
}
void Key::showKey()
{
qCDebug(KEYBOARD_PREVIEW) << "\n\tKey: " << name << "\tshape: " << shapeName << "\toffset: " << offset;
qCDebug(KEYBOARD_PREVIEW) << "\tposition" << position;
}
Row::Row()
{
top = 0;
left = 0;
keyCount = 0;
vertical = 0;
keyList << Key();
}
void Row::addKey()
{
//qCDebug(KEYBOARD_PREVIEW) << "keyCount: " << keyCount;
keyCount++;
keyList << Key();
}
void Row::displayRow()
{
qCDebug(KEYBOARD_PREVIEW) << "\nRow: (" << left << "," << top << ")\n";
qCDebug(KEYBOARD_PREVIEW) << "vertical: " << vertical;
for (int i = 0; i < keyCount; i++) {
keyList[i].showKey();
}
}
Section::Section()
{
top = 0;
left = 0;
angle = 0;
rowCount = 0;
vertical = 0;
rowList << Row();
}
void Section::addRow()
{
//qCDebug(KEYBOARD_PREVIEW) << "\nrowCount: " << rowCount;
rowCount++;
rowList << Row();
}
void Section::displaySection()
{
//qCDebug(KEYBOARD_PREVIEW) << "\nSection: " << name << "\n\tposition: (" << left << "," << top << ");" << angle << "\n";
//qCDebug(KEYBOARD_PREVIEW) << "vertical: " << vertical;
for (int i = 0; i < rowCount; i++) {
qCDebug(KEYBOARD_PREVIEW) << "\n\t";
rowList[i].displayRow();
}
}
Geometry::Geometry()
{
sectionTop = 0;
sectionLeft = 0;
rowTop = 0;
rowLeft = 0;
keyGap = 0;
shape_count = 0;
width = 0;
height = 0;
sectionCount = 0;
vertical = 0;
sectionList << Section();
shapes << GShape();
keyShape = QStringLiteral("NORM");
parsedGeometry = true;
}
void Geometry::setShapeName(const QString &n)
{
shapes[shape_count].setShapeName(n);
}
void Geometry::setShapeCord(double a, double b)
{
shapes[shape_count].setCordinate(a, b);
}
void Geometry::setShapeApprox(double a, double b)
{
shapes[shape_count].setApprox(a, b);
}
void Geometry::addShape()
{
shape_count++;
shapes << GShape();
}
void Geometry::display()
{
qCDebug(KEYBOARD_PREVIEW) << name << "\n" << description << "\nwidth:" << width
<< "\nheight:" << height << "\n" << "sectionTop:" << sectionTop;
qCDebug(KEYBOARD_PREVIEW) << "\nsectionLeft:" << sectionLeft << "\nrowTop:" << rowTop << "\nrowLeft:"
<< rowLeft << "\nkeyGap: " << keyGap << "\nkeyShape:" << keyShape << "\n";
qCDebug(KEYBOARD_PREVIEW) << "vertical:" << vertical;
for (int i = 0; i < shape_count; i++) {
shapes[i].display();
}
for (int j = 0; j < sectionCount; j++) {
sectionList[j].displaySection();
}
}
void Geometry::addSection()
{
//qCDebug(KEYBOARD_PREVIEW) << "\nsectionCount: " << sectionCount;
sectionCount++;
sectionList << Section();
}
GShape Geometry::findShape(const QString &name)
{
GShape l;
for (int i = 0; i < shape_count; i++) {
if (shapes[i].getShapeName() == name) {
return shapes[i];
}
}
return l;
}
/*
* Copyright (C) 2012 Shivam Makkar (amourphious1992@gmail.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef GEOMETRY_COMPONENTS_H
#define GEOMETRY_COMPONENTS_H
#include <QString>
#include <QPoint>
#include <QList>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(KEYBOARD_PREVIEW)
class GShape
{
private:
QString sname;
QPoint approx;
QList <QPoint> cordii;
int cordi_count;
public:
GShape();
void setCordinate(double a, double b);
void setApprox(double a, double b);
QPoint getCordii(int i) const;
void display();
double size(int vertical) const;
void setShapeName(const QString &n)
{
sname = n;
}
QPoint getApprox() const
{
return approx;
}
QString getShapeName()
{
return sname;
}
int getCordi_count() const
{
return cordi_count;
}
};