Character Map Example
字符映射图示例显示了如何创建自定义窗口小部件,该窗口小部件既可以显示其自己的内容,又可以响应用户输入。
该示例显示一个字符数组,用户可以单击该字符以在行编辑中输入文本。 然后可以将行编辑的内容复制到剪贴板中,然后粘贴到其他应用程序中。 这种工具的目的是允许用户输入在键盘上不可用或难以定位的字符。
main.cpp
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QtWidgets>
class CharacterWidget;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
public slots:
void filterChanged(int);
void findStyles(const QFont &font);
void findSizes(const QFont &font);
void insertCharacter(const QString &character);
#ifndef QT_NO_CLIPBOARD
void updateClipboard();
#endif
void showInfo();
private:
QComboBox *filterCombo;
QFontComboBox *fontCombo;
QComboBox *sizeCombo;
QComboBox *styleCombo;
QCheckBox *fontMerging;
QScrollArea *scrollArea;
CharacterWidget *characterWidget;
QLineEdit *lineEdit;
};
#endif
MainWindow.cpp
#include "mainwindow.h"
#include "characterwidget.h"
Q_DECLARE_METATYPE(QFontComboBox::FontFilter)
/* *************************************************************
* 构造函数
* ************************************************************* */
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
// 添加两个菜单
QMenu *fileMenu = menuBar()->addMenu(tr("File"));
fileMenu->addAction(tr("Quit"), this, &QWidget::close);
QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
helpMenu->addAction(tr("Show Font Info"), this, &MainWindow::showInfo);
helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
// 中央控件
QWidget *centralWidget = new QWidget;
// 过滤标签
QLabel *filterLabel = new QLabel(tr("Filter:"));
//过滤组合框,用来过滤字体组合框中的某些特定类型的字体
filterCombo = new QComboBox;
filterCombo->addItem(tr("All"), QVariant::fromValue(QFontComboBox::AllFonts));
filterCombo->addItem(tr("Scalable"), QVariant::fromValue(QFontComboBox::ScalableFonts));
filterCombo->addItem(tr("Monospaced"), QVariant::fromValue(QFontComboBox::MonospacedFonts));
filterCombo->addItem(tr("Proportional"), QVariant::fromValue(QFontComboBox::ProportionalFonts));
filterCombo->setCurrentIndex(0);
// 关联过滤器组合框的当前索引变更信号到filterChanged()
connect(filterCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &MainWindow::filterChanged);
QLabel *fontLabel = new QLabel(tr("Font:"));
fontCombo = new QFontComboBox;
QLabel *sizeLabel = new QLabel(tr("Size:"));
sizeCombo = new QComboBox;
QLabel *styleLabel = new QLabel(tr("Style:"));
styleCombo = new QComboBox;
QLabel *fontMergingLabel = new QLabel(tr("Automatic Font Merging:"));
// 全局变量 字体合并的复选框
fontMerging = new QCheckBox;
fontMerging->setChecked(true);
// 全局变量 滚动区,用来滚动characterWidget
scrollArea = new QScrollArea;
characterWidget = new CharacterWidget;
scrollArea->setWidget(characterWidget);
// 查找字体组合框的当前字体的样式
findStyles(fontCombo->currentFont());
// 查找字体组合框的当前字体的大小
findSizes(fontCombo->currentFont());
lineEdit = new QLineEdit;
lineEdit->setClearButtonEnabled(true); // 末尾添加clearbutton
#ifndef QT_NO_CLIPBOARD
// 添加 剪切板 按钮
QPushButton *clipboardButton = new QPushButton(tr("&To clipboard"));
connect(clipboardButton, &QAbstractButton::clicked, this, &MainWindow::updateClipboard);
#endif
// 字体组合框变化
connect(fontCombo, &QFontComboBox::currentFontChanged,
this, &MainWindow::findStyles);
connect(fontCombo, &QFontComboBox::currentFontChanged,
this, &MainWindow::findSizes);
connect(fontCombo, &QFontComboBox::currentFontChanged,
characterWidget, &CharacterWidget::updateFont);
// 字体大小变化
connect(sizeCombo, &QComboBox::currentTextChanged,
characterWidget, &CharacterWidget::updateSize);
connect(styleCombo, &QComboBox::currentTextChanged,
characterWidget, &CharacterWidget::updateStyle);
// 字符显示控件的选择变化
connect(characterWidget, &CharacterWidget::characterSelected,
this, &MainWindow::insertCharacter);
// 字体合并复选框的toggled信号
connect(fontMerging, &QAbstractButton::toggled, characterWidget, &CharacterWidget::updateFontMerging);
// 布局
QHBoxLayout *controlsLayout = new QHBoxLayout;
controlsLayout->addWidget(filterLabel);
controlsLayout->addWidget(filterCombo, 1);
controlsLayout->addWidget(fontLabel);
controlsLayout->addWidget(fontCombo, 1);
controlsLayout->addWidget(sizeLabel);
controlsLayout->addWidget(sizeCombo, 1);
controlsLayout->addWidget(styleLabel);
controlsLayout->addWidget(styleCombo, 1);
controlsLayout->addWidget(fontMergingLabel);
controlsLayout->addWidget(fontMerging, 1);
controlsLayout->addStretch(1);
QHBoxLayout *lineLayout = new QHBoxLayout;
lineLayout->addWidget(lineEdit, 1);
lineLayout->addSpacing(12);
#ifndef QT_NO_CLIPBOARD
lineLayout->addWidget(clipboardButton);
#endif
QVBoxLayout *centralLayout = new QVBoxLayout;
centralLayout->addLayout(controlsLayout);
centralLayout->addWidget(scrollArea, 1);
centralLayout->addSpacing(4);
centralLayout->addLayout(lineLayout);
centralWidget->setLayout(centralLayout);
setCentralWidget(centralWidget);
setWindowTitle(tr("Character Map"));
}
/* *************************************************************
* findStyles
* ************************************************************* */
void MainWindow::findStyles(const QFont &font)
{
// 定义字体数据库
QFontDatabase fontDatabase;
// 获取样式组合框的当前项的内容
QString currentItem = styleCombo->currentText();
// 清空样式组合框
styleCombo->clear();
// 通过字体数据库返回当前字体家族的样式列表 "Light", "Light Italic", "Bold", "Oblique", "Demi"
const QStringList styles = fontDatabase.styles(font.family());
// 将字体家族的样式列表添加到样式组合框
for (const QString &style : styles)
styleCombo->addItem(style);
// 在样式组合框中查找保持的样式名称
int styleIndex = styleCombo->findText(currentItem);
// 没有找到,就设置当前索引为0,否则就设置查找到的索引
if (styleIndex == -1)
styleCombo->setCurrentIndex(0);
else
styleCombo->setCurrentIndex(styleIndex);
}
/* *************************************************************
* 过滤器组合框变化事件
* ************************************************************* */
void MainWindow::filterChanged(int f)
{
// 获取字体的类型过滤器
const QFontComboBox::FontFilter filter =
qvariant_cast<QFontComboBox::FontFilter>(filterCombo->itemData(f));
// 字体组合框使用过滤器
fontCombo->setFontFilters(filter);
// 更新状态栏信息
statusBar()->showMessage(tr("%n font(s) found", nullptr, fontCombo->count()));
}
/* *************************************************************
* 查找当前字体的可用大小
* ************************************************************* */
void MainWindow::findSizes(const QFont &font)
{
QFontDatabase fontDatabase;
QString currentSize = sizeCombo->currentText();
{ // sizeCombo信号现在被阻止,直到范围结束
const QSignalBlocker blocker(sizeCombo);
sizeCombo->clear();
// 当前字体家族和字体的样式可以平滑缩放
if (fontDatabase.isSmoothlyScalable(font.family(), fontDatabase.styleString(font))) {
// 获取标准大小列表
const QList<int> sizes = QFontDatabase::standardSizes();
// 添加到字体大小组合框
for (const int size : sizes) {
sizeCombo->addItem(QVariant(size).toString());
sizeCombo->setEditable(true);
}
}
// 当前字体家族和字体的样式不能平滑缩放
else {
// 获取家族字体和样式的字体的磅值列表
const QList<int> sizes = fontDatabase.smoothSizes(font.family(), fontDatabase.styleString(font));
for (const int size : sizes ) {
sizeCombo->addItem(QVariant(size).toString());
sizeCombo->setEditable(false);
}
}
}
int sizeIndex = sizeCombo->findText(currentSize);
if(sizeIndex == -1)
sizeCombo->setCurrentIndex(qMax(0, sizeCombo->count() / 3));
else
sizeCombo->setCurrentIndex(sizeIndex);
}
/* *************************************************************
* 在行文本里插入选择的字符串
* ************************************************************* */
void MainWindow::insertCharacter(const QString &character)
{
lineEdit->insert(character);
}
/* *************************************************************
* 更新剪切板
* ************************************************************* */
#ifndef QT_NO_CLIPBOARD
void MainWindow::updateClipboard()
{
QGuiApplication::clipboard()->setText(lineEdit->text(), QClipboard::Clipboard);
QGuiApplication::clipboard()->setText(lineEdit->text(), QClipboard::Selection);
}
#endif
/* *************************************************************
* 自定义信息对话框
* ************************************************************* */
class FontInfoDialog : public QDialog
{
public:
explicit FontInfoDialog(QWidget *parent = nullptr);
private:
QString text() const;
};
/* *************************************************************
* 自定义信息对话框的构造函数
* ************************************************************* */
FontInfoDialog::FontInfoDialog(QWidget *parent) : QDialog(parent)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
// 获取文本信息内容,添加到纯文本控件
QPlainTextEdit *textEdit = new QPlainTextEdit(text(), this);
textEdit->setReadOnly(true);
textEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
mainLayout->addWidget(textEdit);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
mainLayout->addWidget(buttonBox);
}
/* *************************************************************
* 自定义信息对话框的信息内容
* ************************************************************* */
QString FontInfoDialog::text() const
{
QString text;
QTextStream str(&text);
const QFont defaultFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
const QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
const QFont titleFont = QFontDatabase::systemFont(QFontDatabase::TitleFont);
const QFont smallestReadableFont = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
str << "Qt " << QT_VERSION_STR << " on " << QGuiApplication::platformName()
<< ", " << logicalDpiX() << "DPI";
if (!qFuzzyCompare(devicePixelRatioF(), qreal(1)))
str << ", device pixel ratio: " << devicePixelRatioF();
str << "\n\nDefault font : " << defaultFont.family() << ", " << defaultFont.pointSizeF() << "pt\n"
<< "Fixed font : " << fixedFont.family() << ", " << fixedFont.pointSizeF() << "pt\n"
<< "Title font : " << titleFont.family() << ", " << titleFont.pointSizeF() << "pt\n"
<< "Smallest font: " << smallestReadableFont.family() << ", " << smallestReadableFont.pointSizeF() << "pt\n";
return text;
}
/* *************************************************************
* 显示信息对话框
* ************************************************************* */
void MainWindow::showInfo()
{
const QRect screenGeometry = screen()->geometry();
FontInfoDialog *dialog = new FontInfoDialog(this);
dialog->setWindowTitle(tr("Fonts"));
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->resize(screenGeometry.width() / 4, screenGeometry.height() / 4);
dialog->show();
}
CharacterWidget.h
#ifndef CHARACTERWIDGET_H
#define CHARACTERWIDGET_H
#include <QtWidgets>
class CharacterWidget : public QWidget
{
Q_OBJECT
public:
CharacterWidget(QWidget *parent = nullptr);
// 该小部件不包含任何其他小部件,因此它必须提供自己的大小提示,以使其内容正确显示。
QSize sizeHint() const override;
public slots:
// updateFont()和updateStyle()插槽用于在用户更改应用程序中的设置时更新小部件中的字符的字体和样式。
void updateFont(const QFont &font);
void updateSize(const QString &fontSize);
void updateStyle(const QString &fontStyle);
void updateFontMerging(bool enable);
signals:
// characterSelected()信号,以便每当用户在小部件中选择一个字符时,程序的其他部分都得到通知
void characterSelected(const QString &character);
protected:
// 重新实现QWidget::mouseMoveEvent()事件处理程序并定义showToolTip()来启用该特性
void mouseMoveEvent(QMouseEvent *event) override;
// 重新实现QWidget::mousePressEvent(),以允许用户与小部件进行交互。
void mousePressEvent(QMouseEvent *event) override;
// 重新实现QWidget::paintEvent()以绘制自定义内容。
void paintEvent(QPaintEvent *event) override;
private:
void calculateSquareSize();
// 记录小部件的当前字体
QFont displayFont;
// 记录小部件中要显示的列数
int columns = 16;
// 记录小部件中当前高亮显示的字符
int lastKey = -1;
// 记录每个字符显示的方块
int squareSize = 0;
};
#endif
CharacterWidget.cpp
#include "characterwidget.h"
/* *************************************************************
* 显示字符的控件构造函数
* 小部件将被用作一个简单的画布
* 构造函数只调用基类构造函数并为私有数据成员定义一些默认值。
* ************************************************************* */
CharacterWidget::CharacterWidget(QWidget *parent)
: QWidget(parent)
{
// 计算方块的大小
calculateSquareSize();
// 启用鼠标跟踪,以允许我们在小部件中跟踪光标的移动
setMouseTracking(true);
}
/* *************************************************************
* 更新字体
* ************************************************************* */
void CharacterWidget::updateFont(const QFont &font)
{
// displayFont保存来自font的家族
displayFont.setFamily(font.family());
calculateSquareSize();
adjustSize();
update();
}
/* *************************************************************
* 更新大小
* ************************************************************* */
void CharacterWidget::updateSize(const QString &fontSize)
{
displayFont.setPointSize(fontSize.toInt());
calculateSquareSize();
adjustSize();
update();
}
/* *************************************************************
* 更新样式
* ************************************************************* */
void CharacterWidget::updateStyle(const QString &fontStyle)
{
QFontDatabase fontDatabase;
const QFont::StyleStrategy oldStrategy = displayFont.styleStrategy();
displayFont = fontDatabase.font(displayFont.family(), fontStyle, displayFont.pointSize());
displayFont.setStyleStrategy(oldStrategy);
calculateSquareSize();
adjustSize();
update();
}
/* *************************************************************
* 字体合并
* ************************************************************* */
void CharacterWidget::updateFontMerging(bool enable)
{
if (enable)
displayFont.setStyleStrategy(QFont::PreferDefault);
else
displayFont.setStyleStrategy(QFont::NoFontMerging);
adjustSize();
update();
}
/* *************************************************************
* 计算显示方块的大小
* 根据当前字体和当前设备的度量的高度确定
* ************************************************************* */
void CharacterWidget::calculateSquareSize()
{
squareSize = qMax(16, 4 + QFontMetrics(displayFont, this).height());
}
/* *************************************************************
* 重写sizeHint
* 根据columns squareSize 65536确定宽和高
* ************************************************************* */
QSize CharacterWidget::sizeHint() const
{
// 重写sizeHint
return QSize(columns*squareSize, (65536 / columns) * squareSize);
}
/* *************************************************************
* 重写mouseMoveEvent
* 根据columns squareSize 65536确定宽和高
* 设置工具提示
* ************************************************************* */
void CharacterWidget::mouseMoveEvent(QMouseEvent *event)
{
// 从屏幕的点映射到字符控件的坐标
QPoint widgetPosition = mapFromGlobal(event->globalPos());
// QPoint widgetPosition = event->pos();
uint key = (widgetPosition.y() / squareSize) * columns + widgetPosition.x() / squareSize;
// qDebug() << pos;
QString text = QString::fromLatin1("<p>Character: <span style=\"font-size: 24pt; font-family: %1\">")
.arg(displayFont.family())
+ QChar(key)
+ QString::fromLatin1("</span><p>Value: 0x")
+ QString::number(key, 16);
// 工具提示使用了全局坐标中定义的位置
QToolTip::showText(event->globalPos(), text, this);
}
/* *************************************************************
* 重写 mousePressEvent
* ************************************************************* */
void CharacterWidget::mousePressEvent(QMouseEvent *event)
{
// 鼠标左键按下
if (event->button() == Qt::LeftButton) {
lastKey = (event->y() / squareSize) * columns + event->x() / squareSize;
// 如果字符不是 Other_NotAssigned
if (QChar(lastKey).category() != QChar::Other_NotAssigned)
// 发出信号
emit characterSelected(QString(QChar(lastKey)));
update();
}
else
QWidget::mousePressEvent(event);
}
/* *************************************************************
* 重写 paintEvent
* ************************************************************* */
void CharacterWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.fillRect(event->rect(), QBrush(Qt::white));
painter.setFont(displayFont);
// 小部件需要重绘的区域,并用来确定需要显示哪些字符
QRect redrawRect = event->rect();
int beginRow = redrawRect.top() / squareSize;
int endRow = redrawRect.bottom() / squareSize;
int beginColumn = redrawRect.left() / squareSize;
int endColumn = redrawRect.right() / squareSize;
// 使用整数除法,获得显示的每个字符的行号和列号,并为显示的每个字符在小部件上绘制一个正方形。
painter.setPen(QPen(Qt::blue));
for (int row = beginRow; row <= endRow; ++row) {
for (int column = beginColumn; column <= endColumn; ++column) {
painter.drawRect(column * squareSize, row * squareSize, squareSize, squareSize);
}
}
// 数组中每个字符的符号在每个方块中绘制,最近选择的字符的符号显示为红色:
QFontMetrics fontMetrics(displayFont);
painter.setPen(QPen(Qt::META));
for (int row = beginRow; row <= endRow; ++row) {
for (int column = beginColumn; column <= endColumn; ++column) {
int key = row * columns + column;
// 启用剪辑矩形,不需要考虑视口中显示的区域和我们绘制的区域之间的差异,因为可见区域之外的所有东西都将被裁剪。
painter.setClipRect(column * squareSize, row * squareSize, squareSize, squareSize);
if (key == lastKey)
painter.fillRect(column * squareSize + 1, row * squareSize + 1,
squareSize, squareSize, QBrush(Qt::red));
painter.drawText(column * squareSize + (squareSize / 2) -
fontMetrics.horizontalAdvance(QChar(key)) / 2,
row * squareSize + 4 + fontMetrics.ascent(),
QString(QChar(key)));
}
}
}
总结
就是从0~65535个Unicode字符集用不同的字体显示。