vial/src/main/python/keyboard_widget.py

181 lines
5.5 KiB
Python
Raw Normal View History

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 constants import KEY_WIDTH, KEY_SPACING, KEY_HEIGHT, KEYBOARD_WIDGET_PADDING
class KeyWidget:
def __init__(self, desc):
self.desc = desc
self.text = ""
self.tooltip = ""
self.rotation_x = (KEY_WIDTH + KEY_SPACING) * desc.rotation_x
self.rotation_y = (KEY_HEIGHT + KEY_SPACING) * desc.rotation_y
self.rotation_angle = desc.rotation_angle
self.x = (KEY_WIDTH + KEY_SPACING) * desc.x
self.y = (KEY_HEIGHT + KEY_SPACING) * desc.y
self.w = (KEY_WIDTH + KEY_SPACING) * desc.width - KEY_SPACING
self.h = (KEY_HEIGHT + KEY_SPACING) * desc.height - KEY_SPACING
self.rect = QRect(self.x, self.y, self.w, self.h)
self.has2 = desc.width2 != desc.width or desc.height2 != desc.height or desc.x2 != 0 or desc.y2 != 0
self.x2 = self.x + (KEY_WIDTH + KEY_SPACING) * desc.x2
self.y2 = self.y + (KEY_WIDTH + KEY_SPACING) * desc.y2
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.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
points = [(x1, y1), (x1, y2), (x2, y2), (x2, y1)]
bbox = []
for p in points:
t = QTransform()
t.translate(KEYBOARD_WIDGET_PADDING, KEYBOARD_WIDGET_PADDING)
t.translate(self.rotation_x, self.rotation_y)
t.rotate(self.rotation_angle)
t.translate(-self.rotation_x, -self.rotation_y)
p = t.map(QPointF(p[0], p[1]))
bbox.append(p)
return bbox
def calculate_draw_path(self):
path = QPainterPath()
path.addRect(int(self.x), int(self.y), int(self.w), int(self.h))
# second part only considered if different from first
if self.has2:
path2 = QPainterPath()
path2.addRect(int(self.x2), int(self.y2), int(self.w2), int(self.h2))
path = path.united(path2)
return path
def setText(self, text):
self.text = text
def setToolTip(self, tooltip):
self.tooltip = tooltip
class KeyboardWidget(QWidget):
clicked = pyqtSignal(KeyWidget)
def __init__(self):
super().__init__()
self.setMouseTracking(True)
self.keys = []
self.width = self.height = 0
self.active_key = None
def set_keys(self, keys):
self.keys = []
for key in keys:
self.keys.append(KeyWidget(key))
self.calculate_size()
self.update()
def calculate_size(self):
max_w = max_h = 0
for key in self.keys:
p = key.polygon.boundingRect().bottomRight()
max_w = max(max_w, p.x())
max_h = max(max_h, p.y())
self.width = max_w + 2 * KEYBOARD_WIDGET_PADDING
self.height = max_h + 2 * KEYBOARD_WIDGET_PADDING
def paintEvent(self, event):
qp = QPainter()
qp.begin(self)
qp.setRenderHint(QPainter.Antialiasing)
# add a little padding
qp.translate(KEYBOARD_WIDGET_PADDING, KEYBOARD_WIDGET_PADDING)
# for regular keycaps
regular_pen = qp.pen()
regular_pen.setColor(QColor("black"))
qp.setPen(regular_pen)
regular_brush = QBrush()
regular_brush.setColor(QColor("white"))
regular_brush.setStyle(Qt.SolidPattern)
qp.setBrush(regular_brush)
# for currently selected keycap
active_pen = qp.pen()
active_pen.setColor(QColor("white"))
active_brush = QBrush()
active_brush.setColor(QColor("black"))
active_brush.setStyle(Qt.SolidPattern)
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:
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)
qp.restore()
qp.end()
def minimumSizeHint(self):
return QSize(self.width, self.height)
def hit_test(self, pos):
for key in self.keys:
if key.polygon.containsPoint(pos, Qt.OddEvenFill):
return key
return None
def mousePressEvent(self, ev):
prev_active_key = self.active_key
self.active_key = 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()
def deselect(self):
if self.active_key is not None:
self.active_key = None
self.update()
def event(self, ev):
if ev.type() == QEvent.ToolTip:
key = self.hit_test(ev.pos())
if key is not None:
QToolTip.showText(ev.globalPos(), key.tooltip)
else:
QToolTip.hideText()
return super().event(ev)