Fix char format of preedit text in empty text block
When a text block is empty, and we are adding preedit text to it, we need to merge the format of the preedit text with the current format of the cursor, otherwise we will use a default format and then suddenly switch to the proper one when the text is committed. The reason this becomes a bit complex is that there are no rules preventing someone from using several ime attributes to specify formats for isolated parts of the text, and no rules defining the order of such attributes. So even if the common case is one text format attribute for the entire string, we need to make sure we also handle the other cases gracefully, e.g. when we are setting different formats for different substrings and then providing these out of order. To make sure we have these corner cases covered, we also add a set of autotests. [ChangeLog][Qt Widgets][TextEdit] Fixed initial char format of input method text as it is in pre-edit mode. Task-number: QTBUG-59196 Change-Id: I1e37928e3bd1395fec1b5591908d4c69b84eb618 Reviewed-by: Lars Knoll <lars.knoll@qt.io>
This commit is contained in:
parent
ac74abdf50
commit
391e3aeef4
@ -2052,14 +2052,57 @@ void QWidgetTextControlPrivate::inputMethodEvent(QInputMethodEvent *e)
|
||||
preeditCursor = a.start;
|
||||
hideCursor = !a.length;
|
||||
} else if (a.type == QInputMethodEvent::TextFormat) {
|
||||
QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat();
|
||||
QTextCharFormat f = cursor.charFormat();
|
||||
f.merge(qvariant_cast<QTextFormat>(a.value).toCharFormat());
|
||||
if (f.isValid()) {
|
||||
QTextLayout::FormatRange o;
|
||||
o.start = a.start + cursor.position() - block.position();
|
||||
o.length = a.length;
|
||||
o.format = f;
|
||||
overrides.append(o);
|
||||
|
||||
// Make sure list is sorted by start index
|
||||
QVector<QTextLayout::FormatRange>::iterator it = overrides.end();
|
||||
while (it != overrides.begin()) {
|
||||
QVector<QTextLayout::FormatRange>::iterator previous = it - 1;
|
||||
if (o.start >= previous->start) {
|
||||
overrides.insert(it, o);
|
||||
break;
|
||||
}
|
||||
it = previous;
|
||||
}
|
||||
|
||||
if (it == overrides.begin())
|
||||
overrides.prepend(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor.charFormat().isValid()) {
|
||||
int start = cursor.position() - block.position();
|
||||
int end = start + e->preeditString().length();
|
||||
|
||||
QVector<QTextLayout::FormatRange>::iterator it = overrides.begin();
|
||||
while (it != overrides.end()) {
|
||||
QTextLayout::FormatRange range = *it;
|
||||
int rangeStart = range.start;
|
||||
if (rangeStart > start) {
|
||||
QTextLayout::FormatRange o;
|
||||
o.start = start;
|
||||
o.length = rangeStart - start;
|
||||
o.format = cursor.charFormat();
|
||||
it = overrides.insert(it, o) + 1;
|
||||
}
|
||||
|
||||
++it;
|
||||
start = range.start + range.length;
|
||||
}
|
||||
|
||||
if (start < end) {
|
||||
QTextLayout::FormatRange o;
|
||||
o.start = start;
|
||||
o.length = end - start;
|
||||
o.format = cursor.charFormat();
|
||||
overrides.append(o);
|
||||
}
|
||||
}
|
||||
layout->setFormats(overrides);
|
||||
|
@ -46,6 +46,7 @@
|
||||
#include <qcommonstyle.h>
|
||||
#include <qlayout.h>
|
||||
#include <qdir.h>
|
||||
#include <qpaintengine.h>
|
||||
|
||||
#include <qabstracttextdocumentlayout.h>
|
||||
#include <qtextdocumentfragment.h>
|
||||
@ -60,6 +61,8 @@ typedef QPair<Qt::Key, Qt::KeyboardModifier> keyPairType;
|
||||
typedef QList<keyPairType> pairListType;
|
||||
Q_DECLARE_METATYPE(keyPairType);
|
||||
|
||||
Q_DECLARE_METATYPE(QList<QInputMethodEvent::Attribute>);
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QTextEdit)
|
||||
|
||||
class tst_QTextEdit : public QObject
|
||||
@ -200,6 +203,9 @@ private slots:
|
||||
void wheelEvent();
|
||||
#endif
|
||||
|
||||
void preeditCharFormat_data();
|
||||
void preeditCharFormat();
|
||||
|
||||
private:
|
||||
void createSelection();
|
||||
int blockCount() const;
|
||||
@ -2594,5 +2600,235 @@ void tst_QTextEdit::wheelEvent()
|
||||
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
class MyPaintEngine : public QPaintEngine
|
||||
{
|
||||
public:
|
||||
bool begin(QPaintDevice *)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool end()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void updateState(const QPaintEngineState &)
|
||||
{
|
||||
}
|
||||
|
||||
void drawPixmap(const QRectF &, const QPixmap &, const QRectF &)
|
||||
{
|
||||
}
|
||||
|
||||
void drawTextItem(const QPointF &, const QTextItem &textItem) Q_DECL_OVERRIDE
|
||||
{
|
||||
itemFonts.append(qMakePair(textItem.text(), textItem.font()));
|
||||
}
|
||||
|
||||
Type type() const { return User; }
|
||||
|
||||
|
||||
QList<QPair<QString, QFont> > itemFonts;
|
||||
};
|
||||
|
||||
class MyPaintDevice : public QPaintDevice
|
||||
{
|
||||
public:
|
||||
MyPaintDevice() : m_paintEngine(new MyPaintEngine)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
QPaintEngine *paintEngine () const
|
||||
{
|
||||
return m_paintEngine;
|
||||
}
|
||||
|
||||
int metric (QPaintDevice::PaintDeviceMetric metric) const {
|
||||
switch (metric) {
|
||||
case QPaintDevice::PdmWidth:
|
||||
case QPaintDevice::PdmHeight:
|
||||
case QPaintDevice::PdmWidthMM:
|
||||
case QPaintDevice::PdmHeightMM:
|
||||
case QPaintDevice::PdmNumColors:
|
||||
return INT_MAX;
|
||||
case QPaintDevice::PdmDepth:
|
||||
return 32;
|
||||
case QPaintDevice::PdmDpiX:
|
||||
case QPaintDevice::PdmDpiY:
|
||||
case QPaintDevice::PdmPhysicalDpiX:
|
||||
case QPaintDevice::PdmPhysicalDpiY:
|
||||
return 72;
|
||||
case QPaintDevice::PdmDevicePixelRatio:
|
||||
case QPaintDevice::PdmDevicePixelRatioScaled:
|
||||
; // fall through
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
MyPaintEngine *m_paintEngine;
|
||||
};
|
||||
}
|
||||
|
||||
void tst_QTextEdit::preeditCharFormat_data()
|
||||
{
|
||||
QTest::addColumn<QList<QInputMethodEvent::Attribute> >("imeAttributes");
|
||||
QTest::addColumn<QStringList>("substrings");
|
||||
QTest::addColumn<QList<bool> >("boldnessList");
|
||||
QTest::addColumn<QList<bool> >("italicnessList");
|
||||
QTest::addColumn<QList<int> >("pointSizeList");
|
||||
|
||||
{
|
||||
QList<QInputMethodEvent::Attribute> attributes;
|
||||
{
|
||||
QTextCharFormat tcf;
|
||||
tcf.setFontPointSize(13);
|
||||
tcf.setFontItalic(true);
|
||||
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 1, 1, tcf));
|
||||
}
|
||||
|
||||
{
|
||||
QTextCharFormat tcf;
|
||||
tcf.setFontPointSize(8);
|
||||
tcf.setFontWeight(QFont::Normal);
|
||||
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 4, 2, tcf));
|
||||
}
|
||||
|
||||
QTest::newRow("Two formats, middle, in order")
|
||||
<< attributes
|
||||
<< (QStringList() << "P" << "r" << "eE" << "di" << "tText")
|
||||
<< (QList<bool>() << true << true << true << false << true)
|
||||
<< (QList<bool>() << false << true << false << false << false)
|
||||
<< (QList<int>() << 20 << 13 << 20 << 8 << 20);
|
||||
}
|
||||
|
||||
{
|
||||
QList<QInputMethodEvent::Attribute> attributes;
|
||||
{
|
||||
QTextCharFormat tcf;
|
||||
tcf.setFontPointSize(8);
|
||||
tcf.setFontWeight(QFont::Normal);
|
||||
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 4, 2, tcf));
|
||||
}
|
||||
|
||||
{
|
||||
QTextCharFormat tcf;
|
||||
tcf.setFontPointSize(13);
|
||||
tcf.setFontItalic(true);
|
||||
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 1, 1, tcf));
|
||||
}
|
||||
|
||||
QTest::newRow("Two formats, middle, out of order")
|
||||
<< attributes
|
||||
<< (QStringList() << "P" << "r" << "eE" << "di" << "tText")
|
||||
<< (QList<bool>() << true << true << true << false << true)
|
||||
<< (QList<bool>() << false << true << false << false << false)
|
||||
<< (QList<int>() << 20 << 13 << 20 << 8 << 20);
|
||||
}
|
||||
|
||||
{
|
||||
QList<QInputMethodEvent::Attribute> attributes;
|
||||
{
|
||||
QTextCharFormat tcf;
|
||||
tcf.setFontPointSize(13);
|
||||
tcf.setFontItalic(true);
|
||||
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, 1, tcf));
|
||||
}
|
||||
|
||||
{
|
||||
QTextCharFormat tcf;
|
||||
tcf.setFontPointSize(8);
|
||||
tcf.setFontWeight(QFont::Normal);
|
||||
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 4, 2, tcf));
|
||||
}
|
||||
|
||||
QTest::newRow("Two formats, front, in order")
|
||||
<< attributes
|
||||
<< (QStringList() << "P" << "reE" << "di" << "tText")
|
||||
<< (QList<bool>() << true << true << false << true)
|
||||
<< (QList<bool>() << true << false << false << false)
|
||||
<< (QList<int>() << 13 << 20 << 8 << 20);
|
||||
}
|
||||
|
||||
{
|
||||
QList<QInputMethodEvent::Attribute> attributes;
|
||||
{
|
||||
QTextCharFormat tcf;
|
||||
tcf.setFontPointSize(8);
|
||||
tcf.setFontWeight(QFont::Normal);
|
||||
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 4, 2, tcf));
|
||||
}
|
||||
|
||||
{
|
||||
QTextCharFormat tcf;
|
||||
tcf.setFontPointSize(13);
|
||||
tcf.setFontItalic(true);
|
||||
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, 1, tcf));
|
||||
}
|
||||
|
||||
QTest::newRow("Two formats, front, out of order")
|
||||
<< attributes
|
||||
<< (QStringList() << "P" << "reE" << "di" << "tText")
|
||||
<< (QList<bool>() << true << true << false << true)
|
||||
<< (QList<bool>() << true << false << false << false)
|
||||
<< (QList<int>() << 13 << 20 << 8 << 20);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QTextEdit::preeditCharFormat()
|
||||
{
|
||||
QFETCH(QList<QInputMethodEvent::Attribute>, imeAttributes);
|
||||
QFETCH(QStringList, substrings);
|
||||
QFETCH(QList<bool>, boldnessList);
|
||||
QFETCH(QList<bool>, italicnessList);
|
||||
QFETCH(QList<int>, pointSizeList);
|
||||
|
||||
QTextEdit *w = new QTextEdit;
|
||||
w->show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(w));
|
||||
|
||||
// Set main char format
|
||||
{
|
||||
QTextCharFormat tcf;
|
||||
tcf.setFontPointSize(20);
|
||||
tcf.setFontWeight(QFont::Bold);
|
||||
w->mergeCurrentCharFormat(tcf);
|
||||
}
|
||||
|
||||
QList<QInputMethodEvent::Attribute> attributes;
|
||||
attributes.prepend(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor,
|
||||
w->textCursor().position(),
|
||||
0,
|
||||
QVariant()));
|
||||
|
||||
attributes += imeAttributes;
|
||||
|
||||
QInputMethodEvent event("PreEditText", attributes);
|
||||
QApplication::sendEvent(w, &event);
|
||||
|
||||
MyPaintDevice device;
|
||||
{
|
||||
QPainter p(&device);
|
||||
w->document()->drawContents(&p);
|
||||
}
|
||||
|
||||
QCOMPARE(device.m_paintEngine->itemFonts.size(), substrings.size());
|
||||
for (int i = 0; i < substrings.size(); ++i)
|
||||
QCOMPARE(device.m_paintEngine->itemFonts.at(i).first, substrings.at(i));
|
||||
|
||||
for (int i = 0; i < substrings.size(); ++i)
|
||||
QCOMPARE(device.m_paintEngine->itemFonts.at(i).second.bold(), boldnessList.at(i));
|
||||
|
||||
for (int i = 0; i < substrings.size(); ++i)
|
||||
QCOMPARE(device.m_paintEngine->itemFonts.at(i).second.italic(), italicnessList.at(i));
|
||||
|
||||
for (int i = 0; i < substrings.size(); ++i)
|
||||
QCOMPARE(device.m_paintEngine->itemFonts.at(i).second.pointSize(), pointSizeList.at(i));
|
||||
|
||||
delete w;
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QTextEdit)
|
||||
#include "tst_qtextedit.moc"
|
||||
|
Loading…
Reference in New Issue
Block a user