From 215a54fca1d60e8557e4af7f02555fef12380752 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Sun, 18 Oct 2020 00:06:00 -0400 Subject: [PATCH] initial support for masked keycodes --- src/main/python/constants.py | 1 + src/main/python/keyboard_container.py | 25 +++++++-- src/main/python/keyboard_widget.py | 74 +++++++++++++++++++-------- src/main/python/keycodes.py | 20 +++++++- 4 files changed, 93 insertions(+), 27 deletions(-) diff --git a/src/main/python/constants.py b/src/main/python/constants.py index 9171830..f69ccd8 100644 --- a/src/main/python/constants.py +++ b/src/main/python/constants.py @@ -13,3 +13,4 @@ LAYER_BTN_STYLE = "border: 1px solid black; padding: 5px" ACTIVE_LAYER_BTN_STYLE = "border: 1px solid black; padding: 5px; background-color: black; color: white" KEYBOARD_WIDGET_PADDING = 5 +KEYBOARD_WIDGET_MASK_PADDING = 3 diff --git a/src/main/python/keyboard_container.py b/src/main/python/keyboard_container.py index 5ec9d35..2482525 100644 --- a/src/main/python/keyboard_container.py +++ b/src/main/python/keyboard_container.py @@ -7,7 +7,7 @@ from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QVBoxLayout from clickable_label import ClickableLabel from keyboard_widget import KeyboardWidget -from keycodes import keycode_label, keycode_tooltip +from keycodes import keycode_label, keycode_tooltip, keycode_is_mask from constants import LAYER_BTN_STYLE, ACTIVE_LAYER_BTN_STYLE from util import tr @@ -42,6 +42,7 @@ class KeyboardContainer(QWidget): self.rowcol = defaultdict(list) self.selected_row = -1 self.selected_col = -1 + self.selected_mask = False self.keyboard = None self.current_layer = 0 @@ -94,8 +95,14 @@ class KeyboardContainer(QWidget): code = self.keyboard.layout[(self.current_layer, row, col)] text = keycode_label(code) tooltip = keycode_tooltip(code) + mask = keycode_is_mask(code) + mask_text = keycode_label(code & 0xFF) + if mask: + text = text.split("\n")[0] for widget in widgets: + widget.masked = mask widget.setText(text) + widget.setMaskText(mask_text) widget.setToolTip(tooltip) self.container.update() @@ -109,13 +116,23 @@ class KeyboardContainer(QWidget): def set_key(self, keycode): """ Change currently selected key to provided keycode """ - if self.selected_row >= 0 and self.selected_col >= 0: - self.keyboard.set_key(self.current_layer, self.selected_row, self.selected_col, keycode) + l, r, c = self.current_layer, self.selected_row, self.selected_col + + if r >= 0 and c >= 0: + # if masked, ensure that this is a byte-sized keycode + if self.selected_mask: + if keycode > 0xFF: + return + keycode = (self.keyboard.layout[(l, r, c)] & 0xFF00) | keycode + + self.keyboard.set_key(l, r, c, keycode) self.refresh_layer_display() - def on_key_clicked(self, widget): + def on_key_clicked(self, widget, is_mask): """ Change which key is currently selected """ + self.selected_mask = is_mask + for (row, col), widgets in self.rowcol.items(): if widget in widgets: self.selected_row = row diff --git a/src/main/python/keyboard_widget.py b/src/main/python/keyboard_widget.py index d95a8f4..00d3d22 100644 --- a/src/main/python/keyboard_widget.py +++ b/src/main/python/keyboard_widget.py @@ -1,8 +1,8 @@ from PyQt5.QtGui import QPainter, QColor, QPainterPath, QTransform, QBrush, QPolygonF from PyQt5.QtWidgets import QWidget, QToolTip -from PyQt5.QtCore import Qt, QSize, QRect, QPointF, pyqtSignal, QEvent +from PyQt5.QtCore import Qt, QSize, QRect, QPointF, pyqtSignal, QEvent, QRectF -from constants import KEY_WIDTH, KEY_SPACING, KEY_HEIGHT, KEYBOARD_WIDGET_PADDING +from constants import KEY_WIDTH, KEY_SPACING, KEY_HEIGHT, KEYBOARD_WIDGET_PADDING, KEYBOARD_WIDGET_MASK_PADDING class KeyWidget: @@ -10,6 +10,7 @@ class KeyWidget: def __init__(self, desc): self.desc = desc self.text = "" + self.mask_text = "" self.tooltip = "" self.rotation_x = (KEY_WIDTH + KEY_SPACING) * desc.rotation_x @@ -30,15 +31,24 @@ class KeyWidget: self.w2 = (KEY_WIDTH + KEY_SPACING) * desc.width2 - KEY_SPACING self.h2 = (KEY_HEIGHT + KEY_SPACING) * desc.height2 - KEY_SPACING - self.bbox = self.calculate_bbox() + self.bbox = self.calculate_bbox(QRectF(self.x, self.y, self.w, self.h)) self.polygon = QPolygonF(self.bbox + [self.bbox[0]]) self.draw_path = self.calculate_draw_path() - def calculate_bbox(self): - x1 = self.x - y1 = self.y - x2 = self.x + self.w - y2 = self.y + self.h + # calculate areas where the inner keycode will be located + # nonmask = outer (e.g. Rsft_T) + # mask = inner (e.g. KC_A) + self.nonmask_rect = QRectF(self.x, self.y, self.w, self.h / 2) + self.mask_rect = QRectF(self.x + KEYBOARD_WIDGET_MASK_PADDING, self.y + self.h / 2, + self.w - 2 * KEYBOARD_WIDGET_MASK_PADDING, self.h / 2 - KEYBOARD_WIDGET_MASK_PADDING) + self.mask_bbox = self.calculate_bbox(self.mask_rect) + self.mask_polygon = QPolygonF(self.mask_bbox + [self.mask_bbox[0]]) + + def calculate_bbox(self, rect): + x1 = rect.topLeft().x() + y1 = rect.topLeft().y() + x2 = rect.bottomRight().x() + y2 = rect.bottomRight().y() points = [(x1, y1), (x1, y2), (x2, y2), (x2, y1)] bbox = [] for p in points: @@ -66,13 +76,16 @@ class KeyWidget: def setText(self, text): self.text = text + def setMaskText(self, text): + self.mask_text = text + def setToolTip(self, tooltip): self.tooltip = tooltip class KeyboardWidget(QWidget): - clicked = pyqtSignal(KeyWidget) + clicked = pyqtSignal(KeyWidget, bool) def __init__(self): super().__init__() @@ -81,6 +94,7 @@ class KeyboardWidget(QWidget): self.keys = [] self.width = self.height = 0 self.active_key = None + self.active_mask = False def set_keys(self, keys): self.keys = [] @@ -126,21 +140,36 @@ class KeyboardWidget(QWidget): active_brush.setColor(QColor("black")) active_brush.setStyle(Qt.SolidPattern) + mask_font = qp.font() + mask_font.setPointSize(mask_font.pointSize() * 0.8) + for idx, key in enumerate(self.keys): qp.save() qp.translate(key.rotation_x, key.rotation_y) qp.rotate(key.rotation_angle) qp.translate(-key.rotation_x, -key.rotation_y) - if self.active_key == key: + if self.active_key == key and not self.active_mask: qp.setPen(active_pen) qp.setBrush(active_brush) # draw the keycap qp.drawPath(key.draw_path) - # draw the legend - qp.drawText(key.rect, Qt.AlignCenter, key.text) + # if this is a mask key, draw the inner key + if key.masked: + qp.setFont(mask_font) + qp.drawText(key.nonmask_rect, Qt.AlignCenter, key.text) + + if self.active_key == key and self.active_mask: + qp.setPen(active_pen) + qp.setBrush(active_brush) + + qp.drawRect(key.mask_rect) + qp.drawText(key.mask_rect, Qt.AlignCenter, key.mask_text) + else: + # draw the legend + qp.drawText(key.rect, Qt.AlignCenter, key.text) qp.restore() @@ -150,20 +179,21 @@ class KeyboardWidget(QWidget): return QSize(self.width, self.height) def hit_test(self, pos): + """ Returns key, hit_masked_part """ + for key in self.keys: + if key.masked and key.mask_polygon.containsPoint(pos, Qt.OddEvenFill): + return key, True if key.polygon.containsPoint(pos, Qt.OddEvenFill): - return key - return None + return key, False + + return None, False def mousePressEvent(self, ev): - prev_active_key = self.active_key - - self.active_key = self.hit_test(ev.pos()) + self.active_key, self.active_mask = self.hit_test(ev.pos()) if self.active_key is not None: - self.clicked.emit(self.active_key) - - if prev_active_key != self.active_key: - self.update() + self.clicked.emit(self.active_key, self.active_mask) + self.update() def deselect(self): if self.active_key is not None: @@ -172,7 +202,7 @@ class KeyboardWidget(QWidget): def event(self, ev): if ev.type() == QEvent.ToolTip: - key = self.hit_test(ev.pos()) + key = self.hit_test(ev.pos())[0] if key is not None: QToolTip.showText(ev.globalPos(), key.tooltip) else: diff --git a/src/main/python/keycodes.py b/src/main/python/keycodes.py index 09ad6f6..5ccf5ba 100644 --- a/src/main/python/keycodes.py +++ b/src/main/python/keycodes.py @@ -4,11 +4,18 @@ class Keycode: - def __init__(self, code, qmk_id, label, tooltip=None): + masked_keycodes = set() + + def __init__(self, code, qmk_id, label, tooltip=None, masked=False): self.code = code self.qmk_id = qmk_id self.label = label self.tooltip = tooltip + # whether this keycode requires another sub-keycode + self.masked = masked + + if masked: + self.masked_keycodes.add(code) K = Keycode @@ -162,6 +169,8 @@ KEYCODES_ISO = [ KEYCODES_LAYERS = [] QK_ONE_SHOT_MOD = 0x5500 +QK_MOD_TAP = 0x6000 + MOD_LCTL = 0x01 MOD_LSFT = 0x02 MOD_LALT = 0x04 @@ -206,6 +215,8 @@ KEYCODES_QUANTUM = [ K(QK_ONE_SHOT_MOD | MOD_HYPR, "OSM(MOD_HYPR)", "OSM\nHyper", "Enable Control, Shift, Alt, and GUI for one keypress"), + K(QK_MOD_TAP | (MOD_RSFT << 8), "RSFT_T(kc)", "RSft_T\n(kc)", "Right Shift when held, kc when tapped", masked=True), + K(0x5C16, "KC_GESC", "Esc\n~", "Esc normally, but ~ when Shift or GUI is pressed"), K(0x5CD7, "KC_LSPO", "LS\n(", "Left Shift when held, ( when tapped"), K(0x5CD8, "KC_RSPC", "RS\n)", "Right Shift when held, ) when tapped"), @@ -232,6 +243,9 @@ K = None def find_keycode(code): + if keycode_is_mask(code): + code = code & 0xFF00 + for keycode in KEYCODES: if keycode.code == code: return keycode @@ -255,6 +269,10 @@ def keycode_tooltip(code): return tooltip +def keycode_is_mask(code): + return (code & 0xFF00) in Keycode.masked_keycodes + + def recreate_keycodes(): """ Regenerates global KEYCODES array """