Skip to content

Commit b3f3761

Browse files
authored
Fix Chinese text selection (Mudlet#2790)
* Const small booleans that can be * Preparatory refactoring * Normal/narrow characters are far more popular right now, check them first * Added correct calculation of X for the variable-width characters * Clean up debug echoes * Variable renaming improvements * Make selection select the next character when it is selected over half * Remove debug statement * Remove duplication & fix other hardcoded width=1 cases * Don't select from the middle of a character - doesn't work in right to left selection
1 parent b70d6d9 commit b3f3761

2 files changed

Lines changed: 107 additions & 95 deletions

File tree

src/TTextEdit.cpp

Lines changed: 105 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ void TTextEdit::drawLine(QPainter& painter, int lineNumber, int lineOfScreen) co
469469
if (mShowTimeStamps) {
470470
TChar timeStampStyle(QColor(200, 150, 0), QColor(22, 22, 22));
471471
QString timestamp(mpBuffer->timeBuffer.at(lineNumber));
472-
for (QChar c : timestamp) {
472+
for (const QChar c : timestamp) {
473473
cursor.setX(cursor.x() + drawGrapheme(painter, cursor, c, 0, timeStampStyle));
474474
}
475475
}
@@ -502,58 +502,15 @@ int TTextEdit::drawGrapheme(QPainter& painter, const QPoint& cursor, const QStri
502502
if (unicode == '\t') {
503503
charWidth = column / 8 * 8 + 8;
504504
} else {
505-
// mk_wcwidth returns -1 (on error), 0 on a control or combining
506-
// diacritical codepoint or 1 for a normal character or 2 on a wide
507-
// character.
508-
// mk_wcwidth_cjk does the same except it returns 2 instead of 1 for
509-
// characters that have an "ambiguous" East Asian width:
510-
// replaced by wide_wcwidth which returns 1 or 2 or a number of
511-
// (negative) special values:
512-
// mWideAmbigousWidthGlyphs
513-
switch (widechar_wcwidth(unicode)) {
514-
case 2: // Draw as wide
515-
charWidth = 2;
516-
break;
517-
case 1: // Draw as normal/narrow
518-
charWidth = 1;
519-
break;
520-
case widechar_nonprint: // -1 = The character is not printable.
521-
qDebug().nospace().noquote() << "TTextEdit::drawGrapheme(...) WARN - trying to draw a Unicode character which is unprintable, codepoint number: U+" << QString::number(unicode, 16) << ".";
522-
charWidth = 1;
523-
break;
524-
case widechar_combining: // -2 = The character is a zero-width combiner.
525-
qWarning().nospace().noquote() << "TTextEdit::drawGrapheme(...) WARN - trying to draw a Unicode character which is a zero width combiner, codepoint number: U+" << QString::number(unicode, 16) << ".";
526-
charWidth = 1; // Previous code treated this as a normal width character
527-
break;
528-
case widechar_ambiguous: // -3 = The character is East-Asian ambiguous width.
529-
charWidth = mWideAmbigousWidthGlyphs ? 2 : 1;
530-
break;
531-
case widechar_private_use: // -4 = The character is for private use - we cannot know for certain what width to used
532-
charWidth = 1;
533-
qDebug().nospace().noquote() << "TTextEdit::drawGrapheme(...) WARN - trying to draw a Private User Character, we cannot know how wide it is, codepoint number: U+" << QString::number(unicode, 16) << ".";
534-
break;
535-
case widechar_unassigned: // -5 = The character is unassigned.
536-
qWarning().nospace().noquote() << "TTextEdit::drawGrapheme(...) WARN - trying to draw a Unicode character which was not previously assigned and we do not know how wide it is, codepoint number: U+" << QString::number(unicode, 16) << ".";
537-
charWidth = 1; // Previous code treated this as a normal width character
538-
break;
539-
case widechar_widened_in_9: // -6 = Width is 1 in Unicode 8, 2 in Unicode 9+.
540-
if (mUseOldUnicode8) {
541-
charWidth = 1;
542-
} else {
543-
charWidth = 2;
544-
}
545-
break;
546-
default:
547-
Q_UNREACHABLE(); // Got an uncoded return value from widechar_wcwidth(...)
548-
}
505+
charWidth = getGraphemeWidth(unicode);
549506
}
550507

551508
TChar::AttributeFlags attributes = charStyle.allDisplayAttributes();
552-
bool isBold = attributes & TChar::Bold;
553-
bool isItalics = attributes & TChar::Italic;
554-
bool isOverline = attributes & TChar::Overline;
555-
bool isStrikeOut = attributes & TChar::StrikeOut;
556-
bool isUnderline = attributes & TChar::Underline;
509+
const bool isBold = attributes & TChar::Bold;
510+
const bool isItalics = attributes & TChar::Italic;
511+
const bool isOverline = attributes & TChar::Overline;
512+
const bool isStrikeOut = attributes & TChar::StrikeOut;
513+
const bool isUnderline = attributes & TChar::Underline;
557514
if ((painter.font().bold() != isBold)
558515
|| (painter.font().italic() != isItalics)
559516
|| (painter.font().overline() != isOverline)
@@ -592,6 +549,45 @@ int TTextEdit::drawGrapheme(QPainter& painter, const QPoint& cursor, const QStri
592549
painter.drawText(textRect.x(), textRect.bottom() - mFontDescent, grapheme);
593550
return charWidth;
594551
}
552+
int TTextEdit::getGraphemeWidth(uint unicode) const
553+
{
554+
// mk_wcwidth returns -1 (on error), 0 on a control or combining
555+
// diacritical codepoint or 1 for a normal character or 2 on a wide
556+
// character.
557+
// mk_wcwidth_cjk does the same except it returns 2 instead of 1 for
558+
// characters that have an "ambiguous" East Asian width:
559+
// replaced by wide_wcwidth which returns 1 or 2 or a number of
560+
// (negative) special values:
561+
// mWideAmbigousWidthGlyphs
562+
switch (widechar_wcwidth(unicode)) {
563+
case 1: // Draw as normal/narrow
564+
return 1;
565+
case 2: // Draw as wide
566+
return 2;
567+
case widechar_nonprint: // -1 = The character is not printable.
568+
qDebug().nospace().noquote() << "TTextEdit::drawGrapheme(...) WARN - trying to draw a Unicode character which is unprintable, codepoint number: U+" << QString::number(unicode, 16) << ".";
569+
return 1;
570+
case widechar_combining: // -2 = The character is a zero-width combiner.
571+
qWarning().nospace().noquote() << "TTextEdit::drawGrapheme(...) WARN - trying to draw a Unicode character which is a zero width combiner, codepoint number: U+" << QString::number(unicode, 16) << ".";
572+
return 1; // Previous code treated this as a normal width character
573+
case widechar_ambiguous: // -3 = The character is East-Asian ambiguous width.
574+
return mWideAmbigousWidthGlyphs ? 2 : 1;
575+
case widechar_private_use: // -4 = The character is for private use - we cannot know for certain what width to used
576+
qDebug().nospace().noquote() << "TTextEdit::drawGrapheme(...) WARN - trying to draw a Private User Character, we cannot know how wide it is, codepoint number: U+" << QString::number(unicode, 16) << ".";
577+
return 1;
578+
case widechar_unassigned: // -5 = The character is unassigned.
579+
qWarning().nospace().noquote() << "TTextEdit::drawGrapheme(...) WARN - trying to draw a Unicode character which was not previously assigned and we do not know how wide it is, codepoint number: U+" << QString::number(unicode, 16) << ".";
580+
return 1;
581+
case widechar_widened_in_9: // -6 = Width is 1 in Unicode 8, 2 in Unicode 9+.
582+
if (mUseOldUnicode8) {
583+
return 1;
584+
} else {
585+
return 2;
586+
}
587+
default:
588+
return 1; // Got an uncoded return value from widechar_wcwidth(...)
589+
}
590+
}
595591

596592
void TTextEdit::drawForeground(QPainter& painter, const QRect& r)
597593
{
@@ -760,25 +756,26 @@ void TTextEdit::highlight()
760756

761757
mSelectedRegion = mSelectedRegion.subtracted(newRegion);
762758

763-
int y1 = mPA.y();
764-
for (int y = y1, total = mPB.y(); y <= total; ++y) {
765-
int x = 0;
766-
if (y == y1) {
767-
x = mPA.x();
759+
int startY = mPA.y();
760+
auto totalY = static_cast<int>(mpBuffer->buffer.size());
761+
for (int currentY = startY, total = mPB.y(); currentY <= total; ++currentY) {
762+
int currentX = 0;
763+
if (currentY == startY) {
764+
currentX = mPA.x();
768765
}
769766

770-
if (y >= static_cast<int>(mpBuffer->buffer.size())) {
767+
if (currentY >= totalY) {
771768
break;
772769
}
773770

774-
for (;; ++x) {
775-
if ((y == mPB.y()) && (x > mPB.x())) {
771+
for (;; ++currentX) {
772+
if ((currentY == mPB.y()) && (currentX > mPB.x())) {
776773
break;
777774
}
778-
if (x < static_cast<int>(mpBuffer->buffer.at(y).size())) {
779-
if (!(mpBuffer->buffer.at(y).at(x).isSelected())) {
780-
mpBuffer->buffer.at(y).at(x).select();
781-
mpBuffer->dirty[y] = true;
775+
if (currentX < static_cast<int>(mpBuffer->buffer.at(currentY).size())) {
776+
if (!(mpBuffer->buffer.at(currentY).at(currentX).isSelected())) {
777+
mpBuffer->buffer.at(currentY).at(currentX).select();
778+
mpBuffer->dirty[currentY] = true;
782779
}
783780
} else {
784781
break;
@@ -836,17 +833,12 @@ void TTextEdit::mouseMoveEvent(QMouseEvent* event)
836833
if ((mFontWidth == 0) | (mFontHeight == 0)) {
837834
return;
838835
}
839-
int x = event->x() / mFontWidth; // bugfix by BenH (used to be mFontWidth-1)
840-
if (mShowTimeStamps) {
841-
x -= 13;
842-
}
836+
843837
int y = (event->y() / mFontHeight) + imageTopLine();
844-
if (x < 0) {
845-
x = 0;
846-
}
847-
if (y < 0) {
848-
y = 0;
849-
}
838+
y = std::max(y, 0);
839+
840+
int x = convertMouseXToBufferX(event->x(), y);
841+
850842
if (y < static_cast<int>(mpBuffer->buffer.size())) {
851843
if (x < static_cast<int>(mpBuffer->buffer[y].size())) {
852844
if (mpBuffer->buffer.at(y).at(x).linkIndex()) {
@@ -992,6 +984,38 @@ void TTextEdit::mouseMoveEvent(QMouseEvent* event)
992984
highlight();
993985
}
994986

987+
int TTextEdit::convertMouseXToBufferX(const int mouseX, const int lineNumber) const
988+
{
989+
int characterIndex = -1;
990+
if (lineNumber < mpBuffer->lineBuffer.size()) {
991+
int characterWidth = 1;
992+
int currentX = 0;
993+
for (const auto character : mpBuffer->lineBuffer.at(lineNumber)) {
994+
const uint unicode = getGraphemeBaseCharacter(character);
995+
if (unicode == '\t') {
996+
characterWidth = 8;
997+
} else {
998+
characterWidth = getGraphemeWidth(unicode);
999+
}
1000+
currentX += characterWidth * mFontWidth;
1001+
characterIndex++;
1002+
if (currentX >= mouseX) {
1003+
if (mShowTimeStamps) {
1004+
characterIndex -= 13;
1005+
}
1006+
characterIndex = std::max(characterIndex, 0);
1007+
return characterIndex;
1008+
}
1009+
}
1010+
}
1011+
1012+
characterIndex = mouseX / mFontWidth;
1013+
if (mShowTimeStamps) {
1014+
characterIndex -= 13;
1015+
}
1016+
characterIndex = std::max(characterIndex, 0);
1017+
return characterIndex;
1018+
}
9951019

9961020
void TTextEdit::contextMenuEvent(QContextMenuEvent* event)
9971021
{
@@ -1043,20 +1067,12 @@ void TTextEdit::mousePressEvent(QMouseEvent* event)
10431067
if (event->modifiers() & Qt::ControlModifier) {
10441068
mCtrlSelecting = true;
10451069
}
1046-
int x = event->x() / mFontWidth;
1047-
if (mShowTimeStamps) {
1048-
if (x < 13) {
1049-
mCtrlSelecting = true;
1050-
}
1051-
x -= 13;
1052-
}
1070+
10531071
int y = (event->y() / mFontHeight) + imageTopLine();
1054-
if (x < 0) {
1055-
x = 0;
1056-
}
1057-
if (y < 0) {
1058-
y = 0;
1059-
}
1072+
y = std::max(y, 0);
1073+
1074+
int x = convertMouseXToBufferX(event->x(), y);
1075+
10601076
if (y < static_cast<int>(mpBuffer->buffer.size())) {
10611077
if (x < static_cast<int>(mpBuffer->buffer[y].size())) {
10621078
if (mpBuffer->buffer.at(y).at(x).linkIndex()) {
@@ -1138,17 +1154,11 @@ void TTextEdit::mousePressEvent(QMouseEvent* event)
11381154
}
11391155

11401156
if (event->button() == Qt::RightButton) {
1141-
int x = event->x() / mFontWidth;
1142-
if (mShowTimeStamps) {
1143-
x -= 13;
1144-
}
11451157
int y = (event->y() / mFontHeight) + imageTopLine();
1146-
if (x < 0) {
1147-
x = 0;
1148-
}
1149-
if (y < 0) {
1150-
y = 0;
1151-
}
1158+
y = std::max(y, 0);
1159+
1160+
int x = convertMouseXToBufferX(event->x(), y);
1161+
11521162
if (y < static_cast<int>(mpBuffer->buffer.size())) {
11531163
if (x < static_cast<int>(mpBuffer->buffer[y].size())) {
11541164
if (mpBuffer->buffer.at(y).at(x).linkIndex()) {

src/TTextEdit.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ private slots:
130130
static QString convertWhitespaceToVisual(const QChar& first, const QChar& second = QChar::Null);
131131
static QString byteToLuaCodeOrChar(const char*);
132132
std::pair<bool, int> drawTextForClipboard(QPainter& p, QRect r, int lineOffset) const;
133+
int convertMouseXToBufferX(const int mouseX, const int lineNumber) const;
134+
int getGraphemeWidth(uint unicode) const;
133135

134136
int mFontHeight;
135137
int mFontWidth;

0 commit comments

Comments
 (0)