diff --git a/src/main/python/key_widget.py b/src/main/python/key_widget.py new file mode 100644 index 0000000..607fdcf --- /dev/null +++ b/src/main/python/key_widget.py @@ -0,0 +1,61 @@ +from PyQt5.QtCore import pyqtSignal + +from any_keycode_dialog import AnyKeycodeDialog +from keyboard_widget import KeyboardWidget +from kle_serial import Key +from tabbed_keycodes import TabbedKeycodes +from util import KeycodeDisplay + + +class KeyWidget(KeyboardWidget): + + changed = pyqtSignal() + + def __init__(self): + super().__init__(None) + + self.padding = 1 + + self.keycode = 0 + + key = Key() + key.row = key.col = 0 + key.layout_index = key.layout_option = -1 + self.set_keys([key], []) + + self.anykey.connect(self.on_anykey) + + def mousePressEvent(self, ev): + super().mousePressEvent(ev) + if self.active_key is not None: + TabbedKeycodes.open_tray(self) + else: + TabbedKeycodes.close_tray() + + def mouseReleaseEvent(self, ev): + ev.accept() + + def on_keycode_changed(self, keycode): + """ Unlike set_keycode, this handles setting masked keycode inside the mask """ + + if self.active_mask: + if keycode > 0xFF: + return + keycode = (self.keycode & 0xFF00) | keycode + self.set_keycode(keycode) + + def on_anykey(self): + if self.active_key is None: + return + dlg = AnyKeycodeDialog(self.keycode) + if dlg.exec_() and dlg.value >= 0: + self.set_keycode(dlg.value) + + def set_keycode(self, kc): + if kc == self.keycode: + return + self.keycode = kc + KeycodeDisplay.display_keycode(self.widgets[0], self.keycode) + self.update() + + self.changed.emit() diff --git a/src/main/python/keyboard_comm.py b/src/main/python/keyboard_comm.py index fdaa5d3..33eb7bf 100644 --- a/src/main/python/keyboard_comm.py +++ b/src/main/python/keyboard_comm.py @@ -57,6 +57,12 @@ CMD_VIAL_QMK_SETTINGS_GET = 0x0A CMD_VIAL_QMK_SETTINGS_SET = 0x0B CMD_VIAL_QMK_SETTINGS_RESET = 0x0C +CMD_VIAL_DYNAMIC_ENTRY_OP = 0x0D + +DYNAMIC_VIAL_GET_NUMBER_OF_ENTRIES = 0x00 +DYNAMIC_VIAL_TAP_DANCE_GET = 0x01 +DYNAMIC_VIAL_TAP_DANCE_SET = 0x02 + # how much of a macro/keymap buffer we can read/write per packet BUFFER_FETCH_CHUNK = 28 @@ -207,6 +213,7 @@ class Keyboard: self.reload_keymap() self.reload_macros() self.reload_rgb() + self.reload_dynamic() def reload_layers(self): """ Get how many layers the keyboard has """ @@ -374,6 +381,22 @@ class Keyboard: self.backlight_effect = self.usb_send( self.dev, struct.pack(">BB", CMD_VIA_LIGHTING_GET_VALUE, QMK_BACKLIGHT_EFFECT), retries=20)[2] + def reload_dynamic(self): + if self.vial_protocol < 4: + self.tap_dance_count = 0 + self.tap_dance_entries = [] + return + data = self.usb_send(self.dev, struct.pack("BBB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_DYNAMIC_ENTRY_OP, + DYNAMIC_VIAL_GET_NUMBER_OF_ENTRIES), retries=20) + self.tap_dance_count = data[0] + self.tap_dance_entries = [] + for x in range(self.tap_dance_count): + data = self.usb_send(self.dev, struct.pack("BBBB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_DYNAMIC_ENTRY_OP, + DYNAMIC_VIAL_TAP_DANCE_GET, x), retries=20) + if data[0] != 0: + raise RuntimeError("failed retrieving tapdance entry {} from the device".format(x)) + self.tap_dance_entries.append(struct.unpack(" 0: create_custom_user_keycodes(keyboard.custom_keycodes) diff --git a/src/main/python/keymap_editor.py b/src/main/python/keymap_editor.py index 85139a9..f075e46 100644 --- a/src/main/python/keymap_editor.py +++ b/src/main/python/keymap_editor.py @@ -1,8 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-or-later import json -from PyQt5.QtGui import QPalette -from PyQt5.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QMessageBox, QApplication +from PyQt5.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QMessageBox from PyQt5.QtCore import Qt from any_keycode_dialog import AnyKeycodeDialog @@ -12,7 +11,7 @@ from keycodes import recreate_keyboard_keycodes, Keycode from keymaps import KEYMAPS from square_button import SquareButton from tabbed_keycodes import TabbedKeycodes -from util import tr +from util import tr, KeycodeDisplay from vial_device import VialKeyboard @@ -48,8 +47,6 @@ class KeymapEditor(BasicEditor): layout_editor.changed.connect(self.on_layout_changed) - self.keymap_override = KEYMAPS[0][1] - self.container.anykey.connect(self.on_any_keycode) self.tabbed_keycodes = TabbedKeycodes() @@ -60,6 +57,7 @@ class KeymapEditor(BasicEditor): self.addWidget(self.tabbed_keycodes) self.device = None + KeycodeDisplay.notify_keymap_override(self) def on_container_clicked(self): """ Called when a mouse click event is bubbled up to the editor's container """ @@ -115,6 +113,7 @@ class KeymapEditor(BasicEditor): recreate_keyboard_keycodes(self.keyboard) self.tabbed_keycodes.recreate_keycode_buttons() + TabbedKeycodes.tray.recreate_keycode_buttons() self.refresh_layer_display() self.container.setEnabled(self.valid()) @@ -135,11 +134,6 @@ class KeymapEditor(BasicEditor): self.keyboard.restore_layout(data) self.refresh_layer_display() - def set_keymap_override(self, override): - self.keymap_override = override - self.refresh_layer_display() - self.tabbed_keycodes.set_keymap_override(override) - def on_any_keycode(self): if self.container.active_key is None: return @@ -148,17 +142,6 @@ class KeymapEditor(BasicEditor): if dlg.exec_() and dlg.value >= 0: self.on_keycode_changed(dlg.value) - def code_is_overriden(self, code): - """ Check whether a country-specific keymap overrides a code """ - key = Keycode.find_outer_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[Keycode.find_outer_keycode(code).qmk_id] - return Keycode.label(code) - def code_for_widget(self, widget): if widget.desc.row is not None: return self.keyboard.layout[(self.current_layer, widget.desc.row, widget.desc.col)] @@ -177,20 +160,7 @@ class KeymapEditor(BasicEditor): for widget in self.container.widgets: code = self.code_for_widget(widget) - text = self.get_label(code) - tooltip = Keycode.tooltip(code) - mask = Keycode.is_mask(code) - mask_text = self.get_label(code & 0xFF) - if mask: - text = text.split("\n")[0] - widget.masked = mask - widget.setText(text) - widget.setMaskText(mask_text) - widget.setToolTip(tooltip) - if self.code_is_overriden(code): - widget.setColor(QApplication.palette().color(QPalette.Link)) - else: - widget.setColor(None) + KeycodeDisplay.display_keycode(widget, code) self.container.update() self.container.updateGeometry() @@ -245,3 +215,6 @@ class KeymapEditor(BasicEditor): self.refresh_layer_display() self.keyboard.set_layout_options(self.layout_editor.pack()) + + def on_keymap_override(self): + self.refresh_layer_display() diff --git a/src/main/python/main_window.py b/src/main/python/main_window.py index 8c32fe8..43011e6 100644 --- a/src/main/python/main_window.py +++ b/src/main/python/main_window.py @@ -18,8 +18,10 @@ from layout_editor import LayoutEditor from macro_recorder import MacroRecorder from qmk_settings import QmkSettings from rgb_configurator import RGBConfigurator +from tabbed_keycodes import TabbedKeycodes +from tap_dance import TapDance from unlocker import Unlocker -from util import tr, find_vial_devices, EXAMPLE_KEYBOARDS +from util import tr, find_vial_devices, EXAMPLE_KEYBOARDS, KeycodeDisplay from vial_device import VialKeyboard from matrix_test import MatrixTest @@ -58,12 +60,14 @@ class MainWindow(QMainWindow): self.keymap_editor = KeymapEditor(self.layout_editor) self.firmware_flasher = FirmwareFlasher(self) self.macro_recorder = MacroRecorder() + self.tap_dance = TapDance() self.qmk_settings = QmkSettings(self.appctx) self.matrix_tester = MatrixTest(self.layout_editor) self.rgb_configurator = RGBConfigurator() self.editors = [(self.keymap_editor, "Keymap"), (self.layout_editor, "Layout"), (self.macro_recorder, "Macros"), - (self.rgb_configurator, "Lighting"), (self.qmk_settings, "QMK Settings"), + (self.rgb_configurator, "Lighting"), (self.tap_dance, "Tap Dance"), + (self.qmk_settings, "QMK Settings"), (self.matrix_tester, "Matrix tester"), (self.firmware_flasher, "Firmware updater")] Unlocker.global_layout_editor = self.layout_editor @@ -88,6 +92,10 @@ class MainWindow(QMainWindow): layout.addWidget(self.tabs) layout.addWidget(self.lbl_no_devices) layout.setAlignment(self.lbl_no_devices, Qt.AlignHCenter) + self.tray_keycodes = TabbedKeycodes() + self.tray_keycodes.make_tray() + layout.addWidget(self.tray_keycodes) + self.tray_keycodes.hide() w = QWidget() w.setLayout(layout) self.setCentralWidget(w) @@ -257,7 +265,7 @@ class MainWindow(QMainWindow): self.current_device.keyboard.reload() for e in [self.layout_editor, self.keymap_editor, self.firmware_flasher, self.macro_recorder, - self.qmk_settings, self.matrix_tester, self.rgb_configurator]: + self.tap_dance, self.qmk_settings, self.matrix_tester, self.rgb_configurator]: e.rebuild(self.current_device) def refresh_tabs(self): @@ -327,7 +335,7 @@ class MainWindow(QMainWindow): def change_keyboard_layout(self, index): self.settings.setValue("keymap", KEYMAPS[index][0]) - self.keymap_editor.set_keymap_override(KEYMAPS[index][1]) + KeycodeDisplay.set_keymap_override(KEYMAPS[index][1]) def get_theme(self): return self.settings.value("theme", "Dark") @@ -340,6 +348,7 @@ class MainWindow(QMainWindow): msg.exec_() def on_tab_changed(self, index): + TabbedKeycodes.close_tray() old_tab = self.current_tab new_tab = None if index >= 0: diff --git a/src/main/python/tabbed_keycodes.py b/src/main/python/tabbed_keycodes.py index 9c2d5c4..da9e9ff 100644 --- a/src/main/python/tabbed_keycodes.py +++ b/src/main/python/tabbed_keycodes.py @@ -7,10 +7,9 @@ from PyQt5.QtGui import QPalette from constants import KEYCODE_BTN_RATIO from flowlayout import FlowLayout from keycodes import KEYCODES_BASIC, KEYCODES_ISO, KEYCODES_MACRO, KEYCODES_LAYERS, KEYCODES_QUANTUM, \ - KEYCODES_BACKLIGHT, KEYCODES_MEDIA, KEYCODES_SPECIAL, KEYCODES_SHIFTED, KEYCODES_USER, Keycode -from keymaps import KEYMAPS + KEYCODES_BACKLIGHT, KEYCODES_MEDIA, KEYCODES_SPECIAL, KEYCODES_SHIFTED, KEYCODES_USER, Keycode, KEYCODES_TAP_DANCE from square_button import SquareButton -from util import tr +from util import tr, KeycodeDisplay class TabbedKeycodes(QTabWidget): @@ -21,7 +20,8 @@ class TabbedKeycodes(QTabWidget): def __init__(self, parent=None): super().__init__(parent) - self.keymap_override = None + self.target = None + self.is_tray = False self.tab_basic = QScrollArea() self.tab_iso = QScrollArea() @@ -29,6 +29,7 @@ class TabbedKeycodes(QTabWidget): self.tab_quantum = QScrollArea() self.tab_backlight = QScrollArea() self.tab_media = QScrollArea() + self.tab_tap_dance = QScrollArea() self.tab_user = QScrollArea() self.tab_macro = QScrollArea() @@ -41,12 +42,15 @@ class TabbedKeycodes(QTabWidget): (self.tab_quantum, "Quantum", KEYCODES_QUANTUM), (self.tab_backlight, "Backlight", KEYCODES_BACKLIGHT), (self.tab_media, "App, Media and Mouse", KEYCODES_MEDIA), + (self.tab_tap_dance, "Tap Dance", KEYCODES_TAP_DANCE), (self.tab_user, "User", KEYCODES_USER), (self.tab_macro, "Macro", KEYCODES_MACRO), ]: layout = FlowLayout() if tab == self.tab_layers: self.layout_layers = layout + elif tab == self.tab_tap_dance: + self.layout_tap_dance = layout elif tab == self.tab_macro: self.layout_macro = layout elif tab == self.tab_user: @@ -71,16 +75,17 @@ class TabbedKeycodes(QTabWidget): self.addTab(tab, tr("TabbedKeycodes", label)) self.layer_keycode_buttons = [] + self.tap_dance_keycode_buttons = [] self.macro_keycode_buttons = [] self.user_keycode_buttons = [] - self.set_keymap_override(KEYMAPS[0][1]) + KeycodeDisplay.notify_keymap_override(self) - def create_buttons(self, layout, keycodes, wordWrap = False): + def create_buttons(self, layout, keycodes, word_wrap=False): buttons = [] for keycode in keycodes: btn = SquareButton() - btn.setWordWrap(wordWrap) + btn.setWordWrap(word_wrap) btn.setRelSize(KEYCODE_BTN_RATIO) btn.setToolTip(Keycode.tooltip(keycode.code)) btn.clicked.connect(lambda st, k=keycode: self.keycode_changed.emit(k.code)) @@ -91,28 +96,63 @@ class TabbedKeycodes(QTabWidget): return buttons def recreate_keycode_buttons(self): - for btn in self.layer_keycode_buttons + self.macro_keycode_buttons + self.user_keycode_buttons: + for btn in self.layer_keycode_buttons + self.tap_dance_keycode_buttons + self.macro_keycode_buttons \ + + self.user_keycode_buttons: self.widgets.remove(btn) btn.hide() btn.deleteLater() self.layer_keycode_buttons = self.create_buttons(self.layout_layers, KEYCODES_LAYERS) + self.tap_dance_keycode_buttons = self.create_buttons(self.layout_tap_dance, KEYCODES_TAP_DANCE) self.macro_keycode_buttons = self.create_buttons(self.layout_macro, KEYCODES_MACRO) - self.user_keycode_buttons = self.create_buttons(self.layout_user, KEYCODES_USER, wordWrap=True) - self.widgets += self.layer_keycode_buttons + self.macro_keycode_buttons + self.user_keycode_buttons + self.user_keycode_buttons = self.create_buttons(self.layout_user, KEYCODES_USER, word_wrap=True) + self.widgets += self.layer_keycode_buttons + self.tap_dance_keycode_buttons + \ + self.macro_keycode_buttons + self.user_keycode_buttons self.relabel_buttons() - def set_keymap_override(self, override): - self.keymap_override = override + def on_keymap_override(self): self.relabel_buttons() def relabel_buttons(self): for widget in self.widgets: qmk_id = widget.keycode.qmk_id - if qmk_id in self.keymap_override: - label = self.keymap_override[qmk_id] + if qmk_id in KeycodeDisplay.keymap_override: + label = KeycodeDisplay.keymap_override[qmk_id] highlight_color = QApplication.palette().color(QPalette.Link).getRgb() widget.setStyleSheet("QPushButton {color: rgb"+str(highlight_color)+";}") else: label = widget.keycode.label widget.setStyleSheet("QPushButton {}") widget.setText(label.replace("&", "&&")) + + @classmethod + def set_tray(cls, tray): + cls.tray = tray + + @classmethod + def open_tray(cls, target): + cls.tray.show() + if cls.tray.target is not None and cls.tray.target != target: + cls.tray.target.deselect() + cls.tray.target = target + + @classmethod + def close_tray(cls): + if cls.tray.target is not None: + cls.tray.target.deselect() + cls.tray.target = None + cls.tray.hide() + + def make_tray(self): + self.is_tray = True + TabbedKeycodes.set_tray(self) + + self.keycode_changed.connect(self.on_tray_keycode_changed) + self.anykey.connect(self.on_tray_anykey) + + def on_tray_keycode_changed(self, kc): + if self.target is not None: + self.target.on_keycode_changed(kc) + + def on_tray_anykey(self): + if self.target is not None: + self.target.on_anykey() diff --git a/src/main/python/tap_dance.py b/src/main/python/tap_dance.py new file mode 100644 index 0000000..5afb982 --- /dev/null +++ b/src/main/python/tap_dance.py @@ -0,0 +1,179 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +from PyQt5 import QtCore +from PyQt5.QtCore import pyqtSignal, QObject +from PyQt5.QtWidgets import QTabWidget, QWidget, QSizePolicy, QGridLayout, QVBoxLayout, QLabel, QLineEdit, QHBoxLayout, \ + QPushButton, QSpinBox + +from key_widget import KeyWidget +from tabbed_keycodes import TabbedKeycodes +from util import tr +from vial_device import VialKeyboard +from basic_editor import BasicEditor + + +class TapDanceEntryUI(QObject): + + key_changed = pyqtSignal() + timing_changed = pyqtSignal() + + def __init__(self, idx): + super().__init__() + + self.idx = idx + self.container = QGridLayout() + self.populate_container() + + w = QWidget() + w.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) + w.setLayout(self.container) + l = QVBoxLayout() + l.addStretch() + l.addSpacing(10) + l.addWidget(w) + l.setAlignment(w, QtCore.Qt.AlignHCenter) + l.addSpacing(10) + lbl = QLabel("Use TD({}) to set up this action in the keymap.".format(self.idx)) + l.addWidget(lbl) + l.setAlignment(lbl, QtCore.Qt.AlignHCenter) + l.addStretch() + self.w2 = QWidget() + self.w2.setLayout(l) + + def populate_container(self): + self.container.addWidget(QLabel("On tap"), 0, 0) + self.kc_on_tap = KeyWidget() + self.kc_on_tap.changed.connect(self.on_key_changed) + self.container.addWidget(self.kc_on_tap, 0, 1) + self.container.addWidget(QLabel("On hold"), 1, 0) + self.kc_on_hold = KeyWidget() + self.kc_on_hold.changed.connect(self.on_key_changed) + self.container.addWidget(self.kc_on_hold, 1, 1) + self.container.addWidget(QLabel("On double tap"), 2, 0) + self.kc_on_double_tap = KeyWidget() + self.kc_on_double_tap.changed.connect(self.on_key_changed) + self.container.addWidget(self.kc_on_double_tap, 2, 1) + self.container.addWidget(QLabel("On tap + hold"), 3, 0) + self.kc_on_tap_hold = KeyWidget() + self.kc_on_tap_hold.changed.connect(self.on_key_changed) + self.container.addWidget(self.kc_on_tap_hold, 3, 1) + self.container.addWidget(QLabel("Tapping term (ms)"), 4, 0) + self.txt_tapping_term = QSpinBox() + self.txt_tapping_term.valueChanged.connect(self.on_timing_changed) + self.txt_tapping_term.setMinimum(0) + self.txt_tapping_term.setMaximum(10000) + self.container.addWidget(self.txt_tapping_term, 4, 1) + + def widget(self): + return self.w2 + + def load(self, data): + objs = [self.kc_on_tap, self.kc_on_hold, self.kc_on_double_tap, self.kc_on_tap_hold, self.txt_tapping_term] + for o in objs: + o.blockSignals(True) + + self.kc_on_tap.set_keycode(data[0]) + self.kc_on_hold.set_keycode(data[1]) + self.kc_on_double_tap.set_keycode(data[2]) + self.kc_on_tap_hold.set_keycode(data[3]) + self.txt_tapping_term.setValue(data[4]) + + for o in objs: + o.blockSignals(False) + + def save(self): + return ( + self.kc_on_tap.keycode, + self.kc_on_hold.keycode, + self.kc_on_double_tap.keycode, + self.kc_on_tap_hold.keycode, + self.txt_tapping_term.value() + ) + + def on_key_changed(self): + self.key_changed.emit() + + def on_timing_changed(self): + self.timing_changed.emit() + + +class CustomTabWidget(QTabWidget): + + def mouseReleaseEvent(self, ev): + TabbedKeycodes.close_tray() + + +class TapDance(BasicEditor): + + def __init__(self): + super().__init__() + self.keyboard = None + + self.tap_dance_entries = [] + self.tap_dance_entries_available = [] + self.tabs = CustomTabWidget() + for x in range(128): + entry = TapDanceEntryUI(x) + entry.key_changed.connect(self.on_key_changed) + entry.timing_changed.connect(self.on_timing_changed) + self.tap_dance_entries_available.append(entry) + + self.addWidget(self.tabs) + buttons = QHBoxLayout() + buttons.addStretch() + self.btn_save = QPushButton(tr("TapDance", "Save")) + self.btn_save.clicked.connect(self.on_save) + btn_revert = QPushButton(tr("TapDance", "Revert")) + btn_revert.clicked.connect(self.on_revert) + buttons.addWidget(self.btn_save) + buttons.addWidget(btn_revert) + self.addLayout(buttons) + + def rebuild_ui(self): + while self.tabs.count() > 0: + self.tabs.removeTab(0) + self.tap_dance_entries = self.tap_dance_entries_available[:self.keyboard.tap_dance_count] + for x, e in enumerate(self.tap_dance_entries): + self.tabs.addTab(e.widget(), str(x)) + self.reload_ui() + + def reload_ui(self): + for x, e in enumerate(self.tap_dance_entries): + e.load(self.keyboard.tap_dance_get(x)) + self.update_modified_state() + + def on_save(self): + for x, e in enumerate(self.tap_dance_entries): + self.keyboard.tap_dance_set(x, self.tap_dance_entries[x].save()) + self.update_modified_state() + + def on_revert(self): + self.keyboard.reload_dynamic() + self.reload_ui() + + def rebuild(self, device): + super().rebuild(device) + if self.valid(): + self.keyboard = device.keyboard + self.rebuild_ui() + + def valid(self): + return isinstance(self.device, VialKeyboard) and \ + (self.device.keyboard and self.device.keyboard.vial_protocol >= 4 + and self.device.keyboard.tap_dance_count > 0) + + def on_key_changed(self): + self.on_save() + + def update_modified_state(self): + """ Update indication of which tabs are modified, and keep Save button enabled only if it's needed """ + has_changes = False + for x, e in enumerate(self.tap_dance_entries): + if self.tap_dance_entries[x].save() != self.keyboard.tap_dance_get(x): + has_changes = True + self.tabs.setTabText(x, "{}*".format(x)) + else: + self.tabs.setTabText(x, str(x)) + self.btn_save.setEnabled(has_changes) + + def on_timing_changed(self): + self.update_modified_state() diff --git a/src/main/python/util.py b/src/main/python/util.py index 1c020ae..2740431 100644 --- a/src/main/python/util.py +++ b/src/main/python/util.py @@ -5,9 +5,12 @@ import time from logging.handlers import RotatingFileHandler from PyQt5.QtCore import QCoreApplication, QStandardPaths +from PyQt5.QtGui import QPalette +from PyQt5.QtWidgets import QApplication from hidproxy import hid - +from keycodes import Keycode +from keymaps import KEYMAPS tr = QCoreApplication.translate @@ -147,3 +150,50 @@ def init_logger(): handler = RotatingFileHandler(path, maxBytes=5 * 1024 * 1024, backupCount=5) handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s")) logging.getLogger().addHandler(handler) + + +class KeycodeDisplay: + + keymap_override = KEYMAPS[0][1] + clients = [] + + @classmethod + def get_label(cls, code): + """ Get label for a specific keycode """ + if cls.code_is_overriden(code): + return cls.keymap_override[Keycode.find_outer_keycode(code).qmk_id] + return Keycode.label(code) + + @classmethod + def code_is_overriden(cls, code): + """ Check whether a country-specific keymap overrides a code """ + key = Keycode.find_outer_keycode(code) + return key is not None and key.qmk_id in cls.keymap_override + + @classmethod + def display_keycode(cls, widget, code): + text = cls.get_label(code) + tooltip = Keycode.tooltip(code) + mask = Keycode.is_mask(code) + mask_text = cls.get_label(code & 0xFF) + if mask: + text = text.split("\n")[0] + widget.masked = mask + widget.setText(text) + widget.setMaskText(mask_text) + widget.setToolTip(tooltip) + if cls.code_is_overriden(code): + widget.setColor(QApplication.palette().color(QPalette.Link)) + else: + widget.setColor(None) + + @classmethod + def set_keymap_override(cls, override): + cls.keymap_override = override + for client in cls.clients: + client.on_keymap_override() + + @classmethod + def notify_keymap_override(cls, client): + cls.clients.append(client) + client.on_keymap_override()