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(); diff --git a/src/kvirc/ui/KviIrcView.cpp b/src/kvirc/ui/KviIrcView.cpp index 6dba743d5..51b9236b3 100644 --- a/src/kvirc/ui/KviIrcView.cpp +++ b/src/kvirc/ui/KviIrcView.cpp @@ -1684,6 +1684,8 @@ void KviIrcView::paintEvent(QPaintEvent * p) // The IrcView : calculate line wraps // +#define IRCVIEW_ISHIGHSURROGATE(c) ((c).unicode() >= 0xD800 && (c).unicode() <= 0xDBFF) +#define IRCVIEW_ISLOWSURROGATE(c) ((c).unicode() >= 0xDC00 && (c).unicode() <= 0xDFFF) #define IRCVIEW_WCHARWIDTH(c) (((c).unicode() < 0xff) ? m_iFontCharacterWidth[(c).unicode()] : m_pFm->horizontalAdvance(c)) void KviIrcView::calculateLineWraps(KviIrcViewLine * ptr, int maxWidth) @@ -1730,9 +1732,17 @@ void KviIrcView::calculateLineWraps(KviIrcViewLine * ptr, int maxWidth) while(curBlockLen < maxBlockLen) { // FIXME: this is ugly :/ - curBlockWidth += IRCVIEW_WCHARWIDTH(*p); - curBlockLen++; - p++; + if(IRCVIEW_ISHIGHSURROGATE(*p) && curBlockLen < maxBlockLen - 1) + { + // extract and calculate width of both chars together + curBlockWidth += m_pFm->horizontalAdvance(QString(p, 2)); + curBlockLen += 2; + p += 2; + } else { + curBlockWidth += IRCVIEW_WCHARWIDTH(*p); + curBlockLen++; + p++; + } } } @@ -1768,7 +1778,15 @@ void KviIrcView::calculateLineWraps(KviIrcViewLine * ptr, int maxWidth) { p--; curBlockLen--; - curLineWidth -= IRCVIEW_WCHARWIDTH(*p); + if(IRCVIEW_ISLOWSURROGATE(*p) && curBlockLen > 0) + { + // avoid splitting in the middle of a surrogate pair + p--; + curBlockLen--; + curLineWidth -= m_pFm->horizontalAdvance(QString(p, 2)); + } else { + curLineWidth -= IRCVIEW_WCHARWIDTH(*p); + } } // Now look for a space (or a tabulation) @@ -1776,7 +1794,15 @@ void KviIrcView::calculateLineWraps(KviIrcViewLine * ptr, int maxWidth) { p--; curBlockLen--; - curLineWidth -= IRCVIEW_WCHARWIDTH(*p); + if(IRCVIEW_ISLOWSURROGATE(*p) && curBlockLen > 0) + { + // avoid splitting in the middle of a surrogate pair + p--; + curBlockLen--; + curLineWidth -= m_pFm->horizontalAdvance(QString(p, 2)); + } else { + curLineWidth -= IRCVIEW_WCHARWIDTH(*p); + } } if(curBlockLen == 0) @@ -1805,16 +1831,31 @@ void KviIrcView::calculateLineWraps(KviIrcViewLine * ptr, int maxWidth) uint uLoopedChars = 0; do { - curBlockLen++; p++; - curLineWidth += IRCVIEW_WCHARWIDTH(*p); + curBlockLen++; uLoopedChars++; + if(IRCVIEW_ISHIGHSURROGATE(*p) && curBlockLen < maxBlockLen - 1) + { + // extract and calculate width of both chars together + curLineWidth += m_pFm->horizontalAdvance(QString(p, 2)); + // add the second char + p++; + curBlockLen++; + } else { + curLineWidth += IRCVIEW_WCHARWIDTH(*p); + } } while((curLineWidth < maxWidth) && (curBlockLen < maxBlockLen)); // Now overrun, go back 1 char (if we ran over at least 2 chars) if(uLoopedChars > 1) { p--; curBlockLen--; + if(IRCVIEW_ISLOWSURROGATE(*p) && curBlockLen > 0) + { + // avoid splitting in the middle of a surrogate pair + p--; + curBlockLen--; + } } } //K...wrap @@ -1937,15 +1978,35 @@ bool KviIrcView::checkSelectionBlock(KviIrcViewLine * line, int bufIndex) m_pWrappedBlockSelectionInfo->part_2_width = 0; for(int i = 0; i < m_pWrappedBlockSelectionInfo->part_1_length; i++) { - int www = IRCVIEW_WCHARWIDTH(*p); + int www; + if(IRCVIEW_ISHIGHSURROGATE(*p) && i < m_pWrappedBlockSelectionInfo->part_1_length - 1) + { + // extract and calculate width of both chars together + www = m_pFm->horizontalAdvance(QString(p, 2)); + p += 2; + i++; + } else { + www = IRCVIEW_WCHARWIDTH(*p); + p++; + } + m_pWrappedBlockSelectionInfo->part_1_width += www; - p++; } for(int i = 0; i < m_pWrappedBlockSelectionInfo->part_2_length; i++) { - int www = IRCVIEW_WCHARWIDTH(*p); + int www; + if(IRCVIEW_ISHIGHSURROGATE(*p) && i < m_pWrappedBlockSelectionInfo->part_2_length - 1) + { + // extract and calculate width of both chars together + www = m_pFm->horizontalAdvance(QString(p, 2)); + p += 2; + i++; + } else { + www = IRCVIEW_WCHARWIDTH(*p); + p++; + } + m_pWrappedBlockSelectionInfo->part_2_width += www; - p++; } m_pWrappedBlockSelectionInfo->part_3_width = line->pBlocks[bufIndex].block_width - m_pWrappedBlockSelectionInfo->part_1_width - m_pWrappedBlockSelectionInfo->part_2_width; return true; @@ -1959,9 +2020,19 @@ bool KviIrcView::checkSelectionBlock(KviIrcViewLine * line, int bufIndex) m_pWrappedBlockSelectionInfo->part_1_width = 0; for(int i = 0; i < m_pWrappedBlockSelectionInfo->part_1_length; i++) { - int www = IRCVIEW_WCHARWIDTH(*p); + int www; + if(IRCVIEW_ISHIGHSURROGATE(*p) && i < m_pWrappedBlockSelectionInfo->part_1_length - 1) + { + // extract and calculate width of both chars together + www = m_pFm->horizontalAdvance(QString(p, 2)); + p += 2; + i++; + } else { + www = IRCVIEW_WCHARWIDTH(*p); + p++; + } + m_pWrappedBlockSelectionInfo->part_1_width += www; - p++; } m_pWrappedBlockSelectionInfo->part_2_length = line->pBlocks[bufIndex].block_len - m_pWrappedBlockSelectionInfo->part_1_length; m_pWrappedBlockSelectionInfo->part_2_width = line->pBlocks[bufIndex].block_width - m_pWrappedBlockSelectionInfo->part_1_width; @@ -1976,9 +2047,19 @@ bool KviIrcView::checkSelectionBlock(KviIrcViewLine * line, int bufIndex) m_pWrappedBlockSelectionInfo->part_1_width = 0; for(int i = 0; i < m_pWrappedBlockSelectionInfo->part_1_length; i++) { - int www = IRCVIEW_WCHARWIDTH(*p); + int www; + if(IRCVIEW_ISHIGHSURROGATE(*p) && i < m_pWrappedBlockSelectionInfo->part_1_length - 1) + { + // extract and calculate width of both chars together + www = m_pFm->horizontalAdvance(QString(p, 2)); + p += 2; + i++; + } else { + www = IRCVIEW_WCHARWIDTH(*p); + p++; + } + m_pWrappedBlockSelectionInfo->part_1_width += www; - p++; } m_pWrappedBlockSelectionInfo->part_2_length = line->pBlocks[bufIndex].block_len - m_pWrappedBlockSelectionInfo->part_1_length; m_pWrappedBlockSelectionInfo->part_2_width = line->pBlocks[bufIndex].block_width - m_pWrappedBlockSelectionInfo->part_1_width; @@ -2020,9 +2101,19 @@ bool KviIrcView::checkSelectionBlock(KviIrcViewLine * line, int bufIndex) m_pWrappedBlockSelectionInfo->part_1_width = 0; for(int i = 0; i < m_pWrappedBlockSelectionInfo->part_1_length; i++) { - int www = IRCVIEW_WCHARWIDTH(*p); + int www; + if(IRCVIEW_ISHIGHSURROGATE(*p) && i < m_pWrappedBlockSelectionInfo->part_1_length - 1) + { + // extract and calculate width of both chars together + www = m_pFm->horizontalAdvance(QString(p, 2)); + p += 2; + i++; + } else { + www = IRCVIEW_WCHARWIDTH(*p); + p++; + } + m_pWrappedBlockSelectionInfo->part_1_width += www; - p++; } m_pWrappedBlockSelectionInfo->part_2_length = line->pBlocks[bufIndex].block_len - m_pWrappedBlockSelectionInfo->part_1_length; m_pWrappedBlockSelectionInfo->part_2_width = line->pBlocks[bufIndex].block_width - m_pWrappedBlockSelectionInfo->part_1_width; @@ -2065,9 +2156,19 @@ bool KviIrcView::checkSelectionBlock(KviIrcViewLine * line, int bufIndex) m_pWrappedBlockSelectionInfo->part_1_width = 0; for(int i = 0; i < m_pWrappedBlockSelectionInfo->part_1_length; i++) { - int www = IRCVIEW_WCHARWIDTH(*p); + int www; + if(IRCVIEW_ISHIGHSURROGATE(*p) && i < m_pWrappedBlockSelectionInfo->part_1_length - 1) + { + // extract and calculate width of both chars together + www = m_pFm->horizontalAdvance(QString(p, 2)); + p += 2; + i++; + } else { + www = IRCVIEW_WCHARWIDTH(*p); + p++; + } + m_pWrappedBlockSelectionInfo->part_1_width += www; - p++; } m_pWrappedBlockSelectionInfo->part_2_length = line->pBlocks[bufIndex].block_len - m_pWrappedBlockSelectionInfo->part_1_length; m_pWrappedBlockSelectionInfo->part_2_width = line->pBlocks[bufIndex].block_width - m_pWrappedBlockSelectionInfo->part_1_width; @@ -2662,7 +2763,7 @@ int KviIrcView::getVisibleCharIndexAt(KviIrcViewLine *, int xPos, int yPos) oldIndex = retValue; oldLeft = iLeft; curChar = l->szText.at(l->pBlocks[i].block_start + retValue); - if (curChar.unicode() >= 0xD800 && curChar.unicode() <= 0xDC00) // Surrogate pair + if (IRCVIEW_ISHIGHSURROGATE(curChar)) // Surrogate pair { iLeft += m_pFm->horizontalAdvance(l->szText.mid(retValue), 2); retValue+=2; diff --git a/src/kvirc/ui/KviIrcView_getTextLine.cpp b/src/kvirc/ui/KviIrcView_getTextLine.cpp index d45640522..d00d24b86 100644 --- a/src/kvirc/ui/KviIrcView_getTextLine.cpp +++ b/src/kvirc/ui/KviIrcView_getTextLine.cpp @@ -584,7 +584,6 @@ const kvi_wchar_t * KviIrcView::getTextLine( APPEND_LAST_TEXT_BLOCK(data_ptr, p - data_ptr); NEW_LINE_CHUNK(KviControlCodes::ArbitraryBreak); data_ptr = p; - p++; break; case '\r': #ifdef COMPILE_USE_DYNAMIC_LABELS