Some BTextView fixes, most importantly the annoying up/down ping pong when

triggering auto-scrolling in BTextControls...
* _BTextInput_::MinSize() added 1 to the line height, but when aligning the
  text rect, at least one pixel is added at the top and bottom, which makes
  for at least two extra pixels.
* BTextView::_PerformAutoScrolling() had some code which was supposed to
  prevent from out-of-bounds scrolling, but the bottom maximum coordinate
  was not correctly calculated. This and the above item led to the ping-pong
  effect.
* Additionally, I prevented scrolling vertically for one-line text views
  completely.
* On mouse-up, reset the cursor. It may have to be the I-Beam cursor again,
  for example after de-selecting.
* While mouse tracking the selection, always use the I-Beam cursor.
* Also when mouse tracking, do not use the minimum/maximum text offset when
  the mouse is above/below the text rect. Do this only when it's also outside
  on left/right sides. This is less irritating and works like on other
  platforms. It means the first/last line can still be selected, without
  having to constrain the mouse to the inside of the text view.
* When calculating the selection region, don't make the bottom one pixel too
  far up. Lines which contain glyphs that extend below the base-line have
  one more pixel below the glyphs that is inverted now.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@30046 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Stephan Aßmus
2009-04-08 22:10:27 +00:00
parent 34c7214442
commit 82d9f50841
2 changed files with 62 additions and 30 deletions
+3 -1
View File
@@ -153,7 +153,9 @@ BSize
_BTextInput_::MinSize() _BTextInput_::MinSize()
{ {
BSize min; BSize min;
min.height = ceilf(LineHeight(0) + 1.0); min.height = ceilf(LineHeight(0) + 2.0);
// we always add at least one pixel vertical inset top/bottom for
// the text rect.
min.width = min.height * 3; min.width = min.height * 3;
return BLayoutUtils::ComposeSize(ExplicitMinSize(), min); return BLayoutUtils::ComposeSize(ExplicitMinSize(), min);
} }
+59 -29
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2001-2008, Haiku Inc. All rights reserved. * Copyright 2001-2009, Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
* *
* Authors: * Authors:
@@ -1831,14 +1831,27 @@ BTextView::OffsetAt(BPoint point) const
const int32 textLength = fText->Length(); const int32 textLength = fText->Length();
// should we even bother? // should we even bother?
if (point.y >= fTextRect.bottom) if (point.y >= fTextRect.bottom && point.x >= fTextRect.right)
return textLength; return textLength;
else if (point.y < fTextRect.top) else if (point.y < fTextRect.top && point.x <= fTextRect.left)
return 0; return 0;
#define COMPILE_PROBABLY_BAD_CODE 1
#if COMPILE_PROBABLY_BAD_CODE
// NOTE: I have not been able to test what happens if all this is removed.
// For one-line text views (BTextControl), it works just fine. But I would
// need to check StyledEdit or something in various situations (new lines
// at end and other stuff)...
// special case one line text views
if (CountLines() <= 1)
point.y = fTextRect.top;
#endif
int32 lineNum = LineAt(point); int32 lineNum = LineAt(point);
STELine* line = (*fLines)[lineNum]; STELine* line = (*fLines)[lineNum];
#if COMPILE_PROBABLY_BAD_CODE
// special case: if point is within the text rect and PixelToLine() // special case: if point is within the text rect and PixelToLine()
// tells us that it's on the last line, but if point is actually // tells us that it's on the last line, but if point is actually
// lower than the bottom of the last line, return the last offset // lower than the bottom of the last line, return the last offset
@@ -1847,6 +1860,7 @@ BTextView::OffsetAt(BPoint point) const
if (point.y >= ((line + 1)->origin + fTextRect.top)) if (point.y >= ((line + 1)->origin + fTextRect.top))
return textLength; return textLength;
} }
#endif
// convert to text rect coordinates // convert to text rect coordinates
if (fAlignment != B_ALIGN_LEFT) { if (fAlignment != B_ALIGN_LEFT) {
@@ -2033,14 +2047,14 @@ BTextView::GetTextRegion(int32 startOffset, int32 endOffset, BRegion *outRegion)
selRect.left = max_c(startPt.x, fTextRect.left); selRect.left = max_c(startPt.x, fTextRect.left);
selRect.top = startPt.y; selRect.top = startPt.y;
selRect.right = endPt.x - 1.0; selRect.right = endPt.x - 1.0;
selRect.bottom = endPt.y + endLineHeight - 1.0; selRect.bottom = endPt.y + endLineHeight;
outRegion->Include(selRect); outRegion->Include(selRect);
} else { } else {
// more than one line in the specified offset range // more than one line in the specified offset range
selRect.left = max_c(startPt.x, fTextRect.left); selRect.left = max_c(startPt.x, fTextRect.left);
selRect.top = startPt.y; selRect.top = startPt.y;
selRect.right = fTextRect.right; selRect.right = fTextRect.right;
selRect.bottom = startPt.y + startLineHeight - 1.0; selRect.bottom = startPt.y + startLineHeight;
outRegion->Include(selRect); outRegion->Include(selRect);
if (startPt.y + startLineHeight < endPt.y) { if (startPt.y + startLineHeight < endPt.y) {
@@ -2048,14 +2062,14 @@ BTextView::GetTextRegion(int32 startOffset, int32 endOffset, BRegion *outRegion)
selRect.left = fTextRect.left; selRect.left = fTextRect.left;
selRect.top = startPt.y + startLineHeight; selRect.top = startPt.y + startLineHeight;
selRect.right = fTextRect.right; selRect.right = fTextRect.right;
selRect.bottom = endPt.y - 1.0; selRect.bottom = endPt.y;
outRegion->Include(selRect); outRegion->Include(selRect);
} }
selRect.left = fTextRect.left; selRect.left = fTextRect.left;
selRect.top = endPt.y; selRect.top = endPt.y;
selRect.right = endPt.x - 1.0; selRect.right = endPt.x - 1.0;
selRect.bottom = endPt.y + endLineHeight - 1.0; selRect.bottom = endPt.y + endLineHeight;
outRegion->Include(selRect); outRegion->Include(selRect);
} }
} }
@@ -2145,11 +2159,16 @@ BTextView::SetTextRect(BRect rect)
if (rect == fTextRect) if (rect == fTextRect)
return; 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 bool needsRefresh = fTextRect.left != rect.left
|| fTextRect.right != rect.right || fTextRect.top != rect.top; || fTextRect.right != rect.right || fTextRect.top != rect.top;
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) if (!needsRefresh)
rect.bottom = fTextRect.bottom; rect.bottom = fTextRect.bottom;
@@ -4298,6 +4317,8 @@ BTextView::_PerformMouseUp(BPoint where)
Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset); Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset);
_StopMouseTracking(); _StopMouseTracking();
// adjust cursor if necessary
_TrackMouse(where, NULL, true);
return true; return true;
} }
@@ -4394,9 +4415,10 @@ BTextView::_TrackMouse(BPoint where, const BMessage *message, bool force)
if (message && AcceptsDrop(message)) if (message && AcceptsDrop(message))
_TrackDrag(where); _TrackDrag(where);
else if ((fSelectable || fEditable) && !textRegion.Contains(where)) else if ((fSelectable || fEditable)
&& (fTrackingMouse != NULL || !textRegion.Contains(where))) {
SetViewCursor(B_CURSOR_I_BEAM, force); SetViewCursor(B_CURSOR_I_BEAM, force);
else } else
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, force); SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, force);
} }
@@ -4518,7 +4540,7 @@ BTextView::_PerformAutoScrolling()
{ {
// Scroll the view a bit if mouse is outside the view bounds // Scroll the view a bit if mouse is outside the view bounds
BRect bounds = Bounds(); BRect bounds = Bounds();
BPoint scrollBy; BPoint scrollBy(B_ORIGIN);
BPoint constraint = fWhere; BPoint constraint = fWhere;
constraint.ConstrainTo(bounds); constraint.ConstrainTo(bounds);
@@ -4533,27 +4555,35 @@ BTextView::_PerformAutoScrolling()
scrollBy.x = -value; scrollBy.x = -value;
} }
float lineHeight = 0; if (CountLines() > 1) {
float vertDiff = 0; // scroll in Y only if multiple lines!
if (fWhere.y > bounds.bottom) {
lineHeight = LineHeight(LineAt(bounds.LeftBottom())); float lineHeight = 0;
vertDiff = fWhere.y - bounds.bottom; float vertDiff = 0;
} else if (fWhere.y < bounds.top) { if (fWhere.y > bounds.bottom) {
lineHeight = LineHeight(LineAt(bounds.LeftTop())); lineHeight = LineHeight(LineAt(bounds.LeftBottom()));
vertDiff = fWhere.y - bounds.top; // negative value vertDiff = fWhere.y - bounds.bottom;
} else if (fWhere.y < bounds.top) {
lineHeight = LineHeight(LineAt(bounds.LeftTop()));
vertDiff = fWhere.y - bounds.top; // negative value
}
// Always scroll vertically line by line or by multiples of that
// based on the distance of the cursor from the border of the view
// TODO: Refine this, I can't even remember how beos works here
scrollBy.y = lineHeight > 0 ? lineHeight * (int32)(floorf(vertDiff)
/ lineHeight) : 0;
// prevent from scrolling out of view
if (scrollBy.y != 0.0) {
float bottomMax = floorf(fTextRect.bottom
+ fLayoutData->bottomInset);
if (bounds.bottom + scrollBy.y > bottomMax)
scrollBy.y = bottomMax - bounds.bottom;
else if (bounds.top + scrollBy.y < 0)
scrollBy.y = -bounds.top;
}
} }
// Always scroll vertically line by line or by multiples of that
// based on the distance of the cursor from the border of the view
// TODO: Refine this, I can't even remember how beos works here
scrollBy.y = lineHeight > 0 ? lineHeight * (int32)(floorf(vertDiff)
/ lineHeight) : 0;
if (bounds.bottom + scrollBy.y > fTextRect.Height())
scrollBy.y = fTextRect.Height() - bounds.bottom;
else if (bounds.top + scrollBy.y < 0)
scrollBy.y = -bounds.top;
if (scrollBy != B_ORIGIN) if (scrollBy != B_ORIGIN)
ScrollBy(scrollBy.x, scrollBy.y); ScrollBy(scrollBy.x, scrollBy.y);
} }