Пример "Character Map"
Файлы:
Пример "Character Map" показывает, как создать пользовательский виджет, который может выводить на экран и собственный вывод, и реакцию на пользовательский ввод.
Пример выводит на экран массив символов, которые пользователь может щелкнуть для ввода в однострочный редактор. Содержимое однострочного редактора можно затем скопировать в буфер обмена и вставить в другие приложения. Назначение такого вида инструмента заключается, в том чтобы позволить пользователю ввести символы, которые могут быть недоступны или затруднена локализация на клавиатуре.
Пример состоит из следующих классов:
- CharacterWidget выводит на экран доступные символы с текущим шрифтом и стилем.
- MainWindow предоставляет стандартное главное окно, которое содержит информацию о шрифте и стиле, область просмотра символов, однострочный редактор и кнопку для отправки текста в буфер обмена.
Определение класса CharacterWidget
Класс CharacterWidget используется для вывода на экран массива символов в с заданным пользователем шрифтом и стилем. Для большей гибкости, мы создаём подкласс QWidget и переопределим только те функции, которые нужны чтобы предоставить возможности базовой отрисовки и взаимодействия.
Определение класса выглядит примерно так:
class CharacterWidget : public QWidget
{
Q_OBJECT
public:
CharacterWidget(QWidget *parent = 0);
QSize sizeHint() const;
public slots:
void updateFont(const QFont &font);
void updateSize(const QString &fontSize);
void updateStyle(const QString &fontStyle);
void updateFontMerging(bool enable);
signals:
void characterSelected(const QString &character);
protected:
void mouseMoveEvent(QMouseEvent *event);
void mousePressEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *event);
private:
QFont displayFont;
int columns;
int lastKey;
int squareSize;
};
Виджет не содержит каких-либо других виджетов, поэтому он должен предоставить свои собственные подсказки размера чтобы позволить своему содержимому отображаться корректно. Мы переопределили QWidget::paintEvent() для отрисовки пользовательского содержимого. Также переопределили QWidget::mousePressEvent(), чтобы позволить пользователю взаимодействовать с виджетом.
Слоты updateFont() и updateStyle() используются для обновления шрифта и стиля символов в виджете всякий раз, когда пользователь меняет настройки приложения. Класс определяет сигнал characterSelected() для того, чтобы сообщать другим частям приложения всякий раз, когда пользователь выберет символ в виджете. В качестве вежливости, виджет предоставляет всплывающую подсказку, которая показывает значение текущего символа. Мы переопределили обработчик событий QWidget::mouseMoveEvent() и объявили showToolTip(), чтобы разрешить эту возможность.
Закрытые члены данных columns, displayFont и currentKey используются для записи количества отображаемых столбцов, текущего шрифта и текущего выделенного символа в виджете.
Реализация класса CharacterWidget
Поскольку виджет используется как простой холст, то конструктор вызывает только конструктор базового класса и определяет некоторые значения по умолчанию закрытых членов данных.
CharacterWidget::CharacterWidget(QWidget *parent)
: QWidget(parent)
{
squareSize = 24;
columns = 16;
lastKey = -1;
setMouseTracking(true);
}
Инициализируем currentKey значением -1 чтобы показать, что первоначально никакой символ не выбран. Разрешаем отслеживание мыши, чтобы позволить нам двигаться по виджету вслед за курсором.
Класс предоставляет две функции, позволяющие настроить шрифт и стиль шрифта. Обе они модифицируют экранный шрифт виджета и вызывают update():
void CharacterWidget::updateFont(const QFont &font)
{
displayFont.setFamily(font.family());
squareSize = qMax(24, QFontMetrics(displayFont).xHeight() * 3);
adjustSize();
update();
}
void CharacterWidget::updateSize(const QString &fontSize)
{
displayFont.setPointSize(fontSize.toInt());
squareSize = qMax(24, QFontMetrics(displayFont).xHeight() * 3);
adjustSize();
update();
}
Для вывода на экран используем шрифт фиксированного размера. Аналогичным образом, подсказка фиксированного размера предоставляется функцией sizeHint():
QSize CharacterWidget::sizeHint() const
{
return QSize(columns*squareSize, (65536/columns)*squareSize);
}
Три функции стандартных событий реализованы так, чтобы виджет мог реагировать на щелчки, предоставлять всплывающие подсказки и отрисовывать доступные символы. Функция paintEvent() показывает, как содержимое виджета размещается и выводится на экран:
void CharacterWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.fillRect(event->rect(), QBrush(Qt::white));
painter.setFont(displayFont);
QPainter создаётся для виджета и в любом случае мы гарантируем, что фон виджета отрисовывается. Шрифт рисовальщика устанавливается таким же, что и определённый пользователем экранный шрифт.
Область виджета, которую нужно перерисовать, используется для определения, какие символы нужно вывести на экран:
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::gray));
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::black));
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.width(QChar(key))/2,
row*squareSize + 4 + fontMetrics.ascent(),
QString(QChar(key)));
}
}
}
Нам не нужно учитывать разницу между выводимой на экран областью в окно просмотра и областью, рисуемой поскольку всё за пределами видимой области будет отсечено.
mousePressEvent() определяет, как виджет реагирует на щелчки кнопок мыши.
void CharacterWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
lastKey = (event->y()/squareSize)*columns + event->x()/squareSize;
if (QChar(lastKey).category() != QChar::NoCategory)
emit characterSelected(QString(QChar(lastKey)));
update();
}
else
QWidget::mousePressEvent(event);
}
Нам интересно только когда пользователь щёлкнет левой кнопкой по виджету. Когда это происходит, мы вычисляем, какой символ был выбран и отправляем сигнал characterSelected(). Номер символа находим делением x- и y-координат щелчка на размер квадратной ячейки сетки символа. Поскольку количество столбцов в виджете определяется переменной columns, мы просто умножаем индекс строки на её значение и прибавляем номер столбца чтобы получить номер символа.
Если были нажаты другие кнопки мыши, то событие передаётся в базовый класс QWidget. Этим гарантируется, что событие будет правильно обработано другими заинтересованными виджетами.
mouseMoveEvent() отображает позицию курсора мыши в глобальных координатах на координаты виджета, а также определяет символ, по которому щелкнули выполняя вычисление.
void CharacterWidget::mouseMoveEvent(QMouseEvent *event)
{
QPoint widgetPosition = mapFromGlobal(event->globalPos());
uint key = (widgetPosition.y()/squareSize)*columns + widgetPosition.x()/squareSize;
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);
}
Всплывающая подсказка предоставляет поцицию, определённую в глобальных координатах.
Определение класса MainWindow
Класс MainWindow предоставляет минимальный интерфейс для примера, только с конструктором, слотами, которые реагируют на сигналы отправленные стандартными виджетами, и несколькими вспомогательными функциями, которые используются для настройки пользовательского интерфейса.
Определение класса выглядит примерно так:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
public slots:
void findStyles(const QFont &font);
void findSizes(const QFont &font);
void insertCharacter(const QString &character);
void updateClipboard();
private:
CharacterWidget *characterWidget;
QClipboard *clipboard;
QComboBox *styleCombo;
QComboBox *sizeCombo;
QFontComboBox *fontCombo;
QLineEdit *lineEdit;
QScrollArea *scrollArea;
QCheckBox *fontMerging;
};
Главное окно содержит различные виджеты, которые используются для управления тем, как символы будут выводиться на экран, а также определяет функцию findFonts() для ясности и удобства. Слот findStyles() используется виджетами для определения доступных стилей, insertCharacter() - вставляет выбранный пользователем символ в однострочный редактор окна, а updateClipboard() - синхронизирует буфер обмена с содержимым однострочного редактора.
Реализация класса MainWindow
В конструкторе мы устанавливаем центральный виджет и заполняем его несколькими стандартными виджетами (два выпадающих списка, однострочный редактор и кнопка). Также мы создаём пользовательский виджет CharacterWidget, и добавляем QScrollArea для того, чтобы можно просматривать его содержимое:
MainWindow::MainWindow()
{
QWidget *centralWidget = new QWidget;
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);
scrollArea = new QScrollArea;
characterWidget = new CharacterWidget;
scrollArea->setWidget(characterWidget);
QScrollArea предоставляет область просмотра на CharacterWidget, если мы установим его виджет и обработаем большую часть работы, необходимой для предоставления прокручиваемой области просмотра.
Выпадающий список выбора шрифта автоматически заполняется списком доступных шрифтов. Перечисляем доступные для текущего шрифта стили в выпадающем списке используя следующую функцию:
findStyles(fontCombo->currentFont());
Однострочный редактор и кнопка используются для отправки текста в буфер обмена:
lineEdit = new QLineEdit;
QPushButton *clipboardButton = new QPushButton(tr("&To clipboard"));
Также мы получаем объект буфера обмена, чтобы можно было отправить введённый пользователем текст в другие приложения.
Большая часть отправляемых в примере сигналов приходят от стандартных виджетов. Мы соединяем эти сигналы со слотами в данном классе и со слотами, предоставляемым другими виджетами.
connect(fontCombo, SIGNAL(currentFontChanged(const QFont &)),
this, SLOT(findStyles(const QFont &)));
connect(fontCombo, SIGNAL(currentFontChanged(const QFont &)),
this, SLOT(findSizes(const QFont &)));
connect(fontCombo, SIGNAL(currentFontChanged(const QFont &)),
characterWidget, SLOT(updateFont(const QFont &)));
connect(sizeCombo, SIGNAL(currentIndexChanged(const QString &)),
characterWidget, SLOT(updateSize(const QString &)));
connect(styleCombo, SIGNAL(currentIndexChanged(const QString &)),
characterWidget, SLOT(updateStyle(const QString &)));
Сигнал выпадающего списка выбора шрифта currentFontChanged() соединяется с функцией findStyles(), чтобы можно было показать список доступных стилей для всех используемых шрифтов. Поскольку и шрифт, и стиль может изменить пользователь, сигнал выпадающего списка выбора шрифта currentFontChanged() и выпадающего списка стиля currentIndexChanged() соединяются непосредственно с виджетом символа.
Последние два соединения позволяют выбирать символы в виджете символов, а также вставлять текст в буфер обмена:
connect(characterWidget, SIGNAL(characterSelected(const QString &)),
this, SLOT(insertCharacter(const QString &)));
connect(clipboardButton, SIGNAL(clicked()), this, SLOT(updateClipboard()));
Виджет символов отправляет пользовательский сигнал characterSelected() когда пользователь щёлкает по символу, а обрабатывается функцией insertCharacter() данного класса. Буфер обмена изменяется когда кнопка отправляет сигнал clicked(), а обрабатываем этот сигнал с помощью функции updateClipboard().
Оставшийся код конструктора настраивает компоновку центрального виджета и предоставляет заголовок окна:
QHBoxLayout *controlsLayout = new QHBoxLayout;
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);
lineLayout->addWidget(clipboardButton);
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();
Начинаем с записи текущего выделенного стиля, а также очищаем выпадающий список стиля, чтобы можно было вставить стили, связанные с текущим семейством шрифта.
QString style;
foreach (style, fontDatabase.styles(font.family()))
styleCombo->addItem(style);
int styleIndex = styleCombo->findText(currentItem);
if (styleIndex == -1)
styleCombo->setCurrentIndex(0);
else
styleCombo->setCurrentIndex(styleIndex);
}
Мы используем базу данных шрифтов для сбора стилей, которые доступны для текущего шрифта, и вставляем их в выпадающий список стилей. Текущий элемент сбрасывается, если исходный стиль не доступен для этого шрифта.
Последние две функции - это слоты, которые реагируют на сигналы от виджета символов и кнопки главного окна. Функция insertCharacter() используется для вставки символов из виджета символов когда пользователь щёлкнет по символу:
void MainWindow::insertCharacter(const QString &character)
{
lineEdit->insert(character);
}
Символ вставляется в однострочный редактор в текущую позицию курсора.
Кнопка "To clipboard" главного окна соединена с функцией updateClipboard() чтобы при нажатии буфер обмена был обновлён содержимым однострочного редактора:
void MainWindow::updateClipboard()
{
clipboard->setText(lineEdit->text(), QClipboard::Clipboard);
clipboard->setText(lineEdit->text(), QClipboard::Selection);
}
Мы скопировали в буфер обмена весь текст из однострочного редактора, но не очистили однострочный редактор.
|