# Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause from textwrap import dedent from PySide6.QtCore import QSize, Qt, Slot, Signal from PySide6.QtGui import (QBrush, QFont, QFontDatabase, QFontMetrics, QPainter, QPen) from PySide6.QtWidgets import QToolTip, QWidget COLUMNS = 16 class CharacterWidget(QWidget): character_selected = Signal(str) def __init__(self, parent=None): super().__init__(parent) self._display_font = QFont() self._last_key = -1 self._square_size = int(0) self.calculate_square_size() self.setMouseTracking(True) @Slot(QFont) def update_font(self, font): self._display_font.setFamily(font.family()) self.calculate_square_size() self.adjustSize() self.update() @Slot(str) def update_size(self, fontSize): self._display_font.setPointSize(int(fontSize)) self.calculate_square_size() self.adjustSize() self.update() @Slot(str) def update_style(self, fontStyle): old_strategy = self._display_font.styleStrategy() self._display_font = QFontDatabase.font(self._display_font.family(), fontStyle, self._display_font.pointSize()) self._display_font.setStyleStrategy(old_strategy) self.calculate_square_size() self.adjustSize() self.update() @Slot(bool) def update_font_merging(self, enable): if enable: self._display_font.setStyleStrategy(QFont.PreferDefault) else: self._display_font.setStyleStrategy(QFont.NoFontMerging) self.adjustSize() self.update() def calculate_square_size(self): h = QFontMetrics(self._display_font, self).height() self._square_size = max(16, 4 + h) def sizeHint(self): return QSize(COLUMNS * self._square_size, (65536 / COLUMNS) * self._square_size) def _unicode_from_pos(self, point): row = int(point.y() / self._square_size) return row * COLUMNS + int(point.x() / self._square_size) def mouseMoveEvent(self, event): widget_position = self.mapFromGlobal(event.globalPosition().toPoint()) key = self._unicode_from_pos(widget_position) c = chr(key) family = self._display_font.family() text = dedent(f'''

Character: {c}

Value: 0x{key:x} ''') QToolTip.showText(event.globalPosition().toPoint(), text, self) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self._last_key = self._unicode_from_pos(event.position().toPoint()) if self._last_key != -1: c = chr(self._last_key) self.character_selected.emit(f"{c}") self.update() else: super().mousePressEvent(event) def paintEvent(self, event): with QPainter(self) as painter: self.render(event, painter) def render(self, event, painter): painter = QPainter(self) painter.fillRect(event.rect(), QBrush(Qt.white)) painter.setFont(self._display_font) redraw_rect = event.rect() begin_row = int(redraw_rect.top() / self._square_size) end_row = int(redraw_rect.bottom() / self._square_size) begin_column = int(redraw_rect.left() / self._square_size) end_column = int(redraw_rect.right() / self._square_size) painter.setPen(QPen(Qt.gray)) for row in range(begin_row, end_row + 1): for column in range(begin_column, end_column + 1): x = int(column * self._square_size) y = int(row * self._square_size) painter.drawRect(x, y, self._square_size, self._square_size) font_metrics = QFontMetrics(self._display_font) painter.setPen(QPen(Qt.black)) for row in range(begin_row, end_row + 1): for column in range(begin_column, end_column + 1): key = int(row * COLUMNS + column) painter.setClipRect(column * self._square_size, row * self._square_size, self._square_size, self._square_size) if key == self._last_key: painter.fillRect(column * self._square_size + 1, row * self._square_size + 1, self._square_size, self._square_size, QBrush(Qt.red)) text = chr(key) painter.drawText(column * self._square_size + (self._square_size / 2) - font_metrics.horizontalAdvance(text) / 2, row * self._square_size + 4 + font_metrics.ascent(), text)