字符映射示例#

该示例显示一个字符数组,用户可以点击它们将文本输入到行编辑框。输入行编辑框的内容可以复制到剪贴板,并将其粘贴到其他应用程序中。此类工具的目的是允许用户输入键盘上可能无法使用或难以定位的字符。

下载此示例

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import sys

from PySide6.QtWidgets import QApplication

from mainwindow import MainWindow

"""PySide6 port of the widgets/widgets/ charactermap example from Qt6"""


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())
# 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'''
                       <p>Character: <span style="font-size: 24pt; font-family: {family}">
                       {c}</span><p>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)
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from PySide6.QtCore import Qt, qVersion, qFuzzyCompare
from PySide6.QtGui import QGuiApplication, QFontDatabase
from PySide6.QtWidgets import (QDialog, QDialogButtonBox,
                               QPlainTextEdit, QVBoxLayout)


def _format_font(font):
    family = font.family()
    size = font.pointSizeF()
    return f"{family}, {size}pt"


class FontInfoDialog(QDialog):

    def __init__(self, parent):
        super().__init__(parent)
        self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
        main_layout = QVBoxLayout(self)
        text_edit = QPlainTextEdit(self.text(), self)
        text_edit.setReadOnly(True)
        text_edit.setFont(QFontDatabase.systemFont(QFontDatabase.FixedFont))
        main_layout.addWidget(text_edit)
        button_box = QDialogButtonBox(QDialogButtonBox.Close, self)
        button_box.rejected.connect(self.reject)
        main_layout.addWidget(button_box)

    def text(self):
        default_font = QFontDatabase.systemFont(QFontDatabase.GeneralFont)
        fixed_font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
        title_font = QFontDatabase.systemFont(QFontDatabase.TitleFont)
        smallest_readable_font = QFontDatabase.systemFont(QFontDatabase.SmallestReadableFont)

        v = qVersion()
        platform = QGuiApplication.platformName()
        dpi = self.logicalDpiX()
        dpr = self.devicePixelRatio()
        text = f"Qt {v} on {platform}, {dpi}DPI"
        if not qFuzzyCompare(dpr, float(1)):
            text += f", device pixel ratio: {dpr}"
        text += ("\n\nDefault font : " + _format_font(default_font)
                 + "\nFixed font : " + _format_font(fixed_font)
                 + "\nTitle font : " + _format_font(title_font)
                 + "\nSmallest font: " + _format_font(smallest_readable_font))
        return text
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from PySide6.QtCore import Qt, QSignalBlocker, Slot
from PySide6.QtGui import QGuiApplication, QClipboard, QFont, QFontDatabase
from PySide6.QtWidgets import (QCheckBox, QComboBox, QFontComboBox,
                               QHBoxLayout, QLabel, QLineEdit, QMainWindow,
                               QPushButton, QScrollArea,
                               QVBoxLayout, QWidget)

from characterwidget import CharacterWidget
from fontinfodialog import FontInfoDialog


class MainWindow(QMainWindow):

    def __init__(self, parent=None):
        super().__init__(parent)

        self._character_widget = CharacterWidget()
        self._filter_combo = QComboBox()
        self._style_combo = QComboBox()
        self._size_combo = QComboBox()
        self._font_combo = QFontComboBox()
        self._line_edit = QLineEdit()
        self._scroll_area = QScrollArea()
        self._font_merging = QCheckBox()

        file_menu = self.menuBar().addMenu("File")
        file_menu.addAction("Quit", self.close)
        help_menu = self.menuBar().addMenu("Help")
        help_menu.addAction("Show Font Info", self.show_info)
        help_menu.addAction("About &Qt", qApp.aboutQt)  # noqa: F821

        central_widget = QWidget()

        self._filter_label = QLabel("Filter:")
        self._filter_combo = QComboBox()
        self._filter_combo.addItem("All", int(QFontComboBox.AllFonts.value))
        self._filter_combo.addItem("Scalable", int(QFontComboBox.ScalableFonts.value))
        self._filter_combo.addItem("Monospaced", int(QFontComboBox.MonospacedFonts.value))
        self._filter_combo.addItem("Proportional", int(QFontComboBox.ProportionalFonts.value))
        self._filter_combo.setCurrentIndex(0)
        self._filter_combo.currentIndexChanged.connect(self.filter_changed)

        self._font_label = QLabel("Font:")
        self._font_combo = QFontComboBox()
        self._size_label = QLabel("Size:")
        self._size_combo = QComboBox()
        self._style_label = QLabel("Style:")
        self._style_combo = QComboBox()
        self._font_merging_label = QLabel("Automatic Font Merging:")
        self._font_merging = QCheckBox()
        self._font_merging.setChecked(True)

        self._scroll_area = QScrollArea()
        self._character_widget = CharacterWidget()
        self._scroll_area.setWidget(self._character_widget)
        self.find_styles(self._font_combo.currentFont())
        self.find_sizes(self._font_combo.currentFont())

        self._line_edit = QLineEdit()
        self._line_edit.setClearButtonEnabled(True)
        self._clipboard_button = QPushButton("To clipboard")
        self._font_combo.currentFontChanged.connect(self.find_styles)
        self._font_combo.currentFontChanged.connect(self.find_sizes)
        self._font_combo.currentFontChanged.connect(self._character_widget.update_font)
        self._size_combo.currentTextChanged.connect(self._character_widget.update_size)
        self._style_combo.currentTextChanged.connect(self._character_widget.update_style)
        self._character_widget.character_selected.connect(self.insert_character)

        self._clipboard_button.clicked.connect(self.update_clipboard)
        self._font_merging.toggled.connect(self._character_widget.update_font_merging)

        controls_layout = QHBoxLayout()
        controls_layout.addWidget(self._filter_label)
        controls_layout.addWidget(self._filter_combo, 1)
        controls_layout.addWidget(self._font_label)
        controls_layout.addWidget(self._font_combo, 1)
        controls_layout.addWidget(self._size_label)
        controls_layout.addWidget(self._size_combo, 1)
        controls_layout.addWidget(self._style_label)
        controls_layout.addWidget(self._style_combo, 1)
        controls_layout.addWidget(self._font_merging_label)
        controls_layout.addWidget(self._font_merging, 1)
        controls_layout.addStretch(1)

        line_layout = QHBoxLayout()
        line_layout.addWidget(self._line_edit, 1)
        line_layout.addSpacing(12)
        line_layout.addWidget(self._clipboard_button)

        central_layout = QVBoxLayout(central_widget)
        central_layout.addLayout(controls_layout)
        central_layout.addWidget(self._scroll_area, 1)
        central_layout.addSpacing(4)
        central_layout.addLayout(line_layout)

        self.setCentralWidget(central_widget)
        self.setWindowTitle("Character Map")

    @Slot(QFont)
    def find_styles(self, font):
        current_item = self._style_combo.currentText()
        self._style_combo.clear()
        styles = QFontDatabase.styles(font.family())
        for style in styles:
            self._style_combo.addItem(style)

        style_index = self._style_combo.findText(current_item)

        if style_index == -1:
            self._style_combo.setCurrentIndex(0)
        else:
            self._style_combo.setCurrentIndex(style_index)

    @Slot(int)
    def filter_changed(self, f):
        filter = QFontComboBox.FontFilter(self._filter_combo.itemData(f))
        self._font_combo.setFontFilters(filter)
        count = self._font_combo.count()
        self.statusBar().showMessage(f"{count} font(s) found")

    @Slot(QFont)
    def find_sizes(self, font):
        current_size = self._size_combo.currentText()
        with QSignalBlocker(self._size_combo):
            # sizeCombo signals are now blocked until end of scope
            self._size_combo.clear()

            style = QFontDatabase.styleString(font)
            if QFontDatabase.isSmoothlyScalable(font.family(), style):
                sizes = QFontDatabase.standardSizes()
                for size in sizes:
                    self._size_combo.addItem(f"{size}")
                    self._size_combo.setEditable(True)
            else:
                sizes = QFontDatabase.smoothSizes(font.family(), style)
                for size in sizes:
                    self._size_combo.addItem(f"{size}")
                    self._size_combo.setEditable(False)

        size_index = self._size_combo.findText(current_size)

        if size_index == -1:
            self._size_combo.setCurrentIndex(max(0, self._size_combo.count() / 3))
        else:
            self._size_combo.setCurrentIndex(size_index)

    @Slot(str)
    def insert_character(self, character):
        self._line_edit.insert(character)

    @Slot()
    def update_clipboard(self):
        clipboard = QGuiApplication.clipboard()
        clipboard.setText(self._line_edit.text(), QClipboard.Clipboard)
        clipboard.setText(self._line_edit.text(), QClipboard.Selection)

    @Slot()
    def show_info(self):
        screen_geometry = self.screen().geometry()
        dialog = FontInfoDialog(self)
        dialog.setWindowTitle("Fonts")
        dialog.setAttribute(Qt.WA_DeleteOnClose)
        dialog.resize(screen_geometry.width() / 4, screen_geometry.height() / 4)
        dialog.show()