diff options
| author | 2023-11-23 17:32:47 +0100 | |
|---|---|---|
| committer | 2023-11-28 10:36:52 +0100 | |
| commit | bee611a963de836807b10133f278090e32035c56 (patch) | |
| tree | 9794b56ff13d9bfcdc2996362e193bbe4ed3aff8 /src | |
| parent | Ircview: consider surrogate pairs when calculating block widths, wraps, and s... (diff) | |
| download | KVIrc-bee611a963de836807b10133f278090e32035c56.tar.gz KVIrc-bee611a963de836807b10133f278090e32035c56.tar.bz2 KVIrc-bee611a963de836807b10133f278090e32035c56.zip | |
InputEditor: consider surrogate pairs when moving cursor, deleting a char or editing selection
Diffstat (limited to 'src')
| -rw-r--r-- | src/kvirc/ui/KviInputEditor.cpp | 148 |
1 files changed, 66 insertions, 82 deletions
diff --git a/src/kvirc/ui/KviInputEditor.cpp b/src/kvirc/ui/KviInputEditor.cpp index 89d4445a4..18d764576 100644 --- a/src/kvirc/ui/KviInputEditor.cpp +++ b/src/kvirc/ui/KviInputEditor.cpp @@ -1748,6 +1748,9 @@ void KviInputEditor::focusOutEvent(QFocusEvent * e) e->accept(); } +#define INPUT_ISHIGHSURROGATE(c) ((c).unicode() >= 0xD800 && (c).unicode() <= 0xDBFF) +#define INPUT_ISLOWSURROGATE(c) ((c).unicode() >= 0xDC00 && (c).unicode() <= 0xDFFF) + void KviInputEditor::internalCursorRight(bool bShift) { if(m_iCursorPosition >= ((int)(m_szTextBuffer.length()))) @@ -1757,34 +1760,42 @@ void KviInputEditor::internalCursorRight(bool bShift) return; } + int iNewCursorPosition = m_iCursorPosition; + if(INPUT_ISHIGHSURROGATE(m_szTextBuffer.at(iNewCursorPosition))) + { + // avoid to position the cursor in the middle of a surrogate pair + iNewCursorPosition += 2; + } else { + iNewCursorPosition++; + } + //Grow the selection if needed if(bShift) { if((m_iSelectionBegin != -1) && (m_iSelectionEnd != -1)) { if(m_iSelectionEnd <= m_iCursorPosition) - m_iSelectionEnd = m_iCursorPosition + 1; + m_iSelectionEnd = iNewCursorPosition; else if(m_iSelectionBegin >= m_iCursorPosition) - m_iSelectionBegin = m_iCursorPosition + 1; + m_iSelectionBegin = iNewCursorPosition; else { m_iSelectionBegin = m_iCursorPosition; - m_iSelectionEnd = m_iCursorPosition + 1; + m_iSelectionEnd = iNewCursorPosition; } } else { m_iSelectionBegin = m_iCursorPosition; - m_iSelectionEnd = m_iCursorPosition + 1; + m_iSelectionEnd = iNewCursorPosition; } - m_iCursorPosition++; } else { - m_iCursorPosition++; clearSelection(); } + m_iCursorPosition = iNewCursorPosition; m_p->bTextBlocksDirty = true; ensureCursorVisible(); } @@ -1798,33 +1809,39 @@ void KviInputEditor::internalCursorLeft(bool bShift) return; } + int iNewCursorPosition = m_iCursorPosition - 1; + if(INPUT_ISLOWSURROGATE(m_szTextBuffer.at(iNewCursorPosition))) + { + // avoid to position the cursor in the middle of a surrogate pair + iNewCursorPosition--; + } + if(bShift) { if((m_iSelectionBegin != -1) && (m_iSelectionEnd != -1)) { if(m_iSelectionBegin >= m_iCursorPosition) - m_iSelectionBegin = m_iCursorPosition - 1; + m_iSelectionBegin = iNewCursorPosition; else if(m_iSelectionEnd <= m_iCursorPosition) - m_iSelectionEnd = m_iCursorPosition - 1; + m_iSelectionEnd = iNewCursorPosition; else { m_iSelectionEnd = m_iCursorPosition; - m_iSelectionBegin = m_iCursorPosition - 1; + m_iSelectionBegin = iNewCursorPosition; } } else { m_iSelectionEnd = m_iCursorPosition; - m_iSelectionBegin = m_iCursorPosition - 1; + m_iSelectionBegin = iNewCursorPosition; } - m_iCursorPosition--; } else { - m_iCursorPosition--; clearSelection(); } + m_iCursorPosition = iNewCursorPosition; m_p->bTextBlocksDirty = true; ensureCursorVisible(); } @@ -2516,82 +2533,35 @@ int KviInputEditor::charIndexFromXPosition(qreal fXPos) if(!pBlock) return iCurChar; - // This is very tricky. Qt does not provide a simple means to figure out the cursor position - // from an x position on the text. We use QFontMetrics::elidedText() to guess it. - - // Additionally Qt::ElideNone does not work as expected (see QTBUG-40315): it just ignores clipping altogether. - // So we use Qt::ElideRight here but we must take into account the width of the elision - qreal fWidth = fXPos - fCurX; - + qreal fCurWidth = 0, fCharWidth = 0; QFontMetrics * fm = getLastFontMetrics(font()); - - QString szPart = fm->elidedText(pBlock->szText, Qt::ElideRight, fWidth + m_p->fFontElisionWidth); - - if(szPart.endsWith(m_p->szFontElision)) - szPart.truncate(szPart.length() - 1); // kill the elision - - // OK, now we have a good starting point - - qreal fPrevWidth = fm->horizontalAdvance(szPart); int iBlockLength = pBlock->szText.length(); + int iCurPosInBlock = 0; + const QChar * p = pBlock->szText.unicode(); - if(fPrevWidth <= fWidth) - { - // move up adding characters - for(;;) - { - int iPartLength = szPart.length(); - if(iPartLength == iBlockLength) - return iCurChar + iBlockLength; - - szPart = pBlock->szText.left(iPartLength + 1); - - qreal fNextWidth = fm->horizontalAdvance(szPart); - - if(fNextWidth >= fWidth) - { - // gotcha. - qreal fMiddle = (fPrevWidth + fNextWidth) / 2.0; - - if(fWidth < fMiddle) - return iCurChar + iPartLength; - - return iCurChar + iPartLength + 1; - } - - fPrevWidth = fNextWidth; - } - } - else + while(iCurPosInBlock < iBlockLength) { - // move down removing characters - for(;;) + if(INPUT_ISHIGHSURROGATE(*p) && iCurPosInBlock < iBlockLength - 1) { - int iPartLength = szPart.length(); - if(iPartLength == 0) - return iCurChar; - - szPart = pBlock->szText.left(iPartLength - 1); - - qreal fNextWidth = fm->horizontalAdvance(szPart); - - if(fNextWidth <= fWidth) - { - // gotcha. - qreal fMiddle = (fPrevWidth + fNextWidth) / 2.0; - - if(fWidth < fMiddle) - return iCurChar + iPartLength - 1; - - return iCurChar + iPartLength; - } - - fPrevWidth = fNextWidth; + // extract and calculate width of both chars together + fCharWidth = fm->horizontalAdvance(QString(p, 2)); + if(fCurWidth + fCharWidth >= fWidth) + break; + fCurWidth += fCharWidth; + iCurPosInBlock += 2; + p += 2; + } else { + fCharWidth = fm->horizontalAdvance(*p); + if(fCurWidth + fCharWidth >= fWidth) + break; + fCurWidth += fCharWidth; + iCurPosInBlock++; + p++; } } - Q_ASSERT(false); // not reached + return iCurChar + iCurPosInBlock; } qreal KviInputEditor::xPositionFromCharIndex(int iChIdx) @@ -3328,8 +3298,16 @@ void KviInputEditor::backspaceHit() else if(m_iCursorPosition > 0) { m_iCursorPosition--; - addUndo(new EditCommand(EditCommand::RemoveText, m_szTextBuffer.mid(m_iCursorPosition, 1), m_iCursorPosition)); - m_szTextBuffer.remove(m_iCursorPosition, 1); + int iDeletedSize = 1; + if(INPUT_ISLOWSURROGATE(m_szTextBuffer.at(m_iCursorPosition)) && m_iCursorPosition > 0) + { + // avoid splitting in the middle of a surrogate pair + m_iCursorPosition--; + iDeletedSize++; + } + + addUndo(new EditCommand(EditCommand::RemoveText, m_szTextBuffer.mid(m_iCursorPosition, iDeletedSize), m_iCursorPosition)); + m_szTextBuffer.remove(m_iCursorPosition, iDeletedSize); m_p->bTextBlocksDirty = true; } @@ -3351,7 +3329,13 @@ void KviInputEditor::deleteHit() if(m_iCursorPosition < m_szTextBuffer.length()) { - m_szTextBuffer.remove(m_iCursorPosition, 1); + if(INPUT_ISHIGHSURROGATE(m_szTextBuffer.at(m_iCursorPosition)) && m_iCursorPosition < m_szTextBuffer.length() - 1) + { + m_szTextBuffer.remove(m_iCursorPosition, 2); + } else { + m_szTextBuffer.remove(m_iCursorPosition, 1); + } + m_p->bTextBlocksDirty = true; clearSelection(); ensureCursorVisible(); |
