BTextView: Fix Tracker edit name

Don't resize text view in FrameResized() if resizable, this is done
in _AutoResize() instead. Set text rect width to width of max line when
word-wrap is off. Text rect width shrinks to the width of the text
matching behavior of BeOS R5 and previous Haiku. This fixes Tracker
Edit name.

Limit max width to column width in list mode or 30em in icon mode.
Filter paste messages limiting to max width in Tracker Edit name.

General BTextView fixes:

As a consequence of the text rect shrinking to fit the text, adjust
highlighting to go at least to edge of the view even if text rect width
is narrower. Extend the invalidation area beyond text rect when
redrawing to include highlighted areas.

Text views behave properly when overflow occurs i.e. when you type
text off the end of the text view. The text is nudged over as you
type/scroll so that the previous text is visible. This sorta worked
before but now works better.

Fix text rect centering by replacing switch with
BLayoutUtils::AlignOnRect().

Coalesce consecutive draw calls when inserting and deleting text to
prevent flashing for example when resizing the window. Redraw text
when the text view scrolls fixing a bug I noticed in StyledEdit.

Workaround negative height Beezer bug.

Fixes #16642, #16476

Change-Id: I2d32d6039944d2dc3218ce4de71f2966cc98c866
Reviewed-on: https://review.haiku-os.org/c/haiku/+/3642
Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org>
Reviewed-by: Adrien Destugues <pulkomandy@gmail.com>
This commit is contained in:
John Scipione 2021-01-17 14:57:07 -05:00 committed by Alex von Gluck IV
parent 7d961f9746
commit 7912dad947
7 changed files with 322 additions and 199 deletions

View File

@ -302,6 +302,7 @@ private:
int32 scrollTo = INT32_MIN);
void _RecalculateLineBreaks(int32* startLine,
int32* endLine);
void _ValidateTextRect();
int32 _FindLineBreak(int32 fromOffset,
float* _ascent, float* _descent,
float* inOutWidth);

View File

@ -31,6 +31,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <Alignment.h>
#include <Application.h>
#include <Beep.h>
#include <Bitmap.h>
@ -509,12 +510,7 @@ BTextView::AttachedToWindow()
fDragOffset = -1;
fActive = false;
// text rect right must be greater than left
if (fTextRect.right <= fTextRect.left)
fTextRect.right = fTextRect.left + 1;
// text rect bottom must be greater than top
if (fTextRect.bottom <= fTextRect.top)
fTextRect.bottom = fTextRect.top + 1;
_ValidateTextRect();
_AutoResize(true);
@ -808,53 +804,36 @@ BTextView::FrameResized(float newWidth, float newHeight)
{
BView::FrameResized(newWidth, newHeight);
// frame resized in _AutoResize() instead
if (fResizable)
return;
if (fWrap) {
// recalculate line breaks
// will update scroll bars if text rect changes
_ResetTextRect();
} else {
// don't recalculate line breaks,
// move text rect into position and redraw.
BRect oldTextRect(fTextRect);
BRect bounds(0, 0, newWidth, newHeight);
float dataWidth = fLayoutData->leftInset
+ fTextRect.Width() + fLayoutData->rightInset;
newWidth = std::max(dataWidth, newWidth);
switch (fAlignment) {
default:
case B_ALIGN_LEFT:
// move text rect to left top corner
fTextRect.OffsetTo(bounds.left, fTextRect.top);
fTextRect.OffsetBy(fLayoutData->leftInset, 0);
break;
// align rect
BRect rect(fLayoutData->leftInset, fLayoutData->topInset,
newWidth - fLayoutData->rightInset,
newHeight - fLayoutData->bottomInset);
case B_ALIGN_RIGHT:
{
// move text rect to right top corner
float textRectWidthPlusInsets = fTextRect.Width()
+ fLayoutData->leftInset + fLayoutData->rightInset;
float maxWidth = std::max(bounds.Width(),
textRectWidthPlusInsets);
fTextRect.OffsetTo(maxWidth - fTextRect.Width(),
fTextRect.top);
fTextRect.OffsetBy(-fLayoutData->rightInset, 0);
break;
}
rect = BLayoutUtils::AlignOnRect(rect,
BSize(fTextRect.Width(), fTextRect.Height()),
BAlignment(fAlignment, B_ALIGN_TOP));
fTextRect.OffsetTo(rect.left, rect.top);
case B_ALIGN_CENTER:
// move text rect to center
fTextRect.OffsetTo(ceilf((fTextRect.Width() - bounds.Width())
/ 2), fTextRect.top);
break;
}
// only redraw if text rect changes
if (fTextRect != oldTextRect) {
// Make sure that the dirty area outside the text is redrawn too.
BRegion dirty(oldTextRect | fTextRect);
Invalidate(&dirty);
}
// must invalidate whole thing because of highlighting
Invalidate();
_UpdateScrollbars();
}
_UpdateScrollbars();
}
@ -1187,6 +1166,7 @@ BTextView::SetText(const char* text, int32 length, const text_run_array* runs)
if (!Bounds().IsValid()) {
ResizeTo(LineWidth(0), LineHeight(0));
fTextRect = Bounds();
_ValidateTextRect();
_UpdateInsets(fTextRect);
}
@ -2125,35 +2105,44 @@ BTextView::GetTextRegion(int32 startOffset, int32 endOffset,
endLineHeight = ceilf(endLineHeight);
BRect selRect;
const BRect bounds(Bounds());
if (startPt.y == endPt.y) {
// this is a one-line region
selRect.left = std::max(startPt.x, fTextRect.left);
selRect.left = startPt.x;
selRect.top = startPt.y;
selRect.right = endPt.x - 1.0;
selRect.bottom = endPt.y + endLineHeight - 1.0;
selRect.right = endPt.x - 1;
selRect.bottom = endPt.y + endLineHeight - 1;
outRegion->Include(selRect);
} else {
// more than one line in the specified offset range
selRect.left = std::max(startPt.x, fTextRect.left);
// include first line from start of selection to end of window
selRect.left = startPt.x;
selRect.top = startPt.y;
selRect.right = fTextRect.right;
selRect.bottom = startPt.y + startLineHeight - 1.0;
selRect.right = std::max(fTextRect.right,
bounds.right - fLayoutData->rightInset);
selRect.bottom = startPt.y + startLineHeight - 1;
outRegion->Include(selRect);
if (startPt.y + startLineHeight < endPt.y) {
// more than two lines in the range
selRect.left = fTextRect.left;
// include middle lines from start to end of window
selRect.left = std::min(fTextRect.left,
bounds.left + fLayoutData->leftInset);
selRect.top = startPt.y + startLineHeight;
selRect.right = fTextRect.right;
selRect.bottom = endPt.y - 1.0;
selRect.right = std::max(fTextRect.right,
bounds.right - fLayoutData->rightInset);
selRect.bottom = endPt.y - 1;
outRegion->Include(selRect);
}
selRect.left = fTextRect.left;
// include last line start of window to end of selection
selRect.left = std::min(fTextRect.left,
bounds.left + fLayoutData->leftInset);
selRect.top = endPt.y;
selRect.right = endPt.x - 1.0;
selRect.bottom = endPt.y + endLineHeight - 1.0;
selRect.right = endPt.x - 1;
selRect.bottom = endPt.y + endLineHeight - 1;
outRegion->Include(selRect);
}
}
@ -2164,30 +2153,42 @@ BTextView::ScrollToOffset(int32 offset)
{
BRect bounds = Bounds();
float lineHeight = 0.0;
float xDiff = 0.0;
float yDiff = 0.0;
BPoint point = PointAt(offset, &lineHeight);
BPoint scrollBy(B_ORIGIN);
// horizontal
float extraSpace = ceilf(bounds.IntegerWidth() / 2);
if (point.x < bounds.left)
xDiff = point.x - bounds.right + extraSpace;
scrollBy.x = point.x - bounds.right;
else if (point.x > bounds.right)
xDiff = point.x - bounds.left - extraSpace;
scrollBy.x = point.x - bounds.left;
// vertical
if (point.y < bounds.top)
yDiff = point.y - bounds.top;
else if (point.y + lineHeight > bounds.bottom
&& point.y - lineHeight > bounds.top) {
yDiff = point.y + lineHeight - bounds.bottom;
// prevent from scrolling out of view
if (scrollBy.x != 0.0) {
float rightMax = fTextRect.right + fLayoutData->rightInset;
if (bounds.right + scrollBy.x > rightMax)
scrollBy.x = rightMax - bounds.right;
float leftMin = fTextRect.left - fLayoutData->leftInset;
if (bounds.left + scrollBy.x < leftMin)
scrollBy.x = leftMin - bounds.left;
}
// prevent negative scroll offset in y
if (bounds.top + yDiff < 0.0)
yDiff = -bounds.top;
// vertical
if (CountLines() > 1) {
// scroll in Y only if multiple lines!
if (point.y < bounds.top - fLayoutData->topInset)
scrollBy.y = point.y - bounds.top - fLayoutData->topInset;
else if (point.y + lineHeight > bounds.bottom
+ fLayoutData->bottomInset) {
scrollBy.y = point.y + lineHeight - bounds.bottom
+ fLayoutData->bottomInset;
}
}
ScrollBy(xDiff, yDiff);
ScrollBy(scrollBy.x, scrollBy.y);
// Update text rect position and scroll bars
if (CountLines() > 1 && !fWrap)
FrameResized(Bounds().Width(), Bounds().Height());
}
@ -2409,6 +2410,8 @@ BTextView::SetWordWrap(bool wrap)
_HideCaret();
}
BRect savedBounds = Bounds();
fWrap = wrap;
if (wrap)
_ResetTextRect(); // calls _Refresh
@ -2418,6 +2421,10 @@ BTextView::SetWordWrap(bool wrap)
if (fEditable)
ScrollToOffset(fCaretOffset);
// redraw text rect and update scroll bars if bounds have changed
if (Bounds() != savedBounds)
FrameResized(Bounds().Width(), Bounds().Height());
if (updateOnScreen) {
// show the caret, hilite the selection
if (fSelStart != fSelEnd) {
@ -2491,8 +2498,11 @@ BTextView::SetAlignment(alignment align)
fAlignment = align;
// After setting new alignment, update the view/window
if (Window() != NULL)
if (Window() != NULL) {
FrameResized(Bounds().Width(), Bounds().Height());
// text rect position and scroll bars may change
Invalidate();
}
}
}
@ -2557,7 +2567,7 @@ BTextView::MakeResizable(bool resize, BView* resizeView)
}
// We need to reset the right inset, as otherwise the auto-resize would
// get confused about just how wide the textview needs to be.
// This seems to be an artefact of how Tracker creates the textview
// This seems to be an artifact of how Tracker creates the textview
// during a rename action.
fLayoutData->rightInset = fLayoutData->leftInset;
} else {
@ -3185,10 +3195,8 @@ BTextView::_InitObject(BRect textRect, const BFont* initialFont,
// if needed.
fTextRect = textRect;
// NOTE: The only places where text rect is changed:
// * width is possibly adjusted in _AutoResize(),
// * height is adjusted in _RecalculateLineBreaks().
// When used within the layout management framework, the
// text rect is changed to maintain constant insets.
// * width and height are adjusted in _RecalculateLineBreaks(),
// text rect maintains constant insets, use SetInsets() to change.
fMinTextRectWidth = fTextRect.Width();
// see SetTextRect()
fSelStart = fSelEnd = 0;
@ -3258,10 +3266,12 @@ BTextView::_HandleBackspace(int32 modifiers)
undoBuffer->BackwardErase();
}
// we may draw twice, so turn updates off for now
if (Window() != NULL)
Window()->DisableUpdates();
if (fSelStart == fSelEnd) {
if (fSelStart == 0)
return;
else
if (fSelStart != 0)
fSelStart = _PreviousInitialByte(fSelStart);
} else
Highlight(fSelStart, fSelEnd);
@ -3270,6 +3280,10 @@ BTextView::_HandleBackspace(int32 modifiers)
fCaretOffset = fSelEnd = fSelStart;
_Refresh(fSelStart, fSelEnd, fCaretOffset);
// turn updates back on
if (Window() != NULL)
Window()->EnableUpdates();
}
@ -3479,10 +3493,12 @@ BTextView::_HandleDelete(int32 modifiers)
undoBuffer->ForwardErase();
}
// we may draw twice, so turn updates off for now
if (Window() != NULL)
Window()->DisableUpdates();
if (fSelStart == fSelEnd) {
if (fSelEnd == fText->Length())
return;
else
if (fSelEnd != fText->Length())
fSelEnd = _NextInitialByte(fSelEnd);
} else
Highlight(fSelStart, fSelEnd);
@ -3491,6 +3507,10 @@ BTextView::_HandleDelete(int32 modifiers)
fCaretOffset = fSelEnd = fSelStart;
_Refresh(fSelStart, fSelEnd, fCaretOffset);
// turn updates back on
if (Window() != NULL)
Window()->EnableUpdates();
}
@ -3694,6 +3714,10 @@ BTextView::_HandleAlphaKey(const char* bytes, int32 numBytes)
DeleteText(fSelStart, fSelEnd);
}
// we may draw twice, so turn updates off for now
if (Window() != NULL)
Window()->DisableUpdates();
if (fAutoindent && numBytes == 1 && *bytes == B_ENTER) {
int32 start, offset;
start = offset = OffsetAt(_LineAt(fSelStart));
@ -3710,8 +3734,11 @@ BTextView::_HandleAlphaKey(const char* bytes, int32 numBytes)
_DoInsertText(bytes, numBytes, fSelStart, NULL);
fCaretOffset = fSelEnd;
ScrollToOffset(fCaretOffset);
// turn updates back on
if (Window() != NULL)
Window()->EnableUpdates();
}
@ -3883,25 +3910,23 @@ BTextView::_RecalculateLineBreaks(int32* startLine, int32* endLine)
switch (fAlignment) {
default:
case B_ALIGN_LEFT:
// grow right
fTextRect.right = std::max(fTextRect.right,
fTextRect.left + fMinTextRectWidth);
// move right edge
fTextRect.right = fTextRect.left + fMinTextRectWidth;
break;
case B_ALIGN_RIGHT:
// grow left
fTextRect.left = std::min(fTextRect.left,
fTextRect.right - fMinTextRectWidth);
// move left edge
fTextRect.left = fTextRect.right - fMinTextRectWidth;
break;
case B_ALIGN_CENTER:
// grow out
if (fMinTextRectWidth > fTextRect.Width()) {
fTextRect.InsetBy(ceilf((fTextRect.Width()
- fMinTextRectWidth) / 2.0f), 0);
}
// move both edges
fTextRect.InsetBy(roundf((fTextRect.Width()
- fMinTextRectWidth) / 2), 0);
break;
}
_ValidateTextRect();
}
*endLine = lineIndex - 1;
@ -3909,6 +3934,18 @@ BTextView::_RecalculateLineBreaks(int32* startLine, int32* endLine)
}
void
BTextView::_ValidateTextRect()
{
// text rect right must be greater than left
if (fTextRect.right <= fTextRect.left)
fTextRect.right = fTextRect.left + 1;
// text rect bottom must be greater than top
if (fTextRect.bottom <= fTextRect.top)
fTextRect.bottom = fTextRect.top + 1;
}
int32
BTextView::_FindLineBreak(int32 fromOffset, float* _ascent, float* _descent,
float* inOutWidth)
@ -4482,14 +4519,15 @@ BTextView::_DrawLines(int32 startLine, int32 endLine, int32 startOffset,
if (!Window())
return;
// clip the text
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);
const BRect bounds(Bounds());
// clip the text extending to end of selection
BRect clipRect(fTextRect);
clipRect.left = std::min(fTextRect.left,
bounds.left + fLayoutData->leftInset);
clipRect.right = std::max(fTextRect.right,
bounds.right - fLayoutData->rightInset);
clipRect = bounds & clipRect;
BRegion newClip;
newClip.Set(clipRect);
@ -4590,7 +4628,6 @@ BTextView::_RequestDrawLines(int32 startLine, int32 endLine)
Bounds().right,
to != NULL ? to->origin + fTextRect.top : fTextRect.bottom);
Invalidate(invalidRect);
Window()->UpdateIfNeeded();
}
@ -4939,11 +4976,12 @@ BTextView::_PerformAutoScrolling()
// prevent from scrolling out of view
if (scrollBy.x != 0.0) {
float rightMax = floorf(fTextRect.right + fLayoutData->rightInset);
float rightMax = fTextRect.right + fLayoutData->rightInset;
if (bounds.right + scrollBy.x > rightMax)
scrollBy.x = rightMax - bounds.right;
if (bounds.left + scrollBy.x < 0)
scrollBy.x = -bounds.left;
float leftMin = fTextRect.left - fLayoutData->leftInset;
if (bounds.left + scrollBy.x < leftMin)
scrollBy.x = leftMin - bounds.left;
}
if (CountLines() > 1) {
@ -4955,12 +4993,12 @@ BTextView::_PerformAutoScrolling()
// prevent from scrolling out of view
if (scrollBy.y != 0.0) {
float bottomMax = floorf(fTextRect.bottom
+ fLayoutData->bottomInset);
float bottomMax = fTextRect.bottom + fLayoutData->bottomInset;
if (bounds.bottom + scrollBy.y > bottomMax)
scrollBy.y = bottomMax - bounds.bottom;
if (bounds.top + scrollBy.y < 0)
scrollBy.y = -bounds.top;
float topMin = fTextRect.top - fLayoutData->topInset;
if (bounds.top + scrollBy.y < topMin)
scrollBy.y = topMin - bounds.top;
}
}
@ -5046,40 +5084,33 @@ BTextView::_ScrollTo(float x, float y)
void
BTextView::_AutoResize(bool redraw)
{
if (!fResizable)
if (!fResizable || fContainerView == NULL)
return;
BRect bounds = Bounds();
// NOTE: This container view thing is only used by Tracker.
// move container view if not left aligned
float oldWidth = Bounds().Width();
float newWidth = fLayoutData->leftInset + fTextRect.Width()
+ fLayoutData->rightInset;
float right = oldWidth - newWidth;
if (fContainerView != NULL) {
// NOTE: This container view thing is only used by Tracker.
// move container view if not left aligned
float oldWidth = bounds.Width();
float newWidth = ceilf(fLayoutData->leftInset
+ fTextRect.Width() + fLayoutData->rightInset);
if (fAlignment == B_ALIGN_CENTER) {
if (fmod(ceilf(newWidth - oldWidth), 2.0) != 0.0)
newWidth += 1;
if (fAlignment == B_ALIGN_CENTER)
fContainerView->MoveBy(roundf(right / 2), 0);
else if (fAlignment == B_ALIGN_RIGHT)
fContainerView->MoveBy(right, 0);
fContainerView->MoveBy(ceilf(oldWidth - newWidth) / 2, 0);
} else if (fAlignment == B_ALIGN_RIGHT)
fContainerView->MoveBy(ceilf(oldWidth - newWidth), 0);
// resize container view
float grow = newWidth - oldWidth;
fContainerView->ResizeBy(grow, 0);
// resize container view
fContainerView->ResizeBy(ceilf(newWidth - oldWidth), 0);
}
// reposition text view
fTextRect.OffsetTo(fLayoutData->leftInset, fLayoutData->topInset);
// scroll rect to start, there is room for full text
ScrollToOffset(0);
if (redraw)
_RequestDrawLines(0, 0);
// erase any potential left over outside the text rect
// (can only be on right hand side)
BRect dirty(fTextRect.right + 1, fTextRect.top, bounds.right,
fTextRect.bottom);
if (dirty.IsValid()) {
SetLowColor(ViewColor());
FillRect(dirty, B_SOLID_LOW);
}
}

View File

@ -1486,7 +1486,7 @@ BContainerWindow::MessageReceived(BMessage* message)
// The selected item is not a BTextView, so forward the
// message to the PoseView.
if (fPoseView != NULL)
fPoseView->MessageReceived(message);
PostMessage(message, fPoseView);
} else {
// Since we catch the generic clipboard shortcuts in a way that
// means the BTextView will never get them, we must
@ -1497,7 +1497,7 @@ BContainerWindow::MessageReceived(BMessage* message)
// recursion.
if (message->what == B_CUT || message->what == B_COPY
|| message->what == B_PASTE) {
view->MessageReceived(message);
PostMessage(message, view);
}
}
break;

View File

@ -277,7 +277,8 @@ BPoseView::BPoseView(Model* model, uint32 viewMode)
fLastKeyTime(0),
fLastDeskbarFrameCheckTime(LONGLONG_MIN),
fDeskbarFrame(0, 0, -1, -1),
fTextWidgetToCheck(NULL)
fTextWidgetToCheck(NULL),
fActiveTextWidget(NULL)
{
fListElemHeight = ceilf(be_plain_font->Size() * 1.65f);

View File

@ -414,6 +414,9 @@ public:
void SetTextWidgetToCheck(BTextWidget*, BTextWidget* = NULL);
BTextWidget* ActiveTextWidget() { return fActiveTextWidget; };
void SetActiveTextWidget(BTextWidget* w) { fActiveTextWidget = w; };
protected:
// view setup
virtual void SetUpDefaultColumnsIfNeeded();
@ -808,6 +811,7 @@ protected:
static OffscreenBitmap* sOffscreen;
BTextWidget* fTextWidgetToCheck;
BTextWidget* fActiveTextWidget;
typedef BView _inherited;
};

View File

@ -40,6 +40,7 @@ All rights reserved.
#include <Alert.h>
#include <Catalog.h>
#include <Clipboard.h>
#include <Debug.h>
#include <Directory.h>
#include <MessageFilter.h>
@ -75,6 +76,7 @@ BTextWidget::BTextWidget(Model* model, BColumn* column, BPoseView* view)
fVisible(true),
fActive(false),
fSymLink(model->IsSymLink()),
fMaxWidth(0),
fLastClickedTime(0)
{
}
@ -146,27 +148,31 @@ BTextWidget::CalcRectCommon(BPoint poseLoc, const BColumn* column,
const BPoseView* view, float textWidth)
{
BRect result;
float viewWidth = textWidth;
if (view->ViewMode() == kListMode) {
viewWidth = std::min(column->Width(), textWidth);
poseLoc.x += column->Offset();
switch (fAlignment) {
case B_ALIGN_LEFT:
result.left = poseLoc.x;
result.right = result.left + textWidth + 1;
result.right = result.left + 1 + viewWidth;
break;
case B_ALIGN_CENTER:
result.left = poseLoc.x + (column->Width() / 2)
- (textWidth / 2);
result.left = poseLoc.x
+ roundf((column->Width() - viewWidth) / 2);
if (result.left < 0)
result.left = 0;
result.right = result.left + textWidth + 1;
result.right = result.left + 1 + viewWidth;
break;
case B_ALIGN_RIGHT:
result.right = poseLoc.x + column->Width();
result.left = result.right - textWidth - 1;
result.left = result.right - 1 - viewWidth;
if (result.left < 0)
result.left = 0;
break;
@ -179,17 +185,22 @@ BTextWidget::CalcRectCommon(BPoint poseLoc, const BColumn* column,
result.bottom = poseLoc.y
+ roundf((view->ListElemHeight() + view->FontHeight()) / 2);
} else {
viewWidth = std::min(view->StringWidth("M") * 30, textWidth);
if (view->ViewMode() == kIconMode) {
// large/scaled icon mode
result.left = poseLoc.x + (view->IconSizeInt() - textWidth) / 2;
// icon mode
result.left = poseLoc.x
+ roundf((view->IconSizeInt() - viewWidth) / 2);
result.bottom = poseLoc.y + view->IconPoseHeight();
} else {
// mini icon mode
result.left = poseLoc.x + B_MINI_ICON + kMiniIconSeparator;
result.bottom = poseLoc.y
+ roundf((B_MINI_ICON + view->FontHeight()) / 2);
}
result.right = result.left + textWidth;
result.bottom = poseLoc.y + view->IconPoseHeight();
result.right = result.left + viewWidth;
}
result.top = result.bottom - view->FontHeight();
return result;
@ -294,7 +305,7 @@ BTextWidget::MouseUp(BRect bounds, BPoseView* view, BPose* pose, BPoint)
static filter_result
TextViewFilter(BMessage* message, BHandler**, BMessageFilter* filter)
TextViewKeyDownFilter(BMessage* message, BHandler**, BMessageFilter* filter)
{
uchar key;
if (message->FindInt8("byte", (int8*)&key) != B_OK)
@ -306,20 +317,20 @@ TextViewFilter(BMessage* message, BHandler**, BMessageFilter* filter)
filter->Looper());
ThrowOnAssert(window != NULL);
BPoseView* poseView = window->PoseView();
ThrowOnAssert(poseView != NULL);
BPoseView* view = window->PoseView();
ThrowOnAssert(view != NULL);
if (key == B_RETURN || key == B_ESCAPE) {
poseView->CommitActivePose(key == B_RETURN);
view->CommitActivePose(key == B_RETURN);
return B_SKIP_MESSAGE;
}
if (key == B_TAB) {
if (poseView->ActivePose()) {
if (view->ActivePose()) {
if (message->FindInt32("modifiers") & B_SHIFT_KEY)
poseView->ActivePose()->EditPreviousWidget(poseView);
view->ActivePose()->EditPreviousWidget(view);
else
poseView->ActivePose()->EditNextWidget(poseView);
view->ActivePose()->EditNextWidget(view);
}
return B_SKIP_MESSAGE;
@ -329,23 +340,94 @@ TextViewFilter(BMessage* message, BHandler**, BMessageFilter* filter)
// we try to work-around this "bug" here.
// find the text editing view
BView* scrollView = poseView->FindView("BorderView");
BView* scrollView = view->FindView("BorderView");
if (scrollView != NULL) {
BTextView* textView = dynamic_cast<BTextView*>(
scrollView->FindView("WidgetTextView"));
if (textView != NULL) {
BRect textRect = textView->TextRect();
BRect rect = scrollView->Frame();
ASSERT(view->ActiveTextWidget() != NULL);
float maxWidth = view->ActiveTextWidget()->MaxWidth();
bool tooWide = textView->TextRect().Width() > maxWidth;
textView->MakeResizable(!tooWide, tooWide ? NULL : scrollView);
}
}
if (rect.right + 5 > poseView->Bounds().right
|| rect.left - 5 < 0)
textView->MakeResizable(true, NULL);
return B_DISPATCH_MESSAGE;
}
if (textRect.Width() + 10 < rect.Width()) {
textView->MakeResizable(true, scrollView);
// make sure no empty white space stays on the right
textView->ScrollToOffset(0);
static filter_result
TextViewPasteFilter(BMessage* message, BHandler**, BMessageFilter* filter)
{
ThrowOnAssert(filter != NULL);
BContainerWindow* window = dynamic_cast<BContainerWindow*>(
filter->Looper());
ThrowOnAssert(window != NULL);
BPoseView* view = window->PoseView();
ThrowOnAssert(view != NULL);
// the BTextView doesn't respect window borders when resizing itself;
// we try to work-around this "bug" here.
// find the text editing view
BView* scrollView = view->FindView("BorderView");
if (scrollView != NULL) {
BTextView* textView = dynamic_cast<BTextView*>(
scrollView->FindView("WidgetTextView"));
if (textView != NULL) {
float textWidth = textView->TextRect().Width();
// subtract out selected text region width
int32 start, finish;
textView->GetSelection(&start, &finish);
if (start != finish) {
BRegion selectedRegion;
textView->GetTextRegion(start, finish, &selectedRegion);
textWidth -= selectedRegion.Frame().Width();
}
// add pasted text width
if (be_clipboard->Lock()) {
BMessage* clip = be_clipboard->Data();
if (clip != NULL) {
const char* text = NULL;
ssize_t length = 0;
if (clip->FindData("text/plain", B_MIME_TYPE,
(const void**)&text, &length) == B_OK) {
textWidth += textView->StringWidth(text);
}
}
be_clipboard->Unlock();
}
// check if pasted text is too wide
ASSERT(view->ActiveTextWidget() != NULL);
float maxWidth = view->ActiveTextWidget()->MaxWidth();
bool tooWide = textWidth > maxWidth;
if (tooWide) {
// resize text view to max width
// move scroll view if not left aligned
float oldWidth = textView->Bounds().Width();
float newWidth = maxWidth;
float right = oldWidth - newWidth;
if (textView->Alignment() == B_ALIGN_CENTER)
scrollView->MoveBy(roundf(right / 2), 0);
else if (textView->Alignment() == B_ALIGN_RIGHT)
scrollView->MoveBy(right, 0);
// resize scroll view
float grow = newWidth - oldWidth;
scrollView->ResizeBy(grow, 0);
}
textView->MakeResizable(!tooWide, tooWide ? NULL : scrollView);
}
}
@ -366,18 +448,13 @@ BTextWidget::StartEdit(BRect bounds, BPoseView* view, BPose* pose)
return;
}
view->SetActiveTextWidget(this);
// TODO fix text rect being off by a pixel on some files
// get bounds with full text length
BRect rect(bounds);
BRect textRect(bounds);
// label offset
float hOffset = 0;
float vOffset = view->ViewMode() == kListMode ? -1 : -2;
rect.OffsetBy(hOffset, vOffset);
BTextView* textView = new BTextView(rect, "WidgetTextView", textRect,
rect.OffsetBy(view->ViewMode() == kListMode ? -1 : 1, -4);
BTextView* textView = new BTextView(rect, "WidgetTextView", rect,
be_plain_font, 0, B_FOLLOW_ALL, B_WILL_DRAW);
textView->SetWordWrap(false);
@ -385,36 +462,34 @@ BTextWidget::StartEdit(BRect bounds, BPoseView* view, BPose* pose)
DisallowMetaKeys(textView);
fText->SetUpEditing(textView);
textView->AddFilter(new BMessageFilter(B_KEY_DOWN, TextViewFilter));
textView->AddFilter(new BMessageFilter(B_KEY_DOWN, TextViewKeyDownFilter));
textView->AddFilter(new BMessageFilter(B_PASTE, TextViewPasteFilter));
// get full text length
rect.right = rect.left + textView->LineWidth();
rect.bottom = rect.top + textView->LineHeight() - 1;
rect.bottom = rect.top + textView->LineHeight() - 1 + 4;
// enlarge rect by inset amount
rect.InsetBy(-2, -2);
if (view->ViewMode() == kListMode) {
// limit max width to column width in list mode
BColumn* column = view->ColumnFor(fAttrHash);
ASSERT(column != NULL);
fMaxWidth = column->Width();
} else {
// limit max width to 30em in icon and mini icon mode
fMaxWidth = textView->StringWidth("M") * 30;
// undo label offset
textRect = rect.OffsetToCopy(-hOffset, -vOffset);
textView->SetTextRect(textRect);
BPoint origin = view->LeftTop();
textRect = view->Bounds();
bool hitBorder = false;
if (rect.left <= origin.x)
rect.left = origin.x + 1, hitBorder = true;
if (rect.right >= textRect.right)
rect.right = textRect.right - 1, hitBorder = true;
if (textView->LineWidth() > fMaxWidth) {
// compensate for text going over right inset
rect.OffsetBy(-2, 0);
}
}
// resize textView
textView->MoveTo(rect.LeftTop());
textView->ResizeTo(rect.Width(), rect.Height());
textView->ResizeTo(std::min(fMaxWidth, rect.Width()), rect.Height());
textView->SetTextRect(rect);
BScrollView* scrollView = new BScrollView("BorderView", textView, 0, 0,
false, false, B_PLAIN_BORDER);
view->AddChild(scrollView);
// configure text view
// set alignment before adding textView so it doesn't redraw
switch (view->ViewMode()) {
case kIconMode:
textView->SetAlignment(B_ALIGN_CENTER);
@ -428,7 +503,13 @@ BTextWidget::StartEdit(BRect bounds, BPoseView* view, BPose* pose)
textView->SetAlignment(fAlignment);
break;
}
textView->MakeResizable(true, hitBorder ? NULL : scrollView);
BScrollView* scrollView = new BScrollView("BorderView", textView, 0, 0,
false, false, B_PLAIN_BORDER);
view->AddChild(scrollView);
bool tooWide = textView->TextRect().Width() > fMaxWidth;
textView->MakeResizable(!tooWide, tooWide ? NULL : scrollView);
view->SetActivePose(pose);
// tell view about pose
@ -438,8 +519,6 @@ BTextWidget::StartEdit(BRect bounds, BPoseView* view, BPose* pose)
textView->SelectAll();
textView->ScrollToSelection();
// scroll to beginning so that text is visible
textView->ScrollBy(-1, -2);
// scroll in rect to center text
textView->MakeFocus();
// make this text widget invisible while we edit it
@ -459,6 +538,8 @@ void
BTextWidget::StopEdit(bool saveChanges, BPoint poseLoc, BPoseView* view,
BPose* pose, int32 poseIndex)
{
view->SetActiveTextWidget(NULL);
// find the text editing view
BView* scrollView = view->FindView("BorderView");
ASSERT(scrollView != NULL);

View File

@ -107,6 +107,9 @@ public:
void CheckExpiration();
void CancelWait();
float MaxWidth() { return fMaxWidth; };
void SetMaxWidth(float maxWidth) { fMaxWidth = maxWidth; };
private:
BRect CalcRectCommon(BPoint poseLoc, const BColumn*, const BPoseView*,
float width);
@ -121,6 +124,8 @@ private:
bool fActive : 1;
bool fSymLink : 1;
float fMaxWidth;
bigtime_t fLastClickedTime;
struct MouseUpParams fParams;
};