generally overhauled non-wrapping mode of BTextView, as it was considerably broken:

* switching from wrapping to non-wrapping mode triggers a recomputation of
  the text rect
* non-wrapping mode now triggers updating of the data rect for the horizontal 
  scrollbar as needed, fixing #4705
* corrected a couple of width computations that confused bounds and text
  rect width, leading to drawing artefacts at the end of a line
* unified computation of text rect width to always take all lines into
  account (not just the first one)
* some cleanup


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@33703 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Oliver Tappe
2009-10-21 13:42:25 +00:00
parent 3ee51068f7
commit b18f133db3
2 changed files with 85 additions and 83 deletions
+4 -4
View File
@@ -292,6 +292,7 @@ private:
const rgb_color* initialColor);
void _ValidateLayoutData();
void _ResetTextRect();
void _HandleBackspace();
void _HandleArrowKey(uint32 inArrowKey);
@@ -301,12 +302,12 @@ private:
int32 numBytes);
void _Refresh(int32 fromOffset, int32 toOffset,
bool erase, bool scroll);
bool scroll);
void _RecalculateLineBreaks(int32* startLine,
int32* endLine);
int32 _FindLineBreak(int32 fromOffset,
float* outAscent, float* outDescent,
float* ioWidth);
float* inOutWidth);
float _StyledWidth(int32 fromOffset, int32 length,
float* outAscent = NULL,
@@ -333,8 +334,7 @@ private:
int32 startOffset = -1,
bool erase = false);
void _RequestDrawLines(int32 startLine,
int32 endLine, int32 startOffset = -1,
bool erase = false);
int32 endLine);
void _DrawCaret(int32 offset);
void _ShowCaret();
+81 -79
View File
@@ -517,7 +517,7 @@ BTextView::Draw(BRect updateRect)
int32 startLine = _LineAt(BPoint(0.0, updateRect.top));
int32 endLine = _LineAt(BPoint(0.0, updateRect.bottom));
_DrawLines(startLine, endLine);
_DrawLines(startLine, endLine, -1, true);
}
@@ -1137,7 +1137,7 @@ BTextView::SetText(const char *inText, int32 inLength,
InsertText(inText, inLength, 0, inRuns);
// recalc line breaks and draw the text
_Refresh(0, inLength, true, false);
_Refresh(0, inLength, false);
fCaretOffset = fSelStart = fSelEnd = 0;
ScrollTo(B_ORIGIN);
@@ -1177,7 +1177,7 @@ BTextView::SetText(BFile *inFile, int32 inOffset, int32 inLength,
}
// recalc line breaks and draw the text
_Refresh(0, inLength, true, false);
_Refresh(0, inLength, false);
fCaretOffset = fSelStart = fSelEnd = 0;
ScrollToOffset(fSelStart);
@@ -1253,7 +1253,7 @@ BTextView::Delete(int32 startOffset, int32 endOffset)
fSelEnd = fSelStart = fCaretOffset;
// recalc line breaks and draw what's left
_Refresh(startOffset, endOffset, true, false);
_Refresh(startOffset, endOffset, false);
// draw the caret
if (fActive)
@@ -1627,11 +1627,10 @@ BTextView::SetFontAndColor(int32 startOffset, int32 endOffset,
// B_SUPPORTS_LAYOUT) and have it _Refresh() automatically?
InvalidateLayout();
// recalc the line breaks and redraw with new style
_Refresh(startOffset, endOffset, startOffset != endOffset, false);
_Refresh(startOffset, endOffset, false);
} else {
// the line breaks wont change, simply redraw
_RequestDrawLines(_LineAt(startOffset), _LineAt(endOffset), startOffset,
false);
_RequestDrawLines(_LineAt(startOffset), _LineAt(endOffset));
}
_ShowCaret();
@@ -1682,7 +1681,7 @@ BTextView::SetRunArray(int32 startOffset, int32 endOffset,
_SetRunArray(startOffset, endOffset, runs);
_Refresh(startOffset, endOffset, true, false);
_Refresh(startOffset, endOffset, false);
}
@@ -2186,31 +2185,10 @@ BTextView::SetTextRect(BRect rect)
if (rect == fTextRect)
return;
// The text rect height is always calculated anyways to contain the
// whole text. But it is used here to remember the inset the user
// wanted.
bool needsRefresh = fTextRect.left != rect.left
|| fTextRect.right != rect.right || fTextRect.top != rect.top;
if (fWrap)
fLayoutData->UpdateInsets(Bounds().OffsetToCopy(B_ORIGIN), rect);
fLayoutData->UpdateInsets(Bounds().OffsetToCopy(B_ORIGIN), rect);
// When we already know we don't want to recalculate anything, we
// can just ignore the bottom coordinate the user provided.
if (!needsRefresh)
rect.bottom = fTextRect.bottom;
Invalidate();
fTextRect = rect;
fMinTextRectWidth = fTextRect.Width();
// used in auto-resizing mode to keep the text rect from
// shrinking below a certain value.
if (needsRefresh) {
// In BeOS, the text rect height is always adjusted to the text data
// height. Setting a narrower text rect will therefore enlarge the height.
_Refresh(0, TextLength(), true, false);
}
_ResetTextRect();
}
@@ -2224,6 +2202,22 @@ BTextView::TextRect() const
}
void
BTextView::_ResetTextRect()
{
// reset text rect to bounds minus insets ...
fTextRect = Bounds().OffsetToCopy(B_ORIGIN);
fTextRect.left += fLayoutData->leftInset;
fTextRect.top += fLayoutData->topInset;
fTextRect.right -= fLayoutData->rightInset;
fTextRect.bottom -= fLayoutData->bottomInset;
// and rewrap (potentially adjusting the right and the bottom of the text
// rect)
_Refresh(0, TextLength(), false);
}
/*! \brief Sets the insets from the bounds for the BTextView's text rectangle.
*/
void
@@ -2297,7 +2291,7 @@ BTextView::SetTabWidth(float width)
fTabWidth = width;
if (Window() != NULL)
_Refresh(0, fText->Length(), true, false);
_Refresh(0, fText->Length(), false);
}
@@ -2403,7 +2397,9 @@ BTextView::SetWordWrap(bool wrap)
}
fWrap = wrap;
_Refresh(0, fText->Length(), true, false);
if (wrap)
_ResetTextRect();
_Refresh(0, fText->Length(), false);
if (updateOnScreen) {
// show the caret, hilite the selection
@@ -2593,7 +2589,7 @@ BTextView::MakeResizable(bool resize, BView *resizeView)
_NewOffscreen();
}
_Refresh(0, fText->Length(), true, false);
_Refresh(0, fText->Length(), false);
}
@@ -2771,13 +2767,7 @@ BTextView::DoLayout()
if (size.height < fLayoutData->min.height)
size.height = fLayoutData->min.height;
// layout text rect
BRect textRect = Bounds().OffsetToCopy(B_ORIGIN);
textRect.left += fLayoutData->leftInset;
textRect.top += fLayoutData->topInset;
textRect.right -= fLayoutData->rightInset;
textRect.bottom -= fLayoutData->bottomInset;
SetTextRect(textRect);
_ResetTextRect();
}
@@ -2806,12 +2796,7 @@ BTextView::_ValidateLayoutData()
if (fWrap)
fLayoutData->preferred.width = min.width + 5 * lineHeight;
else {
float maxWidth = 0;
for (int i = 0; i < fLines->NumLines(); i++) {
float width = LineWidth(0);
if (maxWidth < width)
maxWidth = width;
}
float maxWidth = fLines->MaxWidth();
if (maxWidth < min.width)
maxWidth = min.width;
@@ -3245,7 +3230,7 @@ BTextView::_HandleBackspace()
DeleteText(fSelStart, fSelEnd);
fCaretOffset = fSelEnd = fSelStart;
_Refresh(fSelStart, fSelEnd, true, true);
_Refresh(fSelStart, fSelEnd, true);
}
@@ -3422,7 +3407,7 @@ BTextView::_HandleDelete()
DeleteText(fSelStart, fSelEnd);
fCaretOffset = fSelEnd = fSelStart;
_Refresh(fSelStart, fSelEnd, true, true);
_Refresh(fSelStart, fSelEnd, true);
}
@@ -3641,8 +3626,7 @@ BTextView::_HandleAlphaKey(const char *bytes, int32 numBytes)
fCaretOffset = fSelEnd;
if (fEditable)
ScrollToOffset(fCaretOffset);
ScrollToOffset(fCaretOffset);
}
@@ -3650,15 +3634,14 @@ BTextView::_HandleAlphaKey(const char *bytes, int32 numBytes)
recalculating linebreaks if needed.
\param fromOffset The offset from where to refresh.
\param toOffset The offset where to refresh to.
\param erase If true, the function will also erase the textview content
in the parts where text isn't present.
\param scroll If true, function will scroll the view to the end offset.
*/
void
BTextView::_Refresh(int32 fromOffset, int32 toOffset, bool erase, bool scroll)
BTextView::_Refresh(int32 fromOffset, int32 toOffset, bool scroll)
{
// TODO: Cleanup
float saveHeight = fTextRect.Height();
float saveWidth = fTextRect.Width();
int32 fromLine = _LineAt(fromOffset);
int32 toLine = _LineAt(toOffset);
int32 saveFromLine = fromLine;
@@ -3677,7 +3660,6 @@ BTextView::_Refresh(int32 fromOffset, int32 toOffset, bool erase, bool scroll)
// if the line breaks have changed, force an erase
if (fromLine != saveFromLine || toLine != saveToLine
|| newHeight != saveHeight) {
erase = true;
fromOffset = -1;
}
@@ -3703,7 +3685,7 @@ BTextView::_Refresh(int32 fromOffset, int32 toOffset, bool erase, bool scroll)
_AutoResize(false);
_RequestDrawLines(fromLine, toLine, drawOffset, erase);
_RequestDrawLines(fromLine, toLine);
// erase the area below the text
BRect eraseRect = bounds;
@@ -3715,7 +3697,7 @@ BTextView::_Refresh(int32 fromOffset, int32 toOffset, bool erase, bool scroll)
}
// update the scroll bars if the text area has changed
if (newHeight != saveHeight)
if (newHeight != saveHeight || fMinTextRectWidth != saveWidth)
_UpdateScrollbars();
if (scroll)
@@ -3753,14 +3735,14 @@ BTextView::_RecalculateLineBreaks(int32 *startLine, int32 *endLine)
int32 fromOffset = curLine->offset;
int32 toOffset = _FindLineBreak(fromOffset, &ascent, &descent, &width);
curLine->ascent = ascent;
curLine->width = width;
// we want to advance at least by one character
int32 nextOffset = _NextInitialByte(fromOffset);
if (toOffset < nextOffset && fromOffset < textLength)
toOffset = nextOffset;
// set the ascent of this line
curLine->ascent = ascent;
lineIndex++;
STELine saveLine = *nextLine;
if (lineIndex > fLines->NumLines() || toOffset < nextLine->offset) {
@@ -3799,9 +3781,17 @@ BTextView::_RecalculateLineBreaks(int32 *startLine, int32 *endLine)
nextLine = curLine + 1;
} while (curLine->offset < textLength);
// make sure that the sentinel line (which starts at the end of the buffer)
// has always a width of 0
(*fLines)[fLines->NumLines()]->width = 0;
// update the text rect
float newHeight = TextHeight(0, fLines->NumLines() - 1);
fTextRect.bottom = fTextRect.top + newHeight;
if (!fWrap) {
fMinTextRectWidth = fLines->MaxWidth();
fTextRect.right = fTextRect.left + fMinTextRectWidth;
}
*endLine = lineIndex - 1;
*startLine = min_c(*startLine, *endLine);
@@ -3810,7 +3800,7 @@ BTextView::_RecalculateLineBreaks(int32 *startLine, int32 *endLine)
int32
BTextView::_FindLineBreak(int32 fromOffset, float *outAscent, float *outDescent,
float *ioWidth)
float *inOutWidth)
{
*outAscent = 0.0;
*outDescent = 0.0;
@@ -3834,6 +3824,7 @@ BTextView::_FindLineBreak(int32 fromOffset, float *outAscent, float *outDescent,
*outDescent = fh.descent + fh.leading;
}
}
*inOutWidth = 0;
return limit;
}
@@ -3846,12 +3837,12 @@ BTextView::_FindLineBreak(int32 fromOffset, float *outAscent, float *outDescent,
offset = limit - fromOffset;
fText->FindChar(B_ENTER, fromOffset, &offset);
offset += fromOffset;
offset = (offset < limit) ? offset + 1 : limit;
int32 toOffset = (offset < limit) ? offset : limit;
*ioWidth = _StyledWidth(fromOffset, offset - fromOffset, outAscent,
*inOutWidth = _StyledWidth(fromOffset, toOffset - fromOffset, outAscent,
outDescent);
return offset;
return offset < limit ? offset + 1 : limit;
}
bool done = false;
@@ -3921,7 +3912,7 @@ BTextView::_FindLineBreak(int32 fromOffset, float *outAscent, float *outDescent,
strWidth += tabWidth;
}
if (strWidth >= *ioWidth) {
if (strWidth >= *inOutWidth) {
// we've found where the line will wrap
bool foundNewline = done;
done = true;
@@ -3948,7 +3939,7 @@ BTextView::_FindLineBreak(int32 fromOffset, float *outAscent, float *outDescent,
}
// ... and compute the resulting width (of visible characters)
strWidth += _StyledWidth(offset, pos + 1, &ascent, &descent);
if (strWidth >= *ioWidth) {
if (strWidth >= *inOutWidth) {
// width of visible characters exceeds line, we need to wrap
// before the current "word"
break;
@@ -3995,7 +3986,7 @@ BTextView::_FindLineBreak(int32 fromOffset, float *outAscent, float *outDescent,
current = offset, offset = _NextInitialByte(offset)) {
strWidth += _StyledWidth(current, offset - current, &ascent,
&descent);
if (strWidth >= *ioWidth) {
if (strWidth >= *inOutWidth) {
offset = _PreviousInitialByte(offset);
break;
}
@@ -4059,6 +4050,13 @@ float
BTextView::_StyledWidth(int32 fromOffset, int32 length, float *outAscent,
float *outDescent) const
{
if (length == 0) {
// determine height of char at given offset, but return empty width
fStyles->Iterate(fromOffset, 1, fInline, NULL, NULL, outAscent,
outDescent);
return 0.0;
}
float result = 0.0;
float ascent = 0.0;
float descent = 0.0;
@@ -4152,7 +4150,7 @@ BTextView::_DoInsertText(const char *inText, int32 inLength, int32 inOffset,
}
// recalc line breaks and draw the text
_Refresh(inOffset, inOffset + inLength, true, false);
_Refresh(inOffset, inOffset + inLength, false);
}
@@ -4299,7 +4297,12 @@ BTextView::_DrawLines(int32 startLine, int32 endLine, int32 startOffset,
return;
// clip the text
BRect clipRect = Bounds() & fTextRect;
BRect textRect(fTextRect);
float minWidth
= Bounds().Width() - fLayoutData->leftInset - fLayoutData->rightInset;
if (textRect.Width() < minWidth)
textRect.right = textRect.left + minWidth;
BRect clipRect = Bounds() & textRect;
clipRect.InsetBy(-1, -1);
BRegion newClip;
@@ -4387,8 +4390,7 @@ BTextView::_DrawLines(int32 startLine, int32 endLine, int32 startOffset,
void
BTextView::_RequestDrawLines(int32 startLine, int32 endLine,
int32 startOffset, bool erase)
BTextView::_RequestDrawLines(int32 startLine, int32 endLine)
{
if (!Window())
return;
@@ -4401,8 +4403,8 @@ BTextView::_RequestDrawLines(int32 startLine, int32 endLine,
STELine *from = (*fLines)[startLine];
STELine *to = endLine == maxLine ? NULL : (*fLines)[endLine + 1];
BRect invalidRect(fTextRect.left, from->origin + fTextRect.top,
fTextRect.right,
BRect invalidRect(Bounds().left, from->origin + fTextRect.top,
Bounds().right,
to != NULL ? to->origin + fTextRect.top : fTextRect.bottom);
Invalidate(invalidRect);
Window()->UpdateIfNeeded();
@@ -4863,7 +4865,7 @@ BTextView::_AutoResize(bool redraw)
BRect bounds = Bounds();
float oldWidth = fTextRect.Width();
float minWidth = fContainerView != NULL ? 3.0 : fMinTextRectWidth;
float newWidth = max_c(minWidth, ceilf(LineWidth(0)));
float newWidth = max_c(minWidth, ceilf(fLines->MaxWidth()));
if (newWidth == oldWidth)
return;
@@ -5278,7 +5280,7 @@ BTextView::_HandleInputMethodChanged(BMessage *message)
}
if (confirmed) {
_Refresh(fSelStart, fSelEnd, true, true);
_Refresh(fSelStart, fSelEnd, true);
_ShowCaret();
// now we need to feed ourselves the individual characters as if the
@@ -5303,7 +5305,7 @@ BTextView::_HandleInputMethodChanged(BMessage *message)
prevPos = currPos;
}
_Refresh(fSelStart, fSelEnd, true, true);
_Refresh(fSelStart, fSelEnd, true);
} else {
// temporarily show transient state of inline input
int32 selectionStart = 0;
@@ -5319,7 +5321,7 @@ BTextView::_HandleInputMethodChanged(BMessage *message)
fSelStart += stringLen;
fCaretOffset = fSelEnd = fSelStart;
_Refresh(inlineOffset, fSelEnd, true, true);
_Refresh(inlineOffset, fSelEnd, true);
_ShowCaret();
}
@@ -5369,7 +5371,7 @@ BTextView::_CancelInputMethod()
if (inlineInput->IsActive() && Window()) {
_Refresh(inlineInput->Offset(), fText->Length() - inlineInput->Offset(),
true, false);
false);
BMessage message(B_INPUT_METHOD_EVENT);
message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED);