add support for language-specific layouts

main
Ilya Zhuravlev 2021-01-01 06:27:48 -05:00
parent 5a1322c1be
commit 49dc6d21ab
10 changed files with 201 additions and 7 deletions

View File

@ -5,8 +5,9 @@ from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QVBoxLayout
from clickable_label import ClickableLabel from clickable_label import ClickableLabel
from keyboard_widget import KeyboardWidget, EncoderWidget from keyboard_widget import KeyboardWidget, EncoderWidget
from keycodes import keycode_label, keycode_tooltip, keycode_is_mask from keycodes import keycode_label, keycode_tooltip, keycode_is_mask, find_keycode
from constants import LAYER_BTN_STYLE, ACTIVE_LAYER_BTN_STYLE from constants import LAYER_BTN_STYLE, ACTIVE_LAYER_BTN_STYLE
from keymaps import KEYMAPS
from util import tr from util import tr
@ -41,6 +42,8 @@ class KeyboardContainer(QWidget):
layout_editor.changed.connect(self.on_layout_changed) layout_editor.changed.connect(self.on_layout_changed)
self.keymap_override = KEYMAPS[0][1]
def rebuild_layers(self): def rebuild_layers(self):
# delete old layer labels # delete old layer labels
for label in self.layer_labels: for label in self.layer_labels:
@ -67,6 +70,17 @@ class KeyboardContainer(QWidget):
self.current_layer = 0 self.current_layer = 0
self.on_layout_changed() self.on_layout_changed()
def code_is_overriden(self, code):
""" Check whether a country-specific keymap overrides a code """
key = find_keycode(code)
return key is not None and key.qmk_id in self.keymap_override
def get_label(self, code):
""" Get label for a specific keycode """
if self.code_is_overriden(code):
return self.keymap_override[find_keycode(code).qmk_id]
return keycode_label(code)
def refresh_layer_display(self): def refresh_layer_display(self):
""" Refresh text on key widgets to display data corresponding to current layer """ """ Refresh text on key widgets to display data corresponding to current layer """
@ -82,16 +96,20 @@ class KeyboardContainer(QWidget):
else: else:
code = self.keyboard.encoder_layout[(self.current_layer, widget.desc.encoder_idx, code = self.keyboard.encoder_layout[(self.current_layer, widget.desc.encoder_idx,
widget.desc.encoder_dir)] widget.desc.encoder_dir)]
text = keycode_label(code) text = self.get_label(code)
tooltip = keycode_tooltip(code) tooltip = keycode_tooltip(code)
mask = keycode_is_mask(code) mask = keycode_is_mask(code)
mask_text = keycode_label(code & 0xFF) mask_text = self.get_label(code & 0xFF)
if mask: if mask:
text = text.split("\n")[0] text = text.split("\n")[0]
widget.masked = mask widget.masked = mask
widget.setText(text) widget.setText(text)
widget.setMaskText(mask_text) widget.setMaskText(mask_text)
widget.setToolTip(tooltip) widget.setToolTip(tooltip)
if self.code_is_overriden(code):
widget.setColor(Qt.blue)
else:
widget.setColor(None)
self.container.update() self.container.update()
self.container.updateGeometry() self.container.updateGeometry()
@ -153,3 +171,7 @@ class KeyboardContainer(QWidget):
self.refresh_layer_display() self.refresh_layer_display()
self.keyboard.set_layout_options(self.layout_editor.pack()) self.keyboard.set_layout_options(self.layout_editor.pack())
def set_keymap_override(self, override):
self.keymap_override = override
self.refresh_layer_display()

View File

@ -16,6 +16,7 @@ class KeyWidget:
self.text = "" self.text = ""
self.mask_text = "" self.mask_text = ""
self.tooltip = "" self.tooltip = ""
self.color = None
self.rotation_x = (KEY_WIDTH + KEY_SPACING) * desc.rotation_x self.rotation_x = (KEY_WIDTH + KEY_SPACING) * desc.rotation_x
self.rotation_y = (KEY_HEIGHT + KEY_SPACING) * desc.rotation_y self.rotation_y = (KEY_HEIGHT + KEY_SPACING) * desc.rotation_y
@ -93,6 +94,9 @@ class KeyWidget:
def setActive(self, active): def setActive(self, active):
self.active = active self.active = active
def setColor(self, color):
self.color = color
class EncoderWidget(KeyWidget): class EncoderWidget(KeyWidget):
@ -248,7 +252,8 @@ class KeyboardWidget(QWidget):
qp.rotate(key.rotation_angle) qp.rotate(key.rotation_angle)
qp.translate(-key.rotation_x, -key.rotation_y) qp.translate(-key.rotation_x, -key.rotation_y)
if key.active or (self.active_key == key and not self.active_mask): active = key.active or (self.active_key == key and not self.active_mask)
if active:
qp.setPen(active_pen) qp.setPen(active_pen)
qp.setBrush(active_brush) qp.setBrush(active_brush)
@ -259,16 +264,24 @@ class KeyboardWidget(QWidget):
# if this is a mask key, draw the inner key # if this is a mask key, draw the inner key
if key.masked: if key.masked:
qp.setFont(mask_font) qp.setFont(mask_font)
qp.save()
if key.color is not None and not active:
qp.setPen(key.color)
qp.drawText(key.nonmask_rect, Qt.AlignCenter, key.text) qp.drawText(key.nonmask_rect, Qt.AlignCenter, key.text)
qp.restore()
if self.active_key == key and self.active_mask: if self.active_key == key and self.active_mask:
qp.setPen(active_pen) qp.setPen(active_pen)
qp.setBrush(active_brush) qp.setBrush(active_brush)
qp.drawRect(key.mask_rect) qp.drawRect(key.mask_rect)
if key.color is not None and not active:
qp.setPen(key.color)
qp.drawText(key.mask_rect, Qt.AlignCenter, key.mask_text) qp.drawText(key.mask_rect, Qt.AlignCenter, key.mask_text)
else: else:
# draw the legend # draw the legend
if key.color is not None and not active:
qp.setPen(key.color)
qp.drawText(key.rect, Qt.AlignCenter, key.text) qp.drawText(key.rect, Qt.AlignCenter, key.text)
qp.restore() qp.restore()

View File

@ -6,10 +6,12 @@ class Keycode:
masked_keycodes = set() masked_keycodes = set()
recorder_alias_to_keycode = dict() recorder_alias_to_keycode = dict()
qmk_id_to_keycode = dict()
def __init__(self, code, qmk_id, label, tooltip=None, masked=False, printable=None, recorder_alias=None): def __init__(self, code, qmk_id, label, tooltip=None, masked=False, printable=None, recorder_alias=None):
self.code = code self.code = code
self.qmk_id = qmk_id self.qmk_id = qmk_id
self.qmk_id_to_keycode[qmk_id] = self
self.label = label self.label = label
self.tooltip = tooltip self.tooltip = tooltip
# whether this keycode requires another sub-keycode # whether this keycode requires another sub-keycode
@ -31,6 +33,10 @@ class Keycode:
def find_by_recorder_alias(cls, alias): def find_by_recorder_alias(cls, alias):
return cls.recorder_alias_to_keycode.get(alias) return cls.recorder_alias_to_keycode.get(alias)
@classmethod
def find_by_qmk_id(cls, qmk_id):
return cls.qmk_id_to_keycode.get(qmk_id)
K = Keycode K = Keycode

View File

@ -0,0 +1,30 @@
# coding: utf-8
keymap = {
"KC_GRAVE": "²",
"KC_1": "1\n&",
"KC_2": "2\né",
"KC_3": '3\n"',
"KC_4": "4\n'",
"KC_5": "5\n(",
"KC_6": "6\n-",
"KC_7": "7\nè",
"KC_8": "8\n_",
"KC_9": "9\nç",
"KC_0": "0\nà",
"KC_MINUS": "°\n)",
"KC_Q": "A",
"KC_W": "Z",
"KC_LBRACKET": "¨\n^",
"KC_RBRACKET": "£\n$",
"KC_A": "Q",
"KC_SCOLON": "M",
"KC_QUOTE": "%\nù",
"KC_NONUS_HASH": "µ\n*",
"KC_NONUS_BSLASH": ">\n<",
"KC_Z": "W",
"KC_M": "?\n,",
"KC_COMMA": ".\n;",
"KC_DOT": "/\n:",
"KC_SLASH": "§\n!",
}

View File

@ -0,0 +1,25 @@
# coding: utf-8
keymap = {
"KC_GRAVE": "°\n^",
"KC_2": '"\n2',
"KC_3": "§\n3",
"KC_6": "&\n6",
"KC_7": "/\n7",
"KC_8": "(\n8",
"KC_9": ")\n9",
"KC_0": "=\n0",
"KC_MINUS": "?\nß",
"KC_EQUAL": "`\n´",
"KC_Y": "Z",
"KC_LBRACKET": "Ü",
"KC_RBRACKET": "*\n+",
"KC_SCOLON": "Ö",
"KC_QUOTE": "Ä",
"KC_NONUS_HASH": "'\n#",
"KC_NONUS_BSLASH": ">\n<",
"KC_Z": "Y",
"KC_COMMA": ";\n,",
"KC_DOT": ":\n.",
"KC_SLASH": "_\n-",
}

View File

@ -0,0 +1,44 @@
# coding: utf-8
keymap = {
"KC_GRAVE": "Ё",
"KC_2": '"\n2',
"KC_3": "\n3",
"KC_4": ";\n4",
"KC_6": ":\n6",
"KC_7": "?\n7",
"KC_Q": "Q\nЙ",
"KC_W": "W\nЦ",
"KC_E": "E\nУ",
"KC_R": "R\nК",
"KC_T": "T\nЕ",
"KC_Y": "Y\nН",
"KC_U": "U\nГ",
"KC_I": "I\nШ",
"KC_O": "O\nЩ",
"KC_P": "P\nЗ",
"KC_LBRACKET": "{\n[ Х",
"KC_RBRACKET": "}\n] Ъ",
"KC_BSLASH": "| /\n\\",
"KC_A": "A\nФ",
"KC_S": "S\nЫ",
"KC_D": "D\nВ",
"KC_F": "F\nА",
"KC_G": "G\nП",
"KC_H": "H\nР",
"KC_J": "J\nО",
"KC_K": "K\nЛ",
"KC_L": "L\nД",
"KC_SCOLON": ":\n; Ж",
"KC_QUOTE": "\"\n' Э",
"KC_Z": "Z\nЯ",
"KC_X": "X\nЧ",
"KC_C": "C\nС",
"KC_V": "V\nМ",
"KC_B": "B\nИ",
"KC_N": "N\nТ",
"KC_M": "M\nЬ",
"KC_COMMA": "<\n, Б",
"KC_DOT": ">\n. Ю",
"KC_SLASH": "? ,\n/ ."
}

View File

@ -41,3 +41,7 @@ class KeymapEditor(BasicEditor):
def restore_layout(self, data): def restore_layout(self, data):
self.keyboard_container.restore_layout(data) self.keyboard_container.restore_layout(data)
def set_keymap_override(self, override):
self.keyboard_container.set_keymap_override(override)
self.tabbed_keycodes.set_keymap_override(override)

View File

@ -0,0 +1,15 @@
from keycodes import Keycode
from keymap import french, german, russian
KEYMAPS = [
("QWERTY", dict()),
("French (AZERTY)", french.keymap),
("German (QWERTZ)", german.keymap),
("Russian (ЙЦУКЕН)", russian.keymap),
]
# make sure that qmk IDs we used are all correct
for name, keymap in KEYMAPS:
for qmk_id in keymap.keys():
if Keycode.find_by_qmk_id(qmk_id) is None:
raise RuntimeError("Misconfigured - cannot find QMK keycode {}".format(qmk_id))

View File

@ -2,12 +2,13 @@
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QComboBox, QToolButton, QHBoxLayout, QVBoxLayout, QMainWindow, QAction, qApp, \ from PyQt5.QtWidgets import QWidget, QComboBox, QToolButton, QHBoxLayout, QVBoxLayout, QMainWindow, QAction, qApp, \
QFileDialog, QDialog, QTabWidget QFileDialog, QDialog, QTabWidget, QActionGroup
import json import json
from firmware_flasher import FirmwareFlasher from firmware_flasher import FirmwareFlasher
from keymap_editor import KeymapEditor from keymap_editor import KeymapEditor
from keymaps import KEYMAPS
from layout_editor import LayoutEditor from layout_editor import LayoutEditor
from macro_recorder import MacroRecorder from macro_recorder import MacroRecorder
from unlocker import Unlocker from unlocker import Unlocker
@ -94,6 +95,17 @@ class MainWindow(QMainWindow):
keyboard_reset_act = QAction(tr("MenuSecurity", "Reboot to bootloader"), self) keyboard_reset_act = QAction(tr("MenuSecurity", "Reboot to bootloader"), self)
keyboard_reset_act.triggered.connect(self.reboot_to_bootloader) keyboard_reset_act.triggered.connect(self.reboot_to_bootloader)
keyboard_layout_menu = self.menuBar().addMenu(tr("Menu", "Keyboard layout"))
keymap_group = QActionGroup(self)
for idx, keymap in enumerate(KEYMAPS):
act = QAction(tr("KeyboardLayout", keymap[0]), self)
act.triggered.connect(lambda checked, x=idx: self.change_keyboard_layout(x))
act.setCheckable(True)
if idx == 0:
act.setChecked(True)
keymap_group.addAction(act)
keyboard_layout_menu.addAction(act)
self.security_menu = self.menuBar().addMenu(tr("Menu", "Security")) self.security_menu = self.menuBar().addMenu(tr("Menu", "Security"))
self.security_menu.addAction(keyboard_unlock_act) self.security_menu.addAction(keyboard_unlock_act)
self.security_menu.addAction(keyboard_lock_act) self.security_menu.addAction(keyboard_lock_act)
@ -194,3 +206,6 @@ class MainWindow(QMainWindow):
if isinstance(self.current_device, VialKeyboard): if isinstance(self.current_device, VialKeyboard):
self.unlocker.perform_unlock(self.current_device.keyboard) self.unlocker.perform_unlock(self.current_device.keyboard)
self.current_device.keyboard.reset() self.current_device.keyboard.reset()
def change_keyboard_layout(self, index):
self.keymap_editor.set_keymap_override(KEYMAPS[index][1])

View File

@ -7,6 +7,7 @@ from constants import KEYCODE_BTN_WIDTH, KEYCODE_BTN_HEIGHT
from flowlayout import FlowLayout from flowlayout import FlowLayout
from keycodes import keycode_tooltip, KEYCODES_BASIC, KEYCODES_ISO, KEYCODES_MACRO, KEYCODES_LAYERS, KEYCODES_QUANTUM, \ from keycodes import keycode_tooltip, KEYCODES_BASIC, KEYCODES_ISO, KEYCODES_MACRO, KEYCODES_LAYERS, KEYCODES_QUANTUM, \
KEYCODES_BACKLIGHT, KEYCODES_MEDIA KEYCODES_BACKLIGHT, KEYCODES_MEDIA
from keymaps import KEYMAPS
from util import tr from util import tr
@ -17,6 +18,8 @@ class TabbedKeycodes(QTabWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.keymap_override = None
self.tab_basic = QScrollArea() self.tab_basic = QScrollArea()
self.tab_iso = QScrollArea() self.tab_iso = QScrollArea()
self.tab_layers = QScrollArea() self.tab_layers = QScrollArea()
@ -25,6 +28,8 @@ class TabbedKeycodes(QTabWidget):
self.tab_media = QScrollArea() self.tab_media = QScrollArea()
self.tab_macro = QScrollArea() self.tab_macro = QScrollArea()
self.widgets = []
for (tab, label, keycodes) in [ for (tab, label, keycodes) in [
(self.tab_basic, "Basic", KEYCODES_BASIC), (self.tab_basic, "Basic", KEYCODES_BASIC),
(self.tab_iso, "ISO/JIS", KEYCODES_ISO), (self.tab_iso, "ISO/JIS", KEYCODES_ISO),
@ -40,7 +45,7 @@ class TabbedKeycodes(QTabWidget):
elif tab == self.tab_macro: elif tab == self.tab_macro:
self.layout_macro = layout self.layout_macro = layout
self.create_buttons(layout, keycodes) self.widgets += self.create_buttons(layout, keycodes)
tab.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) tab.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
tab.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) tab.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
@ -53,15 +58,17 @@ class TabbedKeycodes(QTabWidget):
self.layer_keycode_buttons = [] self.layer_keycode_buttons = []
self.macro_keycode_buttons = [] self.macro_keycode_buttons = []
self.set_keymap_override(KEYMAPS[0][1])
def create_buttons(self, layout, keycodes): def create_buttons(self, layout, keycodes):
buttons = [] buttons = []
for keycode in keycodes: for keycode in keycodes:
btn = QPushButton(keycode.label.replace("&", "&&")) btn = QPushButton()
btn.setFixedSize(KEYCODE_BTN_WIDTH, KEYCODE_BTN_HEIGHT) btn.setFixedSize(KEYCODE_BTN_WIDTH, KEYCODE_BTN_HEIGHT)
btn.setToolTip(keycode_tooltip(keycode.code)) btn.setToolTip(keycode_tooltip(keycode.code))
btn.clicked.connect(lambda st, k=keycode: self.keycode_changed.emit(k.code)) btn.clicked.connect(lambda st, k=keycode: self.keycode_changed.emit(k.code))
btn.keycode = keycode
layout.addWidget(btn) layout.addWidget(btn)
buttons.append(btn) buttons.append(btn)
@ -73,3 +80,16 @@ class TabbedKeycodes(QTabWidget):
btn.deleteLater() btn.deleteLater()
self.layer_keycode_buttons = self.create_buttons(self.layout_layers, KEYCODES_LAYERS) self.layer_keycode_buttons = self.create_buttons(self.layout_layers, KEYCODES_LAYERS)
self.macro_keycode_buttons = self.create_buttons(self.layout_macro, KEYCODES_MACRO) self.macro_keycode_buttons = self.create_buttons(self.layout_macro, KEYCODES_MACRO)
def set_keymap_override(self, override):
self.keymap_override = override
for widget in self.widgets:
qmk_id = widget.keycode.qmk_id
if qmk_id in self.keymap_override:
label = self.keymap_override[qmk_id]
widget.setStyleSheet("QPushButton {color: blue;}")
else:
label = widget.keycode.label
widget.setStyleSheet("QPushButton {}")
widget.setText(label.replace("&", "&&"))