Основная идея основана на использовании текстовых объектов. В местах текста, где должны размещаться виджеты, создаются изображения, которые помещаются в текст, а поверх них размещаются нужные нам виджеты.
Приведенный ниже пример показывает, как можно разместить QLineEdit в тексте.
1. Реализуем текстовый объект.
Заголовочный файл widgettextobject.h:
Код
C++ (Qt)class WidgetTextObject : public QObject, public QTextObjectInterface
{
Q_OBJECT
Q_INTERFACES(QTextObjectInterface)
public:
explicit WidgetTextObject(int objectType, QTextEdit *parent = nullptr);
~WidgetTextObject();
void resize(int w, int h);
QLineEdit *inputWidget();
int objectType();
QSizeF intrinsicSize(QTextDocument *, int, const QTextFormat &);
void drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format);
private:
int _objectType;
QTextEdit *_textEdit;
QLineEdit *_inputWidget;
В конструктор мы передает переменную objectType (тип объекта – целое число, назначение переменной будет показано ниже) и указатель на объект (наш Text Edit, в котором будем размещать содержимое). Назначение переменной objectType будет показано ниже.
Функция resize(int w, int h) предназначена для изменения размеров виджета.
Функция QLineEdit *inputWidget() возвращает указатель на виджет, который будет размещен в тексте.
Функция int objectType() возвращает значение objectType.
Функция QSizeF intrinsicSize(QTextDocument *, int, const QTextFormat &) возвращает размеры изображения, на которое будем помещать виджет.
void drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format) используется для отрисовки изображения, поверх которого будет наложен виджет.
Файл реализации widgettextobject.cpp:
Код
C++ (Qt)WidgetTextObject::WidgetTextObject(int objectType, QTextEdit *parent) : QObject(parent)
{
_objectType = objectType;
_textEdit = parent;
_inputWidget = new QLineEdit(parent->viewport());
_inputWidget->show();
}
WidgetTextObject::~WidgetTextObject()
{
_inputWidget->deleteLater();
}
void WidgetTextObject::resize(int w, int h)
{
_inputWidget->resize(w, h);
_textEdit->viewport()->update();
}
QLineEdit *WidgetTextObject::inputWidget()
{
return _inputWidget;
}
int WidgetTextObject::objectType()
{
return _objectType;
}
QSizeF WidgetTextObject::intrinsicSize(QTextDocument */*doc*/, int /*posInDocument*/,
const QTextFormat &/*format*/)
{
QSize size(_inputWidget->width() + 6, _inputWidget->height());
return QSizeF(size);
}
void WidgetTextObject::drawObject(QPainter *painter, const QRectF &rect,
QTextDocument *doc, int posInDocument,
const QTextFormat &format)
{
QPixmap pix(int(rect.width()), int(rect.height()));
pix.fill(QColor(255, 255, 255, 0));
if(format.toCharFormat().verticalAlignment() == QTextCharFormat::AlignNormal)
{
painter->drawPixmap(int(rect.x()), int(rect.y()), pix);
_inputWidget->move(int(rect.x()) + 3, int(rect.y()));
}
else
{
QTextCursor cursor(doc);
cursor.setPosition(posInDocument);
int cursorHeight = _textEdit->cursorRect(cursor).height();
int y = int(rect.y() + _inputWidget->height() / 2 - cursorHeight / 2);
painter->drawPixmap(int(rect.x()), y, pix);
_inputWidget->move(int(rect.x()) + 3, y);
}
}
При размещении виджета применяется два варианта вертикального выравнивания.
2. Создаем новый класс TextEditWithWidgets.
Заголовочный файл texteditwithwidgets.h
Код
Файл реализацииC++ (Qt)class TextEditWithWidgets : public QTextEdit
{
public:
TextEditWithWidgets(QWidget *parent);
void insertTextObject();
void resizeWidget(int w, int h);
void align(QTextCharFormat::VerticalAlignment alignment);
private:
int _objectType = QTextFormat::UserObject;
QVector <WidgetTextObject *> _objectsVector;
int indexObject(int objectType);
private slots:
void textChanged();
};
#include "texteditwithwidgets.h"
Код
C++ (Qt)TextEditWithWidgets::TextEditWithWidgets(QWidget *parent) :
QTextEdit(parent)
{
connect(this, &QTextEdit::textChanged, this, &TextEditWithWidgets::textChanged);
}
void TextEditWithWidgets::insertTextObject()
{
_objectType++;
WidgetTextObject *textObject = new WidgetTextObject(_objectType, this);
_objectsVector << textObject;
this->document()->documentLayout()->registerHandler(_objectType, textObject);
QTextCharFormat charFormat;
charFormat.setObjectType(_objectType);
QTextCursor cursor = this->textCursor();
cursor.insertText(QString(QChar::ObjectReplacementCharacter), charFormat);
this->setTextCursor(cursor);
}
void TextEditWithWidgets::resizeWidget(int w, int h)
{
QTextCursor cursor = this->textCursor();
QTextCharFormat format = cursor.charFormat();
if(format.objectType() >= QTextFormat::UserObject + 1)
{
int index = indexObject(format.objectType());
if(index != -1)//---Объект найден
{
_objectsVector[index]->resize(w, h);
}
}
}
void TextEditWithWidgets::align(QTextCharFormat::VerticalAlignment alignment)
{
QTextCursor cursor = this->textCursor();
cursor.movePosition(QTextCursor::End);
cursor.insertBlock();
cursor.insertText("c");
QTextBlock bl = this->document()->begin();
while(bl.isValid())
{
QTextCursor cur(bl);
cur.setPosition(bl.text().length(), QTextCursor::KeepAnchor);
QTextCharFormat format;
format.setVerticalAlignment(alignment);
cur.mergeCharFormat(format);
bl = bl.next();
}
cursor.select(QTextCursor::BlockUnderCursor);
cursor.removeSelectedText();
}
int TextEditWithWidgets::indexObject(int objectType)
{
int value = -1;
for(int i = 0; i < _objectsVector.count(); i++)
{
if(_objectsVector[i]->objectType() == objectType)
{
value = i;
}
}
return value;
}
void TextEditWithWidgets::textChanged()
{
QVector <bool> tempVector;
tempVector.resize(_objectsVector.size());
QTextBlock block = this->document()->begin();
while(block.isValid())
{
QTextBlock::iterator iterator;
for(iterator = block.begin(); !(iterator.atEnd()); ++iterator)
{
QTextFragment currentFragment = iterator.fragment();
if(currentFragment.isValid())
{
QTextCharFormat format = currentFragment.charFormat();
if(format.objectType() >= QTextFormat::UserObject + 1)
{
int index = indexObject(format.objectType());
if(index != -1)
{
tempVector[index] = true;
}
}
}
}
block = block.next();
}
for(int i = 0; i < tempVector.count(); i++)
{
if(!tempVector[i])
{
delete _objectsVector[i];
_objectsVector[i] = nullptr;
_objectsVector.remove(i);
tempVector.remove(i);
}
}
}
Данный пример демонстрирует основную идею и конечно же нуждается в некоторой доработке (например, мне не очень нравится реализация слота textChanged()), возможно, кто-то предложит свои идеи или внесет поправки. Проект с комментариями прикладываю (тестировался в Qt 5.12.5).