* Copyright 2001-2020 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus, superstippi@gmx.de
* Stefano Ceccherini, stefano.ceccherini@gmail.com
* Marc Flerackers, mflerackers@androme.be
* Hiroshi Lockheimer (BTextView is based on his STEEngine)
* John Scipione, jscipione@gmail.com
* Oliver Tappe, zooey@hirschkaefer.de
*/
#include <TextView.h>
#include <algorithm>
#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <Alignment.h>
#include <Application.h>
#include <Beep.h>
#include <Bitmap.h>
#include <Clipboard.h>
#include <ControlLook.h>
#include <Debug.h>
#include <Entry.h>
#include <Input.h>
#include <LayoutBuilder.h>
#include <LayoutUtils.h>
#include <MessageRunner.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <PropertyInfo.h>
#include <Region.h>
#include <ScrollBar.h>
#include <SystemCatalog.h>
#include <Window.h>
#include <binary_compatibility/Interface.h>
#include "InlineInput.h"
#include "LineBuffer.h"
#include "StyleBuffer.h"
#include "TextGapBuffer.h"
#include "UndoBuffer.h"
#include "WidthBuffer.h"
using namespace std;
using BPrivate::gSystemCatalog;
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "TextView"
#define TRANSLATE(str) \
gSystemCatalog.GetString(B_TRANSLATE_MARK(str), "TextView")
#undef TRACE
#undef CALLED
#ifdef TRACE_TEXT_VIEW
# include <FunctionTracer.h>
static int32 sFunctionDepth = -1;
# define CALLED(x...) FunctionTracer _ft("BTextView", __FUNCTION__, \
sFunctionDepth)
# define TRACE(x...) { BString _to; \
_to.Append(' ', (sFunctionDepth + 1) * 2); \
printf("%s", _to.String()); printf(x); }
#else
# define CALLED(x...)
# define TRACE(x...)
#endif
#define USE_WIDTHBUFFER 1
#define USE_DOUBLEBUFFERING 0
struct flattened_text_run {
int32 offset;
font_family family;
font_style style;
float size;
float shear;
uint16 face;
uint8 red;
uint8 green;
uint8 blue;
uint8 alpha;
uint16 _reserved_;
};
struct flattened_text_run_array {
uint32 magic;
uint32 version;
int32 count;
flattened_text_run styles[1];
};
static const uint32 kFlattenedTextRunArrayMagic = 'Ali!';
static const uint32 kFlattenedTextRunArrayVersion = 0;
enum {
CHAR_CLASS_DEFAULT,
CHAR_CLASS_WHITESPACE,
CHAR_CLASS_GRAPHICAL,
CHAR_CLASS_QUOTE,
CHAR_CLASS_PUNCTUATION,
CHAR_CLASS_PARENS_OPEN,
CHAR_CLASS_PARENS_CLOSE,
CHAR_CLASS_END_OF_TEXT
};
class BTextView::TextTrackState {
public:
TextTrackState(BMessenger messenger);
~TextTrackState();
void SimulateMouseMovement(BTextView* view);
public:
int32 clickOffset;
bool shiftDown;
BRect selectionRect;
BPoint where;
int32 anchor;
int32 selStart;
int32 selEnd;
private:
BMessageRunner* fRunner;
};
struct BTextView::LayoutData {
LayoutData()
: leftInset(0),
topInset(0),
rightInset(0),
bottomInset(0),
valid(false),
overridden(false)
{
}
float leftInset;
float topInset;
float rightInset;
float bottomInset;
BSize min;
BSize preferred;
bool valid : 1;
bool overridden : 1;
};
static const rgb_color kBlueInputColor = { 152, 203, 255, 255 };
static const rgb_color kRedInputColor = { 255, 152, 152, 255 };
static const float kHorizontalScrollBarStep = 10.0;
static const float kVerticalScrollBarStep = 12.0;
static const int32 kMsgNavigateArrow = '_NvA';
static const int32 kMsgNavigatePage = '_NvP';
static const int32 kMsgRemoveWord = '_RmW';
static property_info sPropertyList[] = {
{
"selection",
{ B_GET_PROPERTY, 0 },
{ B_DIRECT_SPECIFIER, 0 },
"Returns the current selection.", 0,
{ B_INT32_TYPE, 0 }
},
{
"selection",
{ B_SET_PROPERTY, 0 },
{ B_DIRECT_SPECIFIER, 0 },
"Sets the current selection.", 0,
{ B_INT32_TYPE, 0 }
},
{
"Text",
{ B_COUNT_PROPERTIES, 0 },
{ B_DIRECT_SPECIFIER, 0 },
"Returns the length of the text in bytes.", 0,
{ B_INT32_TYPE, 0 }
},
{
"Text",
{ B_GET_PROPERTY, 0 },
{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
"Returns the text in the specified range in the BTextView.", 0,
{ B_STRING_TYPE, 0 }
},
{
"Text",
{ B_SET_PROPERTY, 0 },
{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
"Removes or inserts text into the specified range in the BTextView.", 0,
{ B_STRING_TYPE, 0 }
},
{
"text_run_array",
{ B_GET_PROPERTY, 0 },
{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
"Returns the style information for the text in the specified range in "
"the BTextView.", 0,
{ B_RAW_TYPE, 0 },
},
{
"text_run_array",
{ B_SET_PROPERTY, 0 },
{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
"Sets the style information for the text in the specified range in the "
"BTextView.", 0,
{ B_RAW_TYPE, 0 },
},
{ 0 }
};
BTextView::BTextView(BRect frame, const char* name, BRect textRect,
uint32 resizeMask, uint32 flags)
:
BView(frame, name, resizeMask,
flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE),
fText(NULL),
fLines(NULL),
fStyles(NULL),
fDisallowedChars(NULL),
fUndo(NULL),
fDragRunner(NULL),
fClickRunner(NULL),
fLayoutData(NULL)
{
_InitObject(textRect, NULL, NULL);
SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
}
BTextView::BTextView(BRect frame, const char* name, BRect textRect,
const BFont* initialFont, const rgb_color* initialColor,
uint32 resizeMask, uint32 flags)
:
BView(frame, name, resizeMask,
flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE),
fText(NULL),
fLines(NULL),
fStyles(NULL),
fDisallowedChars(NULL),
fUndo(NULL),
fDragRunner(NULL),
fClickRunner(NULL),
fLayoutData(NULL)
{
_InitObject(textRect, initialFont, initialColor);
SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
}
BTextView::BTextView(const char* name, uint32 flags)
:
BView(name,
flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE),
fText(NULL),
fLines(NULL),
fStyles(NULL),
fDisallowedChars(NULL),
fUndo(NULL),
fDragRunner(NULL),
fClickRunner(NULL),
fLayoutData(NULL)
{
_InitObject(Bounds(), NULL, NULL);
SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
}
BTextView::BTextView(const char* name, const BFont* initialFont,
const rgb_color* initialColor, uint32 flags)
:
BView(name,
flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE),
fText(NULL),
fLines(NULL),
fStyles(NULL),
fDisallowedChars(NULL),
fUndo(NULL),
fDragRunner(NULL),
fClickRunner(NULL),
fLayoutData(NULL)
{
_InitObject(Bounds(), initialFont, initialColor);
SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
}
BTextView::BTextView(BMessage* archive)
:
BView(archive),
fText(NULL),
fLines(NULL),
fStyles(NULL),
fDisallowedChars(NULL),
fUndo(NULL),
fDragRunner(NULL),
fClickRunner(NULL),
fLayoutData(NULL)
{
CALLED();
BRect rect;
if (archive->FindRect("_trect", &rect) != B_OK)
rect.Set(0, 0, 0, 0);
_InitObject(rect, NULL, NULL);
bool toggle;
if (archive->FindBool("_password", &toggle) == B_OK)
HideTyping(toggle);
const char* text = NULL;
if (archive->FindString("_text", &text) == B_OK)
SetText(text);
int32 flag, flag2;
if (archive->FindInt32("_align", &flag) == B_OK)
SetAlignment((alignment)flag);
float value;
if (archive->FindFloat("_tab", &value) == B_OK)
SetTabWidth(value);
if (archive->FindInt32("_col_sp", &flag) == B_OK)
SetColorSpace((color_space)flag);
if (archive->FindInt32("_max", &flag) == B_OK)
SetMaxBytes(flag);
if (archive->FindInt32("_sel", &flag) == B_OK &&
archive->FindInt32("_sel", &flag2) == B_OK)
Select(flag, flag2);
if (archive->FindBool("_stylable", &toggle) == B_OK)
SetStylable(toggle);
if (archive->FindBool("_auto_in", &toggle) == B_OK)
SetAutoindent(toggle);
if (archive->FindBool("_wrap", &toggle) == B_OK)
SetWordWrap(toggle);
if (archive->FindBool("_nsel", &toggle) == B_OK)
MakeSelectable(!toggle);
if (archive->FindBool("_nedit", &toggle) == B_OK)
MakeEditable(!toggle);
ssize_t disallowedCount = 0;
const int32* disallowedChars = NULL;
if (archive->FindData("_dis_ch", B_RAW_TYPE,
(const void**)&disallowedChars, &disallowedCount) == B_OK) {
fDisallowedChars = new BList;
disallowedCount /= sizeof(int32);
for (int32 x = 0; x < disallowedCount; x++) {
fDisallowedChars->AddItem(
reinterpret_cast<void*>(disallowedChars[x]));
}
}
ssize_t runSize = 0;
const void* flattenedRun = NULL;
if (archive->FindData("_runs", B_RAW_TYPE, &flattenedRun, &runSize)
== B_OK) {
text_run_array* runArray = UnflattenRunArray(flattenedRun,
(int32*)&runSize);
if (runArray) {
SetRunArray(0, fText->Length(), runArray);
FreeRunArray(runArray);
}
}
}
BTextView::~BTextView()
{
_CancelInputMethod();
_StopMouseTracking();
_DeleteOffscreen();
delete fText;
delete fLines;
delete fStyles;
delete fDisallowedChars;
delete fUndo;
delete fDragRunner;
delete fClickRunner;
delete fLayoutData;
}
BArchivable*
BTextView::Instantiate(BMessage* archive)
{
CALLED();
if (validate_instantiation(archive, "BTextView"))
return new BTextView(archive);
return NULL;
}
status_t
BTextView::Archive(BMessage* data, bool deep) const
{
CALLED();
status_t err = BView::Archive(data, deep);
if (err == B_OK)
err = data->AddString("_text", Text());
if (err == B_OK)
err = data->AddInt32("_align", fAlignment);
if (err == B_OK)
err = data->AddFloat("_tab", fTabWidth);
if (err == B_OK)
err = data->AddInt32("_col_sp", fColorSpace);
if (err == B_OK)
err = data->AddRect("_trect", fTextRect);
if (err == B_OK)
err = data->AddInt32("_max", fMaxBytes);
if (err == B_OK)
err = data->AddInt32("_sel", fSelStart);
if (err == B_OK)
err = data->AddInt32("_sel", fSelEnd);
if (err == B_OK)
err = data->AddBool("_stylable", fStylable);
if (err == B_OK)
err = data->AddBool("_auto_in", fAutoindent);
if (err == B_OK)
err = data->AddBool("_wrap", fWrap);
if (err == B_OK)
err = data->AddBool("_nsel", !fSelectable);
if (err == B_OK)
err = data->AddBool("_nedit", !fEditable);
if (err == B_OK)
err = data->AddBool("_password", IsTypingHidden());
if (err == B_OK && fDisallowedChars != NULL && fDisallowedChars->CountItems() > 0) {
err = data->AddData("_dis_ch", B_RAW_TYPE, fDisallowedChars->Items(),
fDisallowedChars->CountItems() * sizeof(int32));
}
if (err == B_OK) {
int32 runSize = 0;
text_run_array* runArray = RunArray(0, fText->Length());
void* flattened = FlattenRunArray(runArray, &runSize);
if (flattened != NULL) {
data->AddData("_runs", B_RAW_TYPE, flattened, runSize);
free(flattened);
} else
err = B_NO_MEMORY;
FreeRunArray(runArray);
}
return err;
}
void
BTextView::AttachedToWindow()
{
BView::AttachedToWindow();
SetDrawingMode(B_OP_COPY);
Window()->SetPulseRate(500000);
fCaretVisible = false;
fCaretTime = 0;
fClickCount = 0;
fClickTime = 0;
fDragOffset = -1;
fActive = false;
_ValidateTextRect();
_AutoResize(true);
_UpdateScrollbars();
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
}
void
BTextView::DetachedFromWindow()
{
BView::DetachedFromWindow();
}
void
BTextView::Draw(BRect updateRect)
{
int32 startLine = _LineAt(BPoint(0.0, updateRect.top));
int32 endLine = _LineAt(BPoint(0.0, updateRect.bottom));
_DrawLines(startLine, endLine, -1, true);
}
void
BTextView::MouseDown(BPoint where)
{
if (!fEditable && !fSelectable)
return;
_CancelInputMethod();
if (!IsFocus())
MakeFocus();
_HideCaret();
_StopMouseTracking();
int32 modifiers = 0;
uint32 buttons = 0;
BMessage* currentMessage = Window()->CurrentMessage();
if (currentMessage != NULL) {
currentMessage->FindInt32("modifiers", &modifiers);
currentMessage->FindInt32("buttons", (int32*)&buttons);
}
if (buttons == B_SECONDARY_MOUSE_BUTTON) {
_ShowContextMenu(where);
return;
}
BMessenger messenger(this);
fTrackingMouse = new (nothrow) TextTrackState(messenger);
if (fTrackingMouse == NULL)
return;
fTrackingMouse->clickOffset = OffsetAt(where);
fTrackingMouse->shiftDown = modifiers & B_SHIFT_KEY;
fTrackingMouse->where = where;
bigtime_t clickTime = system_time();
bigtime_t clickSpeed = 0;
get_click_speed(&clickSpeed);
bool multipleClick
= clickTime - fClickTime < clickSpeed
&& fLastClickOffset == fTrackingMouse->clickOffset;
fWhere = where;
SetMouseEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS,
B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
if (fSelStart != fSelEnd && !fTrackingMouse->shiftDown && !multipleClick) {
BRegion region;
GetTextRegion(fSelStart, fSelEnd, ®ion);
if (region.Contains(where)) {
fTrackingMouse->selectionRect = region.Frame();
fClickCount = 1;
fClickTime = clickTime;
fLastClickOffset = OffsetAt(where);
return;
}
}
if (multipleClick) {
if (fClickCount > 3) {
fClickCount = 0;
fClickTime = 0;
} else {
fClickCount++;
fClickTime = clickTime;
}
} else if (!fTrackingMouse->shiftDown) {
fLastClickOffset = fCaretOffset = fTrackingMouse->clickOffset;
fClickCount = 1;
fClickTime = clickTime;
Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset);
}
if (fClickTime == clickTime) {
BMessage message(_PING_);
message.AddInt64("clickTime", clickTime);
delete fClickRunner;
BMessenger messenger(this);
fClickRunner = new (nothrow) BMessageRunner(messenger, &message,
clickSpeed, 1);
}
if (!fSelectable) {
_StopMouseTracking();
return;
}
int32 offset = fSelStart;
if (fTrackingMouse->clickOffset > fSelStart)
offset = fSelEnd;
fTrackingMouse->anchor = offset;
MouseMoved(where, B_INSIDE_VIEW, NULL);
}
void
BTextView::MouseUp(BPoint where)
{
BView::MouseUp(where);
_PerformMouseUp(where);
delete fDragRunner;
fDragRunner = NULL;
}
void
BTextView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
{
if (_PerformMouseMoved(where, code))
return;
switch (code) {
case B_ENTERED_VIEW:
case B_INSIDE_VIEW:
_TrackMouse(where, dragMessage, true);
break;
case B_EXITED_VIEW:
_DragCaret(-1);
if (Window()->IsActive() && dragMessage == NULL)
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
break;
default:
BView::MouseMoved(where, code, dragMessage);
}
}
void
BTextView::WindowActivated(bool active)
{
BView::WindowActivated(active);
if (active && IsFocus()) {
if (!fActive)
_Activate();
} else {
if (fActive)
_Deactivate();
}
BPoint where;
uint32 buttons;
GetMouse(&where, &buttons, false);
if (Bounds().Contains(where))
_TrackMouse(where, NULL);
}
void
BTextView::KeyDown(const char* bytes, int32 numBytes)
{
const char keyPressed = bytes[0];
if (!fEditable) {
switch (keyPressed) {
case B_LEFT_ARROW:
case B_RIGHT_ARROW:
case B_UP_ARROW:
case B_DOWN_ARROW:
_HandleArrowKey(keyPressed);
break;
case B_HOME:
case B_END:
case B_PAGE_UP:
case B_PAGE_DOWN:
_HandlePageKey(keyPressed);
break;
default:
BView::KeyDown(bytes, numBytes);
break;
}
return;
}
if (IsFocus())
be_app->ObscureCursor();
_HideCaret();
switch (keyPressed) {
case B_BACKSPACE:
_HandleBackspace();
break;
case B_LEFT_ARROW:
case B_RIGHT_ARROW:
case B_UP_ARROW:
case B_DOWN_ARROW:
_HandleArrowKey(keyPressed);
break;
case B_DELETE:
_HandleDelete();
break;
case B_HOME:
case B_END:
case B_PAGE_UP:
case B_PAGE_DOWN:
_HandlePageKey(keyPressed);
break;
case B_ESCAPE:
case B_INSERT:
case B_FUNCTION_KEY:
BView::KeyDown(bytes, numBytes);
break;
default:
if (fDisallowedChars
&& fDisallowedChars->HasItem(
reinterpret_cast<void*>((uint32)keyPressed))) {
beep();
return;
}
_HandleAlphaKey(bytes, numBytes);
break;
}
if (fSelStart == fSelEnd)
_ShowCaret();
}
void
BTextView::Pulse()
{
if (fActive && (fEditable || fSelectable) && fSelStart == fSelEnd) {
if (system_time() > (fCaretTime + 500000.0))
_InvertCaret();
}
}
void
BTextView::FrameResized(float newWidth, float newHeight)
{
BView::FrameResized(newWidth, newHeight);
if (fResizable)
return;
if (fWrap) {
_ResetTextRect();
} else {
float dataWidth = _TextWidth();
newWidth = std::max(dataWidth, newWidth);
BRect rect(fLayoutData->leftInset, fLayoutData->topInset,
newWidth - fLayoutData->rightInset,
newHeight - fLayoutData->bottomInset);
rect = BLayoutUtils::AlignOnRect(rect,
BSize(fTextRect.Width(), fTextRect.Height()),
BAlignment(fAlignment, B_ALIGN_TOP));
fTextRect.OffsetTo(rect.left, rect.top);
Invalidate();
_UpdateScrollbars();
}
}
void
BTextView::MakeFocus(bool focus)
{
BView::MakeFocus(focus);
if (focus && Window() != NULL && Window()->IsActive()) {
if (!fActive)
_Activate();
} else {
if (fActive)
_Deactivate();
}
}
void
BTextView::MessageReceived(BMessage* message)
{
if (message->WasDropped()) {
BPoint dropOffset;
BPoint dropPoint = message->DropPoint(&dropOffset);
ConvertFromScreen(&dropPoint);
ConvertFromScreen(&dropOffset);
if (!_MessageDropped(message, dropPoint, dropOffset))
BView::MessageReceived(message);
return;
}
switch (message->what) {
case B_CUT:
if (!IsTypingHidden())
Cut(be_clipboard);
else
beep();
break;
case B_COPY:
if (!IsTypingHidden())
Copy(be_clipboard);
else
beep();
break;
case B_PASTE:
Paste(be_clipboard);
break;
case B_UNDO:
Undo(be_clipboard);
break;
case B_SELECT_ALL:
SelectAll();
break;
case B_INPUT_METHOD_EVENT:
{
int32 opcode;
if (message->FindInt32("be:opcode", &opcode) == B_OK) {
switch (opcode) {
case B_INPUT_METHOD_STARTED:
{
BMessenger messenger;
if (message->FindMessenger("be:reply_to", &messenger)
== B_OK) {
ASSERT(fInline == NULL);
fInline = new InlineInput(messenger);
}
break;
}
case B_INPUT_METHOD_STOPPED:
delete fInline;
fInline = NULL;
break;
case B_INPUT_METHOD_CHANGED:
if (fInline != NULL)
_HandleInputMethodChanged(message);
break;
case B_INPUT_METHOD_LOCATION_REQUEST:
if (fInline != NULL)
_HandleInputMethodLocationRequest();
break;
default:
break;
}
}
break;
}
case B_SET_PROPERTY:
case B_GET_PROPERTY:
case B_COUNT_PROPERTIES:
{
BPropertyInfo propInfo(sPropertyList);
BMessage specifier;
const char* property;
if (message->GetCurrentSpecifier(NULL, &specifier) < B_OK
|| specifier.FindString("property", &property) < B_OK) {
BView::MessageReceived(message);
return;
}
if (propInfo.FindMatch(message, 0, &specifier, specifier.what,
property) < B_OK) {
BView::MessageReceived(message);
break;
}
BMessage reply;
bool handled = false;
switch(message->what) {
case B_GET_PROPERTY:
handled = _GetProperty(message, &specifier, property,
&reply);
break;
case B_SET_PROPERTY:
handled = _SetProperty(message, &specifier, property,
&reply);
break;
case B_COUNT_PROPERTIES:
handled = _CountProperties(message, &specifier,
property, &reply);
break;
default:
break;
}
if (handled)
message->SendReply(&reply);
else
BView::MessageReceived(message);
break;
}
case _PING_:
{
if (message->HasInt64("clickTime")) {
bigtime_t clickTime;
message->FindInt64("clickTime", &clickTime);
if (clickTime == fClickTime) {
if (fSelStart != fSelEnd && fSelectable) {
BRegion region;
GetTextRegion(fSelStart, fSelEnd, ®ion);
if (region.Contains(fWhere))
_TrackMouse(fWhere, NULL);
}
delete fClickRunner;
fClickRunner = NULL;
}
} else if (fTrackingMouse) {
fTrackingMouse->SimulateMouseMovement(this);
_PerformAutoScrolling();
}
break;
}
case _DISPOSE_DRAG_:
if (fEditable)
_TrackDrag(fWhere);
break;
case kMsgNavigateArrow:
{
int32 key = message->GetInt32("key", 0);
int32 modifiers = message->GetInt32("modifiers", 0);
_HandleArrowKey(key, modifiers);
break;
}
case kMsgNavigatePage:
{
int32 key = message->GetInt32("key", 0);
int32 modifiers = message->GetInt32("modifiers", 0);
_HandlePageKey(key, modifiers);
break;
}
case kMsgRemoveWord:
{
int32 key = message->GetInt32("key", 0);
int32 modifiers = message->GetInt32("modifiers", 0);
if (key == B_DELETE)
_HandleDelete(modifiers);
else if (key == B_BACKSPACE)
_HandleBackspace(modifiers);
break;
}
default:
BView::MessageReceived(message);
break;
}
}
BHandler*
BTextView::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
int32 what, const char* property)
{
BPropertyInfo propInfo(sPropertyList);
BHandler* target = this;
if (propInfo.FindMatch(message, index, specifier, what, property) < B_OK) {
target = BView::ResolveSpecifier(message, index, specifier, what,
property);
}
return target;
}
status_t
BTextView::GetSupportedSuites(BMessage* data)
{
if (data == NULL)
return B_BAD_VALUE;
status_t err = data->AddString("suites", "suite/vnd.Be-text-view");
if (err != B_OK)
return err;
BPropertyInfo prop_info(sPropertyList);
err = data->AddFlat("messages", &prop_info);
if (err != B_OK)
return err;
return BView::GetSupportedSuites(data);
}
status_t
BTextView::Perform(perform_code code, void* _data)
{
switch (code) {
case PERFORM_CODE_MIN_SIZE:
((perform_data_min_size*)_data)->return_value
= BTextView::MinSize();
return B_OK;
case PERFORM_CODE_MAX_SIZE:
((perform_data_max_size*)_data)->return_value
= BTextView::MaxSize();
return B_OK;
case PERFORM_CODE_PREFERRED_SIZE:
((perform_data_preferred_size*)_data)->return_value
= BTextView::PreferredSize();
return B_OK;
case PERFORM_CODE_LAYOUT_ALIGNMENT:
((perform_data_layout_alignment*)_data)->return_value
= BTextView::LayoutAlignment();
return B_OK;
case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
((perform_data_has_height_for_width*)_data)->return_value
= BTextView::HasHeightForWidth();
return B_OK;
case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
{
perform_data_get_height_for_width* data
= (perform_data_get_height_for_width*)_data;
BTextView::GetHeightForWidth(data->width, &data->min, &data->max,
&data->preferred);
return B_OK;
}
case PERFORM_CODE_SET_LAYOUT:
{
perform_data_set_layout* data = (perform_data_set_layout*)_data;
BTextView::SetLayout(data->layout);
return B_OK;
}
case PERFORM_CODE_LAYOUT_INVALIDATED:
{
perform_data_layout_invalidated* data
= (perform_data_layout_invalidated*)_data;
BTextView::LayoutInvalidated(data->descendants);
return B_OK;
}
case PERFORM_CODE_DO_LAYOUT:
{
BTextView::DoLayout();
return B_OK;
}
}
return BView::Perform(code, _data);
}
void
BTextView::SetText(const char* text, const text_run_array* runs)
{
SetText(text, text ? strlen(text) : 0, runs);
}
void
BTextView::SetText(const char* text, int32 length, const text_run_array* runs)
{
_CancelInputMethod();
if (fActive) {
if (fSelStart != fSelEnd) {
if (fSelectable)
Highlight(fSelStart, fSelEnd);
} else
_HideCaret();
}
if (fText->Length() > 0)
DeleteText(0, fText->Length());
if (text != NULL && length > 0)
InsertText(text, length, 0, runs);
if (!Bounds().IsValid()) {
ResizeTo(LineWidth(0) - 1, LineHeight(0));
fTextRect = Bounds();
_ValidateTextRect();
_UpdateInsets(fTextRect);
}
_Refresh(0, length);
fCaretOffset = fSelStart = fSelEnd = 0;
_ShowCaret();
}
void
BTextView::SetText(BFile* file, int32 offset, int32 length,
const text_run_array* runs)
{
CALLED();
_CancelInputMethod();
if (file == NULL)
return;
if (fText->Length() > 0)
DeleteText(0, fText->Length());
if (!fText->InsertText(file, offset, length, 0))
return;
fLines->BumpOffset(length, _LineAt(offset) + 1);
fStyles->BumpOffset(length, fStyles->OffsetToRun(offset - 1) + 1);
if (fStylable && runs != NULL)
SetRunArray(offset, offset + length, runs);
else {
_ApplyStyleRange(offset, offset + length);
}
_Refresh(0, length);
fCaretOffset = fSelStart = fSelEnd = 0;
ScrollToOffset(fSelStart);
_ShowCaret();
}
void
BTextView::Insert(const char* text, const text_run_array* runs)
{
if (text != NULL)
_DoInsertText(text, strlen(text), fSelStart, runs);
}
void
BTextView::Insert(const char* text, int32 length, const text_run_array* runs)
{
if (text != NULL && length > 0)
_DoInsertText(text, strnlen(text, length), fSelStart, runs);
}
void
BTextView::Insert(int32 offset, const char* text, int32 length,
const text_run_array* runs)
{
if (offset < 0)
offset = 0;
else if (offset > fText->Length())
offset = fText->Length();
if (text != NULL && length > 0)
_DoInsertText(text, strnlen(text, length), offset, runs);
}
void
BTextView::Delete()
{
Delete(fSelStart, fSelEnd);
}
void
BTextView::Delete(int32 startOffset, int32 endOffset)
{
CALLED();
if (startOffset < 0)
startOffset = 0;
else if (startOffset > fText->Length())
startOffset = fText->Length();
if (endOffset < 0)
endOffset = 0;
else if (endOffset > fText->Length())
endOffset = fText->Length();
if (startOffset == endOffset)
return;
if (fActive) {
if (fSelStart != fSelEnd) {
if (fSelectable)
Highlight(fSelStart, fSelEnd);
} else
_HideCaret();
}
DeleteText(startOffset, endOffset);
if (fCaretOffset >= endOffset)
fCaretOffset -= (endOffset - startOffset);
else if (fCaretOffset >= startOffset && fCaretOffset < endOffset)
fCaretOffset = startOffset;
fSelEnd = fSelStart = fCaretOffset;
_Refresh(startOffset, endOffset, fCaretOffset);
_ShowCaret();
}
const char*
BTextView::Text() const
{
return fText->RealText();
}
int32
BTextView::TextLength() const
{
return fText->Length();
}
void
BTextView::GetText(int32 offset, int32 length, char* buffer) const
{
if (buffer != NULL)
fText->GetString(offset, length, buffer);
}
uchar
BTextView::ByteAt(int32 offset) const
{
if (offset < 0 || offset >= fText->Length())
return '\0';
return fText->RealCharAt(offset);
}
int32
BTextView::CountLines() const
{
return fLines->NumLines();
}
int32
BTextView::CurrentLine() const
{
return LineAt(fSelStart);
}
void
BTextView::GoToLine(int32 index)
{
_CancelInputMethod();
_HideCaret();
fSelStart = fSelEnd = fCaretOffset = OffsetAt(index);
_ShowCaret();
}
void
BTextView::Cut(BClipboard* clipboard)
{
_CancelInputMethod();
if (!fEditable)
return;
if (fUndo) {
delete fUndo;
fUndo = new CutUndoBuffer(this);
}
Copy(clipboard);
Delete();
}
void
BTextView::Copy(BClipboard* clipboard)
{
_CancelInputMethod();
if (clipboard->Lock()) {
clipboard->Clear();
BMessage* clip = clipboard->Data();
if (clip != NULL) {
int32 numBytes = fSelEnd - fSelStart;
const char* text = fText->GetString(fSelStart, &numBytes);
clip->AddData("text/plain", B_MIME_TYPE, text, numBytes);
int32 size;
if (fStylable) {
text_run_array* runArray = RunArray(fSelStart, fSelEnd, &size);
clip->AddData("application/x-vnd.Be-text_run_array",
B_MIME_TYPE, runArray, size);
FreeRunArray(runArray);
}
clipboard->Commit();
}
clipboard->Unlock();
}
}
void
BTextView::Paste(BClipboard* clipboard)
{
CALLED();
_CancelInputMethod();
if (!fEditable || !clipboard->Lock())
return;
BMessage* clip = 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) {
text_run_array* runArray = NULL;
ssize_t runLength = 0;
if (fStylable) {
clip->FindData("application/x-vnd.Be-text_run_array",
B_MIME_TYPE, (const void**)&runArray, &runLength);
}
_FilterDisallowedChars((char*)text, length, runArray);
if (length < 1) {
beep();
clipboard->Unlock();
return;
}
if (fUndo) {
delete fUndo;
fUndo = new PasteUndoBuffer(this, text, length, runArray,
runLength);
}
if (fSelStart != fSelEnd)
Delete();
Insert(text, length, runArray);
ScrollToOffset(fSelEnd);
}
}
clipboard->Unlock();
}
void
BTextView::Clear()
{
if (fUndo) {
delete fUndo;
fUndo = new ClearUndoBuffer(this);
}
Delete();
}
bool
BTextView::AcceptsPaste(BClipboard* clipboard)
{
bool result = false;
if (fEditable && clipboard && clipboard->Lock()) {
BMessage* data = clipboard->Data();
result = data && data->HasData("text/plain", B_MIME_TYPE);
clipboard->Unlock();
}
return result;
}
bool
BTextView::AcceptsDrop(const BMessage* message)
{
return fEditable && message
&& message->HasData("text/plain", B_MIME_TYPE);
}
void
BTextView::Select(int32 startOffset, int32 endOffset)
{
CALLED();
if (!fSelectable)
return;
_CancelInputMethod();
if (startOffset < 0)
startOffset = 0;
else if (startOffset > fText->Length())
startOffset = fText->Length();
if (endOffset < 0)
endOffset = 0;
else if (endOffset > fText->Length())
endOffset = fText->Length();
if (startOffset > endOffset)
return;
if (startOffset == fSelStart && endOffset == fSelEnd)
return;
fStyles->InvalidateNullStyle();
_HideCaret();
if (startOffset == endOffset) {
if (fSelStart != fSelEnd) {
if (fActive)
Highlight(fSelStart, fSelEnd);
}
fSelStart = fSelEnd = fCaretOffset = startOffset;
_ShowCaret();
} else {
if (fActive) {
long start, end;
if (startOffset != fSelStart) {
if (startOffset > fSelStart) {
start = fSelStart;
end = startOffset;
} else {
start = startOffset;
end = fSelStart;
}
Highlight(start, end);
}
if (endOffset != fSelEnd) {
if (endOffset > fSelEnd) {
start = fSelEnd;
end = endOffset;
} else {
start = endOffset;
end = fSelEnd;
}
Highlight(start, end);
}
}
fSelStart = startOffset;
fSelEnd = endOffset;
}
}
void
BTextView::SelectAll()
{
Select(0, fText->Length());
}
void
BTextView::GetSelection(int32* _start, int32* _end) const
{
int32 start = 0;
int32 end = 0;
if (fSelectable) {
start = fSelStart;
end = fSelEnd;
}
if (_start)
*_start = start;
if (_end)
*_end = end;
}
void
BTextView::AdoptSystemColors()
{
if (IsEditable())
SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
else
SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR, _UneditableTint());
SetLowUIColor(ViewUIColor());
SetHighUIColor(B_DOCUMENT_TEXT_COLOR);
}
bool
BTextView::HasSystemColors() const
{
float tint = B_NO_TINT;
float uneditableTint = _UneditableTint();
return ViewUIColor(&tint) == B_DOCUMENT_BACKGROUND_COLOR
&& (tint == B_NO_TINT || tint == uneditableTint)
&& LowUIColor(&tint) == B_DOCUMENT_BACKGROUND_COLOR
&& (tint == B_NO_TINT || tint == uneditableTint)
&& HighUIColor(&tint) == B_DOCUMENT_TEXT_COLOR && tint == B_NO_TINT;
}
void
BTextView::SetFontAndColor(const BFont* font, uint32 mode, const rgb_color* color)
{
SetFontAndColor(fSelStart, fSelEnd, font, mode, color);
}
void
BTextView::SetFontAndColor(int32 startOffset, int32 endOffset,
const BFont* font, uint32 mode, const rgb_color* color)
{
CALLED();
_HideCaret();
const int32 textLength = fText->Length();
if (!fStylable) {
startOffset = 0;
endOffset = textLength;
} else {
if (startOffset < 0)
startOffset = 0;
else if (startOffset > textLength)
startOffset = textLength;
if (endOffset < 0)
endOffset = 0;
else if (endOffset > textLength)
endOffset = textLength;
}
fStyles->InvalidateNullStyle();
_ApplyStyleRange(startOffset, endOffset, mode, font, color);
if ((mode & (B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE)) != 0) {
InvalidateLayout();
_Refresh(startOffset, endOffset);
} else {
_RequestDrawLines(_LineAt(startOffset), _LineAt(endOffset));
}
_ShowCaret();
}
void
BTextView::GetFontAndColor(int32 offset, BFont* _font,
rgb_color* _color) const
{
fStyles->GetStyle(offset, _font, _color);
}
void
BTextView::GetFontAndColor(BFont* _font, uint32* _mode,
rgb_color* _color, bool* _sameColor) const
{
fStyles->ContinuousGetStyle(_font, _mode, _color, _sameColor,
fSelStart, fSelEnd);
}
void
BTextView::SetRunArray(int32 startOffset, int32 endOffset,
const text_run_array* runs)
{
CALLED();
_CancelInputMethod();
text_run_array oneRun;
if (!fStylable) {
if (runs->count == 0)
return;
startOffset = 0;
endOffset = fText->Length();
oneRun.count = 1;
oneRun.runs[0] = runs->runs[0];
oneRun.runs[0].offset = 0;
runs = &oneRun;
} else {
if (startOffset < 0)
startOffset = 0;
else if (startOffset > fText->Length())
startOffset = fText->Length();
if (endOffset < 0)
endOffset = 0;
else if (endOffset > fText->Length())
endOffset = fText->Length();
}
_SetRunArray(startOffset, endOffset, runs);
_Refresh(startOffset, endOffset);
}
text_run_array*
BTextView::RunArray(int32 startOffset, int32 endOffset, int32* _size) const
{
if (startOffset < 0)
startOffset = 0;
else if (startOffset > fText->Length())
startOffset = fText->Length();
if (endOffset < 0)
endOffset = 0;
else if (endOffset > fText->Length())
endOffset = fText->Length();
STEStyleRange* styleRange
= fStyles->GetStyleRange(startOffset, endOffset - 1);
if (styleRange == NULL)
return NULL;
text_run_array* runArray = AllocRunArray(styleRange->count, _size);
if (runArray != NULL) {
for (int32 i = 0; i < runArray->count; i++) {
runArray->runs[i].offset = styleRange->runs[i].offset;
runArray->runs[i].font = styleRange->runs[i].style.font;
runArray->runs[i].color = styleRange->runs[i].style.color;
}
}
free(styleRange);
return runArray;
}
int32
BTextView::LineAt(int32 offset) const
{
if (offset < 0)
offset = 0;
else if (offset > fText->Length())
offset = fText->Length();
int32 lineNum = _LineAt(offset);
if (_IsOnEmptyLastLine(offset))
lineNum++;
return lineNum;
}
int32
BTextView::LineAt(BPoint point) const
{
int32 lineNum = _LineAt(point);
if ((*fLines)[lineNum + 1]->origin <= point.y - fTextRect.top)
lineNum++;
return lineNum;
}
BPoint
BTextView::PointAt(int32 offset, float* _height) const
{
if (offset < 0)
offset = 0;
else if (offset > fText->Length())
offset = fText->Length();
int32 lineNum = _LineAt(offset);
STELine* line = (*fLines)[lineNum];
float height = 0;
BPoint result;
result.x = 0.0;
result.y = line->origin + fTextRect.top;
bool onEmptyLastLine = _IsOnEmptyLastLine(offset);
if (fStyles->NumRuns() == 0) {
fStyles->SyncNullStyle(0);
height = _NullStyleHeight();
} else {
height = (line + 1)->origin - line->origin;
if (onEmptyLastLine) {
result.y += height;
fStyles->SyncNullStyle(offset);
height = _NullStyleHeight();
} else {
int32 length = offset - line->offset;
result.x += _TabExpandedStyledWidth(line->offset, length);
}
}
if (fAlignment != B_ALIGN_LEFT) {
float lineWidth = onEmptyLastLine ? 0.0 : LineWidth(lineNum);
float alignmentOffset = fTextRect.Width() + 1 - lineWidth;
if (fAlignment == B_ALIGN_CENTER)
alignmentOffset = floorf(alignmentOffset / 2);
result.x += alignmentOffset;
}
result.x += fTextRect.left;
result.x = lroundf(result.x);
result.y = lroundf(result.y);
if (_height != NULL)
*_height = height;
return result;
}
int32
BTextView::OffsetAt(BPoint point) const
{
const int32 textLength = fText->Length();
if (point.y >= fTextRect.bottom)
return textLength;
else if (point.y < fTextRect.top)
return 0;
int32 lineNum = _LineAt(point);
STELine* line = (*fLines)[lineNum];
#define COMPILE_PROBABLY_BAD_CODE 1
#if COMPILE_PROBABLY_BAD_CODE
if (lineNum == (fLines->NumLines() - 1)) {
if (point.y >= ((line + 1)->origin + fTextRect.top))
return textLength;
}
#endif
if (fAlignment != B_ALIGN_LEFT) {
float alignmentOffset = fTextRect.Width() + 1 - LineWidth(lineNum);
if (fAlignment == B_ALIGN_CENTER)
alignmentOffset = floorf(alignmentOffset / 2);
point.x -= alignmentOffset;
}
point.x -= fTextRect.left;
point.x = std::max(point.x, 0.0f);
int32 offset = line->offset;
const int32 limit = (line + 1)->offset;
float location = 0;
do {
const int32 nextInitial = _NextInitialByte(offset);
const int32 saveOffset = offset;
float width = 0;
if (ByteAt(offset) == B_TAB)
width = _ActualTabWidth(location);
else
width = _StyledWidth(saveOffset, nextInitial - saveOffset);
if (location + width > point.x) {
if (fabs(location + width - point.x) < fabs(location - point.x))
offset = nextInitial;
break;
}
location += width;
offset = nextInitial;
} while (offset < limit);
if (offset == (line + 1)->offset) {
if (ByteAt(offset - 1) == B_ENTER)
return --offset;
if (offset != textLength && ByteAt(offset - 1) == B_SPACE)
return --offset;
}
return offset;
}
int32
BTextView::OffsetAt(int32 line) const
{
if (line < 0)
return 0;
if (line > fLines->NumLines())
return fText->Length();
return (*fLines)[line]->offset;
}
void
BTextView::FindWord(int32 offset, int32* _fromOffset, int32* _toOffset)
{
if (offset < 0) {
if (_fromOffset)
*_fromOffset = 0;
if (_toOffset)
*_toOffset = 0;
return;
}
if (offset > fText->Length()) {
if (_fromOffset)
*_fromOffset = fText->Length();
if (_toOffset)
*_toOffset = fText->Length();
return;
}
if (_fromOffset)
*_fromOffset = _PreviousWordBoundary(offset);
if (_toOffset)
*_toOffset = _NextWordBoundary(offset);
}
bool
BTextView::CanEndLine(int32 offset)
{
if (offset < 0 || offset > fText->Length())
return false;
uint32 classification = _CharClassification(offset);
if (classification == CHAR_CLASS_END_OF_TEXT || ByteAt(offset) == B_ENTER)
return true;
uint32 nextClassification = _CharClassification(offset + 1);
if (nextClassification == CHAR_CLASS_END_OF_TEXT)
return true;
if (classification == CHAR_CLASS_DEFAULT
&& nextClassification == CHAR_CLASS_PUNCTUATION) {
return false;
}
if ((classification == CHAR_CLASS_WHITESPACE
&& nextClassification != CHAR_CLASS_WHITESPACE)
|| (classification != CHAR_CLASS_WHITESPACE
&& nextClassification == CHAR_CLASS_WHITESPACE)) {
return true;
}
if (classification == CHAR_CLASS_WHITESPACE
&& (nextClassification != CHAR_CLASS_WHITESPACE
|| ByteAt(offset + 1) == B_ENTER)) {
return true;
}
if (classification == CHAR_CLASS_PUNCTUATION
&& nextClassification != CHAR_CLASS_PUNCTUATION
&& nextClassification != CHAR_CLASS_PARENS_CLOSE
&& nextClassification != CHAR_CLASS_QUOTE) {
return true;
}
if ((classification == CHAR_CLASS_QUOTE
|| classification == CHAR_CLASS_GRAPHICAL
|| classification == CHAR_CLASS_PARENS_CLOSE)
&& nextClassification == CHAR_CLASS_WHITESPACE) {
return true;
}
return false;
}
float
BTextView::LineWidth(int32 lineNumber) const
{
if (lineNumber < 0 || lineNumber >= fLines->NumLines())
return 0;
STELine* line = (*fLines)[lineNumber];
int32 length = (line + 1)->offset - line->offset;
if (ByteAt((line + 1)->offset - 1) == B_ENTER)
length--;
return _TabExpandedStyledWidth(line->offset, length);
}
float
BTextView::LineHeight(int32 lineNumber) const
{
float lineHeight = TextHeight(lineNumber, lineNumber);
if (lineHeight == 0.0) {
const BFont* font;
fStyles->GetNullStyle(&font, NULL);
if (font == NULL)
font = be_plain_font;
font_height fontHeight;
font->GetHeight(&fontHeight);
lineHeight = ceilf(fontHeight.ascent + fontHeight.descent) + 1;
}
return lineHeight;
}
float
BTextView::TextHeight(int32 startLine, int32 endLine) const
{
const int32 numLines = fLines->NumLines();
if (startLine < 0)
startLine = 0;
else if (startLine > numLines - 1)
startLine = numLines - 1;
if (endLine < 0)
endLine = 0;
else if (endLine > numLines - 1)
endLine = numLines - 1;
float height = (*fLines)[endLine + 1]->origin
- (*fLines)[startLine]->origin;
if (startLine != endLine && endLine == numLines - 1
&& fText->RealCharAt(fText->Length() - 1) == B_ENTER) {
height += (*fLines)[endLine + 1]->origin - (*fLines)[endLine]->origin;
}
return ceilf(height);
}
void
BTextView::GetTextRegion(int32 startOffset, int32 endOffset,
BRegion* outRegion) const
{
if (!outRegion)
return;
outRegion->MakeEmpty();
if (startOffset < 0)
startOffset = 0;
else if (startOffset > fText->Length())
startOffset = fText->Length();
if (endOffset < 0)
endOffset = 0;
else if (endOffset > fText->Length())
endOffset = fText->Length();
if (startOffset >= endOffset)
return;
float startLineHeight = 0.0;
float endLineHeight = 0.0;
BPoint startPt = PointAt(startOffset, &startLineHeight);
BPoint endPt = PointAt(endOffset, &endLineHeight);
startLineHeight = ceilf(startLineHeight);
endLineHeight = ceilf(endLineHeight);
BRect selRect;
const BRect bounds(Bounds());
if (startPt.y == endPt.y) {
selRect.left = startPt.x;
selRect.top = startPt.y;
selRect.right = endPt.x - 1;
selRect.bottom = endPt.y + endLineHeight - 1;
outRegion->Include(selRect);
} else {
selRect.left = startPt.x;
selRect.top = startPt.y;
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) {
selRect.left = std::min(fTextRect.left,
bounds.left + fLayoutData->leftInset);
selRect.top = startPt.y + startLineHeight;
selRect.right = std::max(fTextRect.right,
bounds.right - fLayoutData->rightInset);
selRect.bottom = endPt.y - 1;
outRegion->Include(selRect);
}
selRect.left = std::min(fTextRect.left,
bounds.left + fLayoutData->leftInset);
selRect.top = endPt.y;
selRect.right = endPt.x - 1;
selRect.bottom = endPt.y + endLineHeight - 1;
outRegion->Include(selRect);
}
}
void
BTextView::ScrollToOffset(int32 offset)
{
BRect bounds = Bounds();
float lineHeight = 0.0;
BPoint point = PointAt(offset, &lineHeight);
BPoint scrollBy(B_ORIGIN);
if (point.x < bounds.left)
scrollBy.x = point.x - bounds.right;
else if (point.x > bounds.right)
scrollBy.x = point.x - bounds.left;
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;
}
if (CountLines() > 1) {
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(scrollBy.x, scrollBy.y);
if (CountLines() > 1 && !fWrap)
FrameResized(Bounds().Width(), Bounds().Height());
}
void
BTextView::ScrollToSelection()
{
ScrollToOffset(fSelStart);
}
void
BTextView::Highlight(int32 startOffset, int32 endOffset)
{
if (startOffset < 0)
startOffset = 0;
else if (startOffset > fText->Length())
startOffset = fText->Length();
if (endOffset < 0)
endOffset = 0;
else if (endOffset > fText->Length())
endOffset = fText->Length();
if (startOffset >= endOffset)
return;
BRegion selRegion;
GetTextRegion(startOffset, endOffset, &selRegion);
SetDrawingMode(B_OP_INVERT);
FillRegion(&selRegion, B_SOLID_HIGH);
SetDrawingMode(B_OP_COPY);
}
void
BTextView::SetTextRect(BRect rect)
{
if (rect == fTextRect)
return;
if (!fWrap) {
rect.right = Bounds().right;
rect.bottom = Bounds().bottom;
}
_UpdateInsets(rect);
fTextRect = rect;
_ResetTextRect();
}
BRect
BTextView::TextRect() const
{
return fTextRect;
}
void
BTextView::_ResetTextRect()
{
BRect oldTextRect(fTextRect);
fTextRect = Bounds().OffsetToCopy(B_ORIGIN);
fTextRect.left += fLayoutData->leftInset;
fTextRect.top += fLayoutData->topInset;
fTextRect.right -= fLayoutData->rightInset;
fTextRect.bottom -= fLayoutData->bottomInset;
_Refresh(0, fText->Length());
BRegion invalid(oldTextRect | fTextRect);
invalid.Exclude(fTextRect);
Invalidate(&invalid);
}
void
BTextView::SetInsets(float left, float top, float right, float bottom)
{
if (fLayoutData->leftInset == left
&& fLayoutData->topInset == top
&& fLayoutData->rightInset == right
&& fLayoutData->bottomInset == bottom)
return;
fLayoutData->leftInset = left;
fLayoutData->topInset = top;
fLayoutData->rightInset = right;
fLayoutData->bottomInset = bottom;
fLayoutData->overridden = true;
InvalidateLayout();
Invalidate();
}
void
BTextView::GetInsets(float* _left, float* _top, float* _right,
float* _bottom) const
{
if (_left)
*_left = fLayoutData->leftInset;
if (_top)
*_top = fLayoutData->topInset;
if (_right)
*_right = fLayoutData->rightInset;
if (_bottom)
*_bottom = fLayoutData->bottomInset;
}
void
BTextView::SetStylable(bool stylable)
{
fStylable = stylable;
}
bool
BTextView::IsStylable() const
{
return fStylable;
}
void
BTextView::SetTabWidth(float width)
{
if (width == fTabWidth)
return;
fTabWidth = width;
if (Window() != NULL)
_Refresh(0, fText->Length());
}
float
BTextView::TabWidth() const
{
return fTabWidth;
}
void
BTextView::MakeSelectable(bool selectable)
{
if (selectable == fSelectable)
return;
fSelectable = selectable;
if (fActive && fSelStart != fSelEnd && Window() != NULL)
Highlight(fSelStart, fSelEnd);
}
bool
BTextView::IsSelectable() const
{
return fSelectable;
}
void
BTextView::MakeEditable(bool editable)
{
if (editable == fEditable)
return;
fEditable = editable;
if (HasSystemColors())
AdoptSystemColors();
if (fEditable)
fStyles->InvalidateNullStyle();
if (Window() != NULL && fActive) {
if (!fEditable) {
if (!fSelectable)
_HideCaret();
_CancelInputMethod();
}
}
}
bool
BTextView::IsEditable() const
{
return fEditable;
}
void
BTextView::SetWordWrap(bool wrap)
{
if (wrap == fWrap)
return;
bool updateOnScreen = fActive && Window() != NULL;
if (updateOnScreen) {
if (fSelStart != fSelEnd) {
if (fSelectable)
Highlight(fSelStart, fSelEnd);
} else
_HideCaret();
}
BRect savedBounds = Bounds();
fWrap = wrap;
if (wrap)
_ResetTextRect();
else
_Refresh(0, fText->Length());
if (fEditable || fSelectable)
ScrollToOffset(fCaretOffset);
if (Bounds() != savedBounds)
FrameResized(Bounds().Width(), Bounds().Height());
if (updateOnScreen) {
if (fSelStart != fSelEnd) {
if (fSelectable)
Highlight(fSelStart, fSelEnd);
} else
_ShowCaret();
}
}
bool
BTextView::DoesWordWrap() const
{
return fWrap;
}
void
BTextView::SetMaxBytes(int32 max)
{
const int32 textLength = fText->Length();
fMaxBytes = max;
if (fMaxBytes < textLength) {
int32 offset = fMaxBytes;
const int32 previousInitial = _PreviousInitialByte(offset);
if (_NextInitialByte(previousInitial) != offset)
offset = previousInitial;
Delete(offset, textLength);
}
}
int32
BTextView::MaxBytes() const
{
return fMaxBytes;
}
void
BTextView::DisallowChar(uint32 character)
{
if (fDisallowedChars == NULL)
fDisallowedChars = new BList;
if (!fDisallowedChars->HasItem(reinterpret_cast<void*>(character)))
fDisallowedChars->AddItem(reinterpret_cast<void*>(character));
}
void
BTextView::AllowChar(uint32 character)
{
if (fDisallowedChars != NULL)
fDisallowedChars->RemoveItem(reinterpret_cast<void*>(character));
}
void
BTextView::SetAlignment(alignment align)
{
if (fAlignment != align &&
(align == B_ALIGN_LEFT ||
align == B_ALIGN_RIGHT ||
align == B_ALIGN_CENTER)) {
fAlignment = align;
if (Window() != NULL) {
FrameResized(Bounds().Width(), Bounds().Height());
Invalidate();
}
}
}
alignment
BTextView::Alignment() const
{
return fAlignment;
}
void
BTextView::SetAutoindent(bool state)
{
fAutoindent = state;
}
bool
BTextView::DoesAutoindent() const
{
return fAutoindent;
}
void
BTextView::SetColorSpace(color_space colors)
{
if (colors != fColorSpace && fOffscreen) {
fColorSpace = colors;
_DeleteOffscreen();
_NewOffscreen();
}
}
color_space
BTextView::ColorSpace() const
{
return fColorSpace;
}
void
BTextView::MakeResizable(bool resize, BView* resizeView)
{
if (resize) {
fResizable = true;
fContainerView = resizeView;
if (fWrap) {
fWrap = false;
if (fActive && Window() != NULL) {
if (fSelStart != fSelEnd) {
if (fSelectable)
Highlight(fSelStart, fSelEnd);
} else
_HideCaret();
}
}
fLayoutData->rightInset = fLayoutData->leftInset;
} else {
fResizable = false;
fContainerView = NULL;
if (fOffscreen)
_DeleteOffscreen();
_NewOffscreen();
}
_Refresh(0, fText->Length());
}
bool
BTextView::IsResizable() const
{
return fResizable;
}
void
BTextView::SetDoesUndo(bool undo)
{
if (undo && fUndo == NULL)
fUndo = new UndoBuffer(this, B_UNDO_UNAVAILABLE);
else if (!undo && fUndo != NULL) {
delete fUndo;
fUndo = NULL;
}
}
bool
BTextView::DoesUndo() const
{
return fUndo != NULL;
}
void
BTextView::HideTyping(bool enabled)
{
if (enabled)
Delete(0, fText->Length());
fText->SetPasswordMode(enabled);
}
bool
BTextView::IsTypingHidden() const
{
return fText->PasswordMode();
}
void
BTextView::ResizeToPreferred()
{
BView::ResizeToPreferred();
}
void
BTextView::GetPreferredSize(float* _width, float* _height)
{
CALLED();
_ValidateLayoutData();
if (_width) {
float width = Bounds().Width();
if (width < fLayoutData->min.width
|| (Flags() & B_SUPPORTS_LAYOUT) != 0) {
width = fLayoutData->min.width;
}
*_width = width;
}
if (_height) {
float height = Bounds().Height();
if (height < fLayoutData->min.height
|| (Flags() & B_SUPPORTS_LAYOUT) != 0) {
height = fLayoutData->min.height;
}
*_height = height;
}
}
BSize
BTextView::MinSize()
{
CALLED();
_ValidateLayoutData();
return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
}
BSize
BTextView::MaxSize()
{
CALLED();
return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
}
BSize
BTextView::PreferredSize()
{
CALLED();
_ValidateLayoutData();
return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
fLayoutData->preferred);
}
bool
BTextView::HasHeightForWidth()
{
if (IsEditable())
return BView::HasHeightForWidth();
return true;
}
void
BTextView::GetHeightForWidth(float width, float* min, float* max,
float* preferred)
{
if (IsEditable()) {
BView::GetHeightForWidth(width, min, max, preferred);
return;
}
BRect saveTextRect = fTextRect;
fTextRect.right = fTextRect.left + width;
if (fLayoutData->overridden) {
fTextRect.left += fLayoutData->leftInset;
fTextRect.right -= fLayoutData->rightInset;
}
int32 fromLine = _LineAt(0);
int32 toLine = _LineAt(fText->Length());
_RecalculateLineBreaks(&fromLine, &toLine);
if (fLayoutData->overridden) {
fTextRect.top -= fLayoutData->topInset;
fTextRect.bottom += fLayoutData->bottomInset;
}
if (min != NULL)
*min = fTextRect.Height();
if (max != NULL)
*max = B_SIZE_UNLIMITED;
if (preferred != NULL)
*preferred = fTextRect.Height();
fTextRect = saveTextRect;
}
void
BTextView::LayoutInvalidated(bool descendants)
{
CALLED();
fLayoutData->valid = false;
}
void
BTextView::DoLayout()
{
if (!(Flags() & B_SUPPORTS_LAYOUT))
return;
CALLED();
if (GetLayout()) {
BView::DoLayout();
return;
}
_ValidateLayoutData();
BSize size(Bounds().Size());
if (size.width < fLayoutData->min.width)
size.width = fLayoutData->min.width;
if (size.height < fLayoutData->min.height)
size.height = fLayoutData->min.height;
_ResetTextRect();
}
void
BTextView::_ValidateLayoutData()
{
if (fLayoutData->valid)
return;
CALLED();
float lineHeight = ceilf(LineHeight(0));
TRACE("line height: %.2f\n", lineHeight);
BSize min(lineHeight * 3, lineHeight);
min.width += fLayoutData->leftInset + fLayoutData->rightInset;
min.height += fLayoutData->topInset + fLayoutData->bottomInset;
fLayoutData->min = min;
fLayoutData->preferred.height = _TextHeight();
if (fWrap)
fLayoutData->preferred.width = min.width + 5 * lineHeight;
else {
float maxWidth = fLines->MaxWidth() + fLayoutData->leftInset + fLayoutData->rightInset;
if (maxWidth < min.width)
maxWidth = min.width;
fLayoutData->preferred.width = maxWidth;
fLayoutData->min = fLayoutData->preferred;
}
fLayoutData->valid = true;
ResetLayoutInvalidation();
TRACE("width: %.2f, height: %.2f\n", min.width, min.height);
}
void
BTextView::AllAttached()
{
BView::AllAttached();
}
void
BTextView::AllDetached()
{
BView::AllDetached();
}
text_run_array*
BTextView::AllocRunArray(int32 entryCount, int32* outSize)
{
int32 size = sizeof(text_run_array) + (entryCount - 1) * sizeof(text_run);
text_run_array* runArray = (text_run_array*)calloc(size, 1);
if (runArray == NULL) {
if (outSize != NULL)
*outSize = 0;
return NULL;
}
runArray->count = entryCount;
for (int32 i = 0; i < runArray->count; i++)
new (&runArray->runs[i].font) BFont;
if (outSize != NULL)
*outSize = size;
return runArray;
}
text_run_array*
BTextView::CopyRunArray(const text_run_array* orig, int32 countDelta)
{
text_run_array* copy = AllocRunArray(countDelta, NULL);
if (copy != NULL) {
for (int32 i = 0; i < countDelta; i++) {
copy->runs[i].offset = orig->runs[i].offset;
copy->runs[i].font = orig->runs[i].font;
copy->runs[i].color = orig->runs[i].color;
}
}
return copy;
}
void
BTextView::FreeRunArray(text_run_array* array)
{
if (array == NULL)
return;
for (int32 i = 0; i < array->count; i++)
array->runs[i].font.~BFont();
free(array);
}
void*
BTextView::FlattenRunArray(const text_run_array* runArray, int32* _size)
{
CALLED();
int32 size = sizeof(flattened_text_run_array) + (runArray->count - 1)
* sizeof(flattened_text_run);
flattened_text_run_array* array = (flattened_text_run_array*)malloc(size);
if (array == NULL) {
if (_size)
*_size = 0;
return NULL;
}
array->magic = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayMagic);
array->version = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayVersion);
array->count = B_HOST_TO_BENDIAN_INT32(runArray->count);
for (int32 i = 0; i < runArray->count; i++) {
array->styles[i].offset = B_HOST_TO_BENDIAN_INT32(
runArray->runs[i].offset);
runArray->runs[i].font.GetFamilyAndStyle(&array->styles[i].family,
&array->styles[i].style);
array->styles[i].size = B_HOST_TO_BENDIAN_FLOAT(
runArray->runs[i].font.Size());
array->styles[i].shear = B_HOST_TO_BENDIAN_FLOAT(
runArray->runs[i].font.Shear());
array->styles[i].face = B_HOST_TO_BENDIAN_INT16(
runArray->runs[i].font.Face());
array->styles[i].red = runArray->runs[i].color.red;
array->styles[i].green = runArray->runs[i].color.green;
array->styles[i].blue = runArray->runs[i].color.blue;
array->styles[i].alpha = 255;
array->styles[i]._reserved_ = 0;
}
if (_size)
*_size = size;
return array;
}
text_run_array*
BTextView::UnflattenRunArray(const void* data, int32* _size)
{
CALLED();
flattened_text_run_array* array = (flattened_text_run_array*)data;
if (B_BENDIAN_TO_HOST_INT32(array->magic) != kFlattenedTextRunArrayMagic
|| B_BENDIAN_TO_HOST_INT32(array->version)
!= kFlattenedTextRunArrayVersion) {
if (_size)
*_size = 0;
return NULL;
}
int32 count = B_BENDIAN_TO_HOST_INT32(array->count);
text_run_array* runArray = AllocRunArray(count, _size);
if (runArray == NULL)
return NULL;
for (int32 i = 0; i < count; i++) {
runArray->runs[i].offset = B_BENDIAN_TO_HOST_INT32(
array->styles[i].offset);
runArray->runs[i].font.SetFamilyAndStyle(array->styles[i].family, NULL);
runArray->runs[i].font.SetFamilyAndStyle(NULL, array->styles[i].style);
runArray->runs[i].font.SetSize(B_BENDIAN_TO_HOST_FLOAT(
array->styles[i].size));
runArray->runs[i].font.SetShear(B_BENDIAN_TO_HOST_FLOAT(
array->styles[i].shear));
uint16 face = B_BENDIAN_TO_HOST_INT16(array->styles[i].face);
if (face != B_REGULAR_FACE) {
runArray->runs[i].font.SetFace(face);
}
runArray->runs[i].color.red = array->styles[i].red;
runArray->runs[i].color.green = array->styles[i].green;
runArray->runs[i].color.blue = array->styles[i].blue;
runArray->runs[i].color.alpha = array->styles[i].alpha;
}
return runArray;
}
void
BTextView::InsertText(const char* text, int32 length, int32 offset,
const text_run_array* runs)
{
CALLED();
if (length < 0)
length = 0;
if (offset < 0)
offset = 0;
else if (offset > fText->Length())
offset = fText->Length();
if (length > 0) {
fText->InsertText(text, length, offset);
fLines->BumpOffset(length, _LineAt(offset) + 1);
fStyles->BumpOffset(length, fStyles->OffsetToRun(offset - 1) + 1);
if (offset <= fSelEnd) {
fSelStart += length;
fCaretOffset = fSelEnd = fSelStart;
}
}
if (fStylable && runs != NULL)
_SetRunArray(offset, offset + length, runs);
else {
_ApplyStyleRange(offset, offset + length);
}
}
void
BTextView::DeleteText(int32 fromOffset, int32 toOffset)
{
CALLED();
if (fromOffset < 0)
fromOffset = 0;
else if (fromOffset > fText->Length())
fromOffset = fText->Length();
if (toOffset < 0)
toOffset = 0;
else if (toOffset > fText->Length())
toOffset = fText->Length();
if (fromOffset >= toOffset)
return;
fStyles->InvalidateNullStyle();
fStyles->SyncNullStyle(fromOffset);
fText->RemoveRange(fromOffset, toOffset);
fLines->RemoveLineRange(fromOffset, toOffset);
fStyles->RemoveStyleRange(fromOffset, toOffset);
int32 range = toOffset - fromOffset;
if (fSelStart >= toOffset) {
fSelStart -= range;
fSelEnd -= range;
} else if (fSelStart >= fromOffset && fSelEnd <= toOffset) {
fSelStart = fSelEnd = fromOffset;
} else if (fSelStart >= fromOffset && fSelEnd > toOffset) {
fSelStart = fromOffset;
fSelEnd = fromOffset + fSelEnd - toOffset;
} else if (fSelStart < fromOffset && fSelEnd < toOffset) {
fSelEnd = fromOffset;
} else if (fSelStart < fromOffset && fSelEnd >= toOffset) {
fSelEnd -= range;
}
}
\param clipboard A \a clipboard to use for the undo operation.
*/
void
BTextView::Undo(BClipboard* clipboard)
{
if (fUndo)
fUndo->Undo(clipboard);
}
undo_state
BTextView::UndoState(bool* isRedo) const
{
return fUndo == NULL ? B_UNDO_UNAVAILABLE : fUndo->State(isRedo);
}
void
BTextView::GetDragParameters(BMessage* drag, BBitmap** bitmap, BPoint* point,
BHandler** handler)
{
CALLED();
if (drag == NULL)
return;
drag->AddPointer("be:originator", this);
drag->AddInt32("be_actions", B_TRASH_TARGET);
int32 numBytes = fSelEnd - fSelStart;
const char* text = fText->GetString(fSelStart, &numBytes);
drag->AddData("text/plain", B_MIME_TYPE, text, numBytes);
int32 size = 0;
text_run_array* styles = RunArray(fSelStart, fSelEnd, &size);
if (styles != NULL) {
drag->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE,
styles, size);
FreeRunArray(styles);
}
if (bitmap != NULL)
*bitmap = NULL;
if (handler != NULL)
*handler = NULL;
}
void BTextView::_ReservedTextView3() {}
void BTextView::_ReservedTextView4() {}
void BTextView::_ReservedTextView5() {}
void BTextView::_ReservedTextView6() {}
void BTextView::_ReservedTextView7() {}
void BTextView::_ReservedTextView8() {}
void BTextView::_ReservedTextView9() {}
void BTextView::_ReservedTextView10() {}
void BTextView::_ReservedTextView11() {}
void BTextView::_ReservedTextView12() {}
\param textRect The BTextView's text rect.
\param initialFont The font which the BTextView will use.
\param initialColor The initial color of the text.
*/
void
BTextView::_InitObject(BRect textRect, const BFont* initialFont,
const rgb_color* initialColor)
{
BFont font;
if (initialFont == NULL)
GetFont(&font);
else
font = *initialFont;
_NormalizeFont(&font);
rgb_color documentTextColor = ui_color(B_DOCUMENT_TEXT_COLOR);
if (initialColor == NULL)
initialColor = &documentTextColor;
fText = new BPrivate::TextGapBuffer;
fLines = new LineBuffer;
fStyles = new StyleBuffer(&font, initialColor);
fInstalledNavigateCommandWordwiseShortcuts = false;
fInstalledNavigateOptionWordwiseShortcuts = false;
fInstalledNavigateOptionLinewiseShortcuts = false;
fInstalledNavigateHomeEndDocwiseShortcuts = false;
fInstalledSelectCommandWordwiseShortcuts = false;
fInstalledSelectOptionWordwiseShortcuts = false;
fInstalledSelectOptionLinewiseShortcuts = false;
fInstalledSelectHomeEndDocwiseShortcuts = false;
fInstalledRemoveCommandWordwiseShortcuts = false;
fInstalledRemoveOptionWordwiseShortcuts = false;
fTextRect = textRect;
fMinTextRectWidth = fTextRect.Width();
fSelStart = fSelEnd = 0;
fCaretVisible = false;
fCaretTime = 0;
fCaretOffset = 0;
fClickCount = 0;
fClickTime = 0;
fDragOffset = -1;
fCursor = 0;
fActive = false;
fStylable = false;
fTabWidth = 28.0;
fSelectable = true;
fEditable = true;
fWrap = true;
fMaxBytes = INT32_MAX;
fDisallowedChars = NULL;
fAlignment = B_ALIGN_LEFT;
fAutoindent = false;
fOffscreen = NULL;
fColorSpace = B_CMAP8;
fResizable = false;
fContainerView = NULL;
fUndo = NULL;
fInline = NULL;
fDragRunner = NULL;
fClickRunner = NULL;
fTrackingMouse = NULL;
fLayoutData = new LayoutData;
_UpdateInsets(textRect);
fLastClickOffset = -1;
SetDoesUndo(true);
}
void
BTextView::_HandleBackspace(int32 modifiers)
{
if (!fEditable)
return;
if (modifiers < 0) {
BMessage* currentMessage = Window()->CurrentMessage();
if (currentMessage == NULL
|| currentMessage->FindInt32("modifiers", &modifiers) != B_OK) {
modifiers = 0;
}
}
bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0;
bool optionKeyDown = (modifiers & B_OPTION_KEY) != 0;
bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0;
if ((commandKeyDown || optionKeyDown) && !controlKeyDown) {
fSelStart = _PreviousWordStart(fCaretOffset - 1);
fSelEnd = fCaretOffset;
}
if (fUndo) {
TypingUndoBuffer* undoBuffer = dynamic_cast<TypingUndoBuffer*>(fUndo);
if (!undoBuffer) {
delete fUndo;
fUndo = undoBuffer = new TypingUndoBuffer(this);
}
undoBuffer->BackwardErase();
}
if (Window() != NULL)
Window()->DisableUpdates();
if (fSelStart == fSelEnd) {
if (fSelStart != 0)
fSelStart = _PreviousInitialByte(fSelStart);
} else
Highlight(fSelStart, fSelEnd);
DeleteText(fSelStart, fSelEnd);
fCaretOffset = fSelEnd = fSelStart;
_Refresh(fSelStart, fSelEnd, fCaretOffset);
if (Window() != NULL)
Window()->EnableUpdates();
}
void
BTextView::_HandleArrowKey(uint32 arrowKey, int32 modifiers)
{
if (fText->Length() == 0)
return;
int32 selStart = fSelStart;
int32 selEnd = fSelEnd;
if (modifiers < 0) {
BMessage* currentMessage = Window()->CurrentMessage();
if (currentMessage == NULL
|| currentMessage->FindInt32("modifiers", &modifiers) != B_OK) {
modifiers = 0;
}
}
bool shiftKeyDown = (modifiers & B_SHIFT_KEY) != 0;
bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0;
bool optionKeyDown = (modifiers & B_OPTION_KEY) != 0;
bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0;
int32 lastClickOffset = fCaretOffset;
switch (arrowKey) {
case B_LEFT_ARROW:
if (!fEditable && !fSelectable)
_ScrollBy(-kHorizontalScrollBarStep, 0);
else if (fSelStart != fSelEnd && !shiftKeyDown)
fCaretOffset = fSelStart;
else {
if ((commandKeyDown || optionKeyDown) && !controlKeyDown)
fCaretOffset = _PreviousWordStart(fCaretOffset - 1);
else
fCaretOffset = _PreviousInitialByte(fCaretOffset);
if (shiftKeyDown && fCaretOffset != lastClickOffset) {
if (fCaretOffset < fSelStart) {
selStart = fCaretOffset;
if (lastClickOffset > fSelStart) {
selEnd = fSelStart;
}
} else {
selEnd = fCaretOffset;
}
}
}
break;
case B_RIGHT_ARROW:
if (!fEditable && !fSelectable)
_ScrollBy(kHorizontalScrollBarStep, 0);
else if (fSelStart != fSelEnd && !shiftKeyDown)
fCaretOffset = fSelEnd;
else {
if ((commandKeyDown || optionKeyDown) && !controlKeyDown)
fCaretOffset = _NextWordEnd(fCaretOffset);
else
fCaretOffset = _NextInitialByte(fCaretOffset);
if (shiftKeyDown && fCaretOffset != lastClickOffset) {
if (fCaretOffset > fSelEnd) {
selEnd = fCaretOffset;
if (lastClickOffset < fSelEnd) {
selStart = fSelEnd;
}
} else {
selStart = fCaretOffset;
}
}
}
break;
case B_UP_ARROW:
{
if (!fEditable && !fSelectable)
_ScrollBy(0, -kVerticalScrollBarStep);
else if (fSelStart != fSelEnd && !shiftKeyDown)
fCaretOffset = fSelStart;
else {
if (optionKeyDown && !commandKeyDown && !controlKeyDown)
fCaretOffset = _PreviousLineStart(fCaretOffset);
else if (commandKeyDown && !optionKeyDown && !controlKeyDown) {
_ScrollTo(0, 0);
fCaretOffset = 0;
} else {
float height;
BPoint point = PointAt(fCaretOffset, &height);
for (int i = 1; i <= height; i++) {
point.y--;
int32 offset = OffsetAt(point);
if (offset < fCaretOffset || i == height) {
fCaretOffset = offset;
break;
}
}
}
if (shiftKeyDown && fCaretOffset != lastClickOffset) {
if (fCaretOffset < fSelStart) {
selStart = fCaretOffset;
if (lastClickOffset > fSelStart) {
selEnd = fSelStart;
}
} else {
selEnd = fCaretOffset;
}
}
}
break;
}
case B_DOWN_ARROW:
{
if (!fEditable && !fSelectable)
_ScrollBy(0, kVerticalScrollBarStep);
else if (fSelStart != fSelEnd && !shiftKeyDown)
fCaretOffset = fSelEnd;
else {
if (optionKeyDown && !commandKeyDown && !controlKeyDown)
fCaretOffset = _NextLineEnd(fCaretOffset);
else if (commandKeyDown && !optionKeyDown && !controlKeyDown) {
_ScrollTo(0, fTextRect.bottom + fLayoutData->bottomInset);
fCaretOffset = fText->Length();
} else {
float height;
BPoint point = PointAt(fCaretOffset, &height);
point.y += height;
fCaretOffset = OffsetAt(point);
}
if (shiftKeyDown && fCaretOffset != lastClickOffset) {
if (fCaretOffset > fSelEnd) {
selEnd = fCaretOffset;
if (lastClickOffset < fSelEnd) {
selStart = fSelEnd;
}
} else {
selStart = fCaretOffset;
}
}
}
break;
}
}
fStyles->InvalidateNullStyle();
if (fEditable || fSelectable) {
if (shiftKeyDown)
Select(selStart, selEnd);
else
Select(fCaretOffset, fCaretOffset);
ScrollToOffset(fCaretOffset);
}
}
void
BTextView::_HandleDelete(int32 modifiers)
{
if (!fEditable)
return;
if (modifiers < 0) {
BMessage* currentMessage = Window()->CurrentMessage();
if (currentMessage == NULL
|| currentMessage->FindInt32("modifiers", &modifiers) != B_OK) {
modifiers = 0;
}
}
bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0;
bool optionKeyDown = (modifiers & B_OPTION_KEY) != 0;
bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0;
if ((commandKeyDown || optionKeyDown) && !controlKeyDown) {
fSelStart = fCaretOffset;
fSelEnd = _NextWordEnd(fCaretOffset) + 1;
}
if (fUndo) {
TypingUndoBuffer* undoBuffer = dynamic_cast<TypingUndoBuffer*>(fUndo);
if (!undoBuffer) {
delete fUndo;
fUndo = undoBuffer = new TypingUndoBuffer(this);
}
undoBuffer->ForwardErase();
}
if (Window() != NULL)
Window()->DisableUpdates();
if (fSelStart == fSelEnd) {
if (fSelEnd != fText->Length())
fSelEnd = _NextInitialByte(fSelEnd);
} else
Highlight(fSelStart, fSelEnd);
DeleteText(fSelStart, fSelEnd);
fCaretOffset = fSelEnd = fSelStart;
_Refresh(fSelStart, fSelEnd, fCaretOffset);
if (Window() != NULL)
Window()->EnableUpdates();
}
void
BTextView::_HandlePageKey(uint32 pageKey, int32 modifiers)
{
if (modifiers < 0) {
BMessage* currentMessage = Window()->CurrentMessage();
if (currentMessage == NULL
|| currentMessage->FindInt32("modifiers", &modifiers) != B_OK) {
modifiers = 0;
}
}
bool shiftKeyDown = (modifiers & B_SHIFT_KEY) != 0;
bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0;
bool optionKeyDown = (modifiers & B_OPTION_KEY) != 0;
bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0;
STELine* line = NULL;
int32 selStart = fSelStart;
int32 selEnd = fSelEnd;
int32 lastClickOffset = fCaretOffset;
switch (pageKey) {
case B_HOME:
if (!fEditable && !fSelectable) {
fCaretOffset = 0;
_ScrollTo(0, 0);
break;
} else {
if (commandKeyDown && !optionKeyDown && !controlKeyDown) {
_ScrollTo(0, 0);
fCaretOffset = 0;
} else {
line = (*fLines)[_LineAt(lastClickOffset)];
fCaretOffset = line->offset;
}
if (!shiftKeyDown)
selStart = selEnd = fCaretOffset;
else if (fCaretOffset != lastClickOffset) {
if (fCaretOffset < fSelStart) {
selStart = fCaretOffset;
if (lastClickOffset > fSelStart) {
selEnd = fSelStart;
}
} else {
selEnd = fCaretOffset;
}
}
}
break;
case B_END:
if (!fEditable && !fSelectable) {
fCaretOffset = fText->Length();
_ScrollTo(0, fTextRect.bottom + fLayoutData->bottomInset);
break;
} else {
if (commandKeyDown && !optionKeyDown && !controlKeyDown) {
_ScrollTo(0, fTextRect.bottom + fLayoutData->bottomInset);
fCaretOffset = fText->Length();
} else {
int32 currentLine = _LineAt(lastClickOffset);
if (currentLine + 1 < fLines->NumLines()) {
line = (*fLines)[currentLine + 1];
fCaretOffset = _PreviousInitialByte(line->offset);
} else {
if (fCaretOffset != fText->Length()) {
fCaretOffset = fText->Length();
if (ByteAt(fCaretOffset - 1) == B_ENTER)
fCaretOffset--;
}
}
}
if (!shiftKeyDown)
selStart = selEnd = fCaretOffset;
else if (fCaretOffset != lastClickOffset) {
if (fCaretOffset > fSelEnd) {
selEnd = fCaretOffset;
if (lastClickOffset < fSelEnd) {
selStart = fSelEnd;
}
} else {
selStart = fCaretOffset;
}
}
}
break;
case B_PAGE_UP:
{
float lineHeight;
BPoint currentPos = PointAt(fCaretOffset, &lineHeight);
BPoint nextPos(currentPos.x,
currentPos.y + lineHeight - Bounds().Height());
fCaretOffset = OffsetAt(nextPos);
nextPos = PointAt(fCaretOffset);
_ScrollBy(0, nextPos.y - currentPos.y);
if (!fEditable && !fSelectable)
break;
if (!shiftKeyDown)
selStart = selEnd = fCaretOffset;
else if (fCaretOffset != lastClickOffset) {
if (fCaretOffset < fSelStart) {
selStart = fCaretOffset;
if (lastClickOffset > fSelStart) {
selEnd = fSelStart;
}
} else {
selEnd = fCaretOffset;
}
}
break;
}
case B_PAGE_DOWN:
{
BPoint currentPos = PointAt(fCaretOffset);
BPoint nextPos(currentPos.x, currentPos.y + Bounds().Height());
fCaretOffset = OffsetAt(nextPos);
nextPos = PointAt(fCaretOffset);
_ScrollBy(0, nextPos.y - currentPos.y);
if (!fEditable && !fSelectable)
break;
if (!shiftKeyDown)
selStart = selEnd = fCaretOffset;
else if (fCaretOffset != lastClickOffset) {
if (fCaretOffset > fSelEnd) {
selEnd = fCaretOffset;
if (lastClickOffset < fSelEnd) {
selStart = fSelEnd;
}
} else {
selStart = fCaretOffset;
}
}
break;
}
}
if (fEditable || fSelectable) {
if (shiftKeyDown)
Select(selStart, selEnd);
else
Select(fCaretOffset, fCaretOffset);
ScrollToOffset(fCaretOffset);
}
}
\param bytes The string or character associated with the key.
\param numBytes The amount of bytes containes in "bytes".
*/
void
BTextView::_HandleAlphaKey(const char* bytes, int32 numBytes)
{
if (!fEditable)
return;
if (fUndo) {
TypingUndoBuffer* undoBuffer = dynamic_cast<TypingUndoBuffer*>(fUndo);
if (!undoBuffer) {
delete fUndo;
fUndo = undoBuffer = new TypingUndoBuffer(this);
}
undoBuffer->InputCharacter(numBytes);
}
if (fSelStart != fSelEnd) {
Highlight(fSelStart, fSelEnd);
DeleteText(fSelStart, fSelEnd);
}
if (Window() != NULL)
Window()->DisableUpdates();
if (fAutoindent && numBytes == 1 && *bytes == B_ENTER) {
int32 start, offset;
start = offset = OffsetAt(_LineAt(fSelStart));
while (ByteAt(offset) != '\0' &&
(ByteAt(offset) == B_TAB || ByteAt(offset) == B_SPACE)
&& offset < fSelStart)
offset++;
_DoInsertText(bytes, numBytes, fSelStart, NULL);
if (start != offset)
_DoInsertText(Text() + start, offset - start, fSelStart, NULL);
} else
_DoInsertText(bytes, numBytes, fSelStart, NULL);
fCaretOffset = fSelEnd;
ScrollToOffset(fCaretOffset);
if (Window() != NULL)
Window()->EnableUpdates();
}
if needed.
\param fromOffset The offset from where to refresh.
\param toOffset The offset where to refresh to.
\param scrollTo Scroll the view to \a scrollTo offset if not \c INT32_MIN.
*/
void
BTextView::_Refresh(int32 fromOffset, int32 toOffset, int32 scrollTo)
{
float saveHeight = fTextRect.Height();
float saveWidth = fTextRect.Width();
int32 fromLine = _LineAt(fromOffset);
int32 toLine = _LineAt(toOffset);
int32 saveFromLine = fromLine;
int32 saveToLine = toLine;
_RecalculateLineBreaks(&fromLine, &toLine);
if (!Window())
return;
BRect bounds = Bounds();
float newHeight = fTextRect.Height();
if (fromLine != saveFromLine || toLine != saveToLine
|| newHeight != saveHeight) {
fromOffset = -1;
}
if (newHeight != saveHeight) {
if (newHeight < saveHeight)
toLine = _LineAt(BPoint(0.0f, saveHeight + fTextRect.top));
else
toLine = _LineAt(BPoint(0.0f, newHeight + fTextRect.top));
}
int32 fromVisible = _LineAt(BPoint(0.0f, bounds.top));
int32 toVisible = _LineAt(BPoint(0.0f, bounds.bottom));
fromLine = std::max(fromVisible, fromLine);
toLine = std::min(toLine, toVisible);
_AutoResize(false);
_RequestDrawLines(fromLine, toLine);
BRect eraseRect = bounds;
eraseRect.top = fTextRect.top + (*fLines)[fLines->NumLines()]->origin;
eraseRect.bottom = fTextRect.top + saveHeight;
if (eraseRect.bottom > eraseRect.top && eraseRect.Intersects(bounds)) {
SetLowColor(ViewColor());
FillRect(eraseRect, B_SOLID_LOW);
}
if (newHeight != saveHeight || fMinTextRectWidth != saveWidth)
_UpdateScrollbars();
if (scrollTo != INT32_MIN)
ScrollToOffset(scrollTo);
Flush();
}
\param startLine The line number to start recalculating line breaks.
\param endLine The line number to stop recalculating line breaks.
*/
void
BTextView::_RecalculateLineBreaks(int32* startLine, int32* endLine)
{
CALLED();
float width = fTextRect.Width();
if (fWrap && (!fTextRect.IsValid() || width == 0))
return;
*startLine = (*startLine < 0) ? 0 : *startLine;
*endLine = (*endLine > fLines->NumLines() - 1) ? fLines->NumLines() - 1
: *endLine;
int32 textLength = fText->Length();
int32 lineIndex = (*startLine > 0) ? *startLine - 1 : 0;
int32 recalThreshold = (*fLines)[*endLine + 1]->offset;
STELine* curLine = (*fLines)[lineIndex];
STELine* nextLine = curLine + 1;
do {
float ascent, descent;
int32 fromOffset = curLine->offset;
int32 toOffset = _FindLineBreak(fromOffset, &ascent, &descent, &width);
curLine->ascent = ascent;
curLine->width = width;
int32 nextOffset = _NextInitialByte(fromOffset);
if (toOffset < nextOffset && fromOffset < textLength)
toOffset = nextOffset;
lineIndex++;
STELine saveLine = *nextLine;
if (lineIndex > fLines->NumLines() || toOffset < nextLine->offset) {
STELine newLine;
newLine.offset = toOffset;
newLine.origin = ceilf(curLine->origin + ascent + descent) + 1;
newLine.ascent = 0;
fLines->InsertLine(&newLine, lineIndex);
} else {
nextLine->offset = toOffset;
nextLine->origin = ceilf(curLine->origin + ascent + descent) + 1;
while (lineIndex < fLines->NumLines()
&& toOffset >= ((*fLines)[lineIndex] + 1)->offset) {
fLines->RemoveLines(lineIndex + 1);
}
nextLine = (*fLines)[lineIndex];
if (nextLine->offset == saveLine.offset) {
if (nextLine->offset >= recalThreshold) {
if (nextLine->origin != saveLine.origin)
fLines->BumpOrigin(nextLine->origin - saveLine.origin,
lineIndex + 1);
break;
}
} else {
if (lineIndex > 0 && lineIndex == *startLine)
*startLine = lineIndex - 1;
}
}
curLine = (*fLines)[lineIndex];
nextLine = curLine + 1;
} while (curLine->offset < textLength);
(*fLines)[fLines->NumLines()]->width = 0;
fTextRect.left = Bounds().left + fLayoutData->leftInset;
fTextRect.right = Bounds().right - fLayoutData->rightInset;
float newHeight = TextHeight(0, fLines->NumLines() - 1);
fTextRect.bottom = fTextRect.top + newHeight;
if (!fWrap) {
fMinTextRectWidth = fLines->MaxWidth() - 1;
switch (fAlignment) {
default:
case B_ALIGN_LEFT:
fTextRect.right = fTextRect.left + fMinTextRectWidth;
break;
case B_ALIGN_RIGHT:
fTextRect.left = fTextRect.right - fMinTextRectWidth;
break;
case B_ALIGN_CENTER:
fTextRect.InsetBy(roundf((fTextRect.Width()
- fMinTextRectWidth) / 2), 0);
break;
}
_ValidateTextRect();
}
*endLine = lineIndex - 1;
*startLine = std::min(*startLine, *endLine);
}
void
BTextView::_ValidateTextRect()
{
if (fTextRect.right <= fTextRect.left)
fTextRect.right = fTextRect.left + 1;
if (fTextRect.bottom <= fTextRect.top)
fTextRect.bottom = fTextRect.top + 1;
}
int32
BTextView::_FindLineBreak(int32 fromOffset, float* _ascent, float* _descent,
float* inOutWidth)
{
*_ascent = 0.0;
*_descent = 0.0;
const int32 limit = fText->Length();
if (fromOffset >= limit) {
if (fStyles->NumRuns() > 0) {
fStyles->Iterate(fromOffset, 1, fInline, NULL, NULL, _ascent,
_descent);
} else {
if (fStyles->IsValidNullStyle()) {
const BFont* font = NULL;
fStyles->GetNullStyle(&font, NULL);
font_height fh;
font->GetHeight(&fh);
*_ascent = fh.ascent;
*_descent = fh.descent + fh.leading;
}
}
*inOutWidth = 0;
return limit;
}
int32 offset = fromOffset;
if (!fWrap) {
offset = limit - fromOffset;
fText->FindChar(B_ENTER, fromOffset, &offset);
offset += fromOffset;
int32 toOffset = (offset < limit) ? offset : limit;
*inOutWidth = _TabExpandedStyledWidth(fromOffset, toOffset - fromOffset,
_ascent, _descent);
return offset < limit ? offset + 1 : limit;
}
bool done = false;
float ascent = 0.0;
float descent = 0.0;
int32 delta = 0;
float deltaWidth = 0.0;
float strWidth = 0.0;
uchar theChar;
while (offset < limit && !done) {
for (; (offset + delta) < limit; delta++) {
if (CanEndLine(offset + delta)) {
theChar = fText->RealCharAt(offset + delta);
if (theChar != B_SPACE && theChar != B_TAB
&& theChar != B_ENTER) {
delta++;
}
break;
}
}
int32 deltaBeforeWhitespace = delta;
for (; (offset + delta) < limit; delta++) {
theChar = fText->RealCharAt(offset + delta);
if (theChar == B_ENTER) {
done = true;
delta++;
break;
} else if (theChar != B_SPACE && theChar != B_TAB) {
break;
}
}
delta = std::max(delta, (int32)1);
deltaWidth = _TabExpandedStyledWidth(offset,
done ? delta - 1 : delta, &ascent, &descent);
strWidth += deltaWidth;
if (strWidth >= *inOutWidth) {
done = true;
if (delta == deltaBeforeWhitespace) {
break;
}
strWidth -= deltaWidth;
strWidth += _StyledWidth(offset, deltaBeforeWhitespace, NULL, NULL);
if (strWidth >= *inOutWidth) {
break;
}
}
*_ascent = std::max(ascent, *_ascent);
*_descent = std::max(descent, *_descent);
offset += delta;
delta = 0;
}
if (offset - fromOffset < 1) {
*_ascent = 0.0;
*_descent = 0.0;
strWidth = 0.0;
int32 current = fromOffset;
for (offset = _NextInitialByte(current); current < limit;
current = offset, offset = _NextInitialByte(offset)) {
strWidth += _StyledWidth(current, offset - current, &ascent,
&descent);
if (strWidth >= *inOutWidth) {
offset = _PreviousInitialByte(offset);
break;
}
*_ascent = std::max(ascent, *_ascent);
*_descent = std::max(descent, *_descent);
}
}
return std::min(offset, limit);
}
int32
BTextView::_PreviousLineStart(int32 offset)
{
if (offset <= 0)
return 0;
while (offset > 0) {
offset = _PreviousInitialByte(offset);
if (_CharClassification(offset) == CHAR_CLASS_WHITESPACE
&& ByteAt(offset) == B_ENTER) {
return offset + 1;
}
}
return offset;
}
int32
BTextView::_NextLineEnd(int32 offset)
{
int32 textLen = fText->Length();
if (offset >= textLen)
return textLen;
while (offset < textLen) {
if (_CharClassification(offset) == CHAR_CLASS_WHITESPACE
&& ByteAt(offset) == B_ENTER) {
break;
}
offset = _NextInitialByte(offset);
}
return offset;
}
int32
BTextView::_PreviousWordBoundary(int32 offset)
{
uint32 charType = _CharClassification(offset);
int32 previous;
while (offset > 0) {
previous = _PreviousInitialByte(offset);
if (_CharClassification(previous) != charType)
break;
offset = previous;
}
return offset;
}
int32
BTextView::_NextWordBoundary(int32 offset)
{
int32 textLen = fText->Length();
uint32 charType = _CharClassification(offset);
while (offset < textLen) {
offset = _NextInitialByte(offset);
if (_CharClassification(offset) != charType)
break;
}
return offset;
}
int32
BTextView::_PreviousWordStart(int32 offset)
{
if (offset <= 1)
return 0;
--offset;
if (_CharClassification(offset) != CHAR_CLASS_DEFAULT) {
while (offset > 0) {
offset = _PreviousInitialByte(offset);
if (_CharClassification(offset) == CHAR_CLASS_DEFAULT)
break;
}
}
while (offset > 0) {
int32 previous = _PreviousInitialByte(offset);
if (_CharClassification(previous) != CHAR_CLASS_DEFAULT)
break;
offset = previous;
}
return offset;
}
int32
BTextView::_NextWordEnd(int32 offset)
{
int32 textLen = fText->Length();
if (_CharClassification(offset) != CHAR_CLASS_DEFAULT) {
while (offset < textLen) {
offset = _NextInitialByte(offset);
if (_CharClassification(offset) == CHAR_CLASS_DEFAULT)
break;
}
}
while (offset < textLen) {
offset = _NextInitialByte(offset);
if (_CharClassification(offset) != CHAR_CLASS_DEFAULT)
break;
}
return offset;
}
offset with the given length, expanding all tab characters as needed.
*/
float
BTextView::_TabExpandedStyledWidth(int32 offset, int32 length, float* _ascent,
float* _descent) const
{
float ascent = 0.0;
float descent = 0.0;
float maxAscent = 0.0;
float maxDescent = 0.0;
float width = 0.0;
int32 numBytes = length;
bool foundTab = false;
do {
foundTab = fText->FindChar(B_TAB, offset, &numBytes);
width += _StyledWidth(offset, numBytes, &ascent, &descent);
if (maxAscent < ascent)
maxAscent = ascent;
if (maxDescent < descent)
maxDescent = descent;
if (foundTab) {
width += _ActualTabWidth(width);
numBytes++;
}
offset += numBytes;
length -= numBytes;
numBytes = length;
} while (foundTab && length > 0);
if (_ascent != NULL)
*_ascent = maxAscent;
if (_descent != NULL)
*_descent = maxDescent;
return width;
}
\param fromOffset The offset where to start.
\param length The length of the text to examine.
\param _ascent A pointer to a float which will contain the maximum ascent.
\param _descent A pointer to a float which will contain the maximum descent.
\return The width for the text within the given limits.
*/
float
BTextView::_StyledWidth(int32 fromOffset, int32 length, float* _ascent,
float* _descent) const
{
if (length == 0) {
fStyles->Iterate(fromOffset, 1, fInline, NULL, NULL, _ascent,
_descent);
return 0.0;
}
float result = 0.0;
float ascent = 0.0;
float descent = 0.0;
float maxAscent = 0.0;
float maxDescent = 0.0;
const BFont* font = NULL;
int32 numBytes;
while ((numBytes = fStyles->Iterate(fromOffset, length, fInline, &font,
NULL, &ascent, &descent)) != 0) {
maxAscent = std::max(ascent, maxAscent);
maxDescent = std::max(descent, maxDescent);
#if USE_WIDTHBUFFER
if (BPrivate::gWidthBuffer != NULL) {
result += BPrivate::gWidthBuffer->StringWidth(*fText, fromOffset,
numBytes, font);
} else {
#endif
const char* text = fText->GetString(fromOffset, &numBytes);
result += font->StringWidth(text, numBytes);
#if USE_WIDTHBUFFER
}
#endif
fromOffset += numBytes;
length -= numBytes;
}
if (_ascent != NULL)
*_ascent = maxAscent;
if (_descent != NULL)
*_descent = maxDescent;
return result;
}
float
BTextView::_ActualTabWidth(float location) const
{
float tabWidth = fTabWidth - fmod(location, fTabWidth);
if (round(tabWidth) == 0)
tabWidth = fTabWidth;
return tabWidth;
}
void
BTextView::_DoInsertText(const char* text, int32 length, int32 offset,
const text_run_array* runs)
{
_CancelInputMethod();
if (fText->Length() + length > MaxBytes())
return;
if (fSelStart != fSelEnd)
Select(fSelStart, fSelStart);
const int32 textLength = fText->Length();
if (offset > textLength)
offset = textLength;
InsertText(text, length, offset, runs);
_Refresh(offset, offset + length);
}
void
BTextView::_DoDeleteText(int32 fromOffset, int32 toOffset)
{
CALLED();
}
void
BTextView::_DrawLine(BView* view, const int32 &lineNum,
const int32 &startOffset, const bool &erase, BRect &eraseRect,
BRegion &inputRegion)
{
STELine* line = (*fLines)[lineNum];
float startLeft = fTextRect.left;
if (startOffset != -1) {
if (ByteAt(startOffset) == B_ENTER) {
startLeft = PointAt(line->offset).x;
} else
startLeft = PointAt(startOffset).x;
} else if (fAlignment != B_ALIGN_LEFT) {
float alignmentOffset = fTextRect.Width() + 1 - LineWidth(lineNum);
if (fAlignment == B_ALIGN_CENTER)
alignmentOffset = floorf(alignmentOffset / 2);
startLeft += alignmentOffset;
}
int32 length = (line + 1)->offset;
if (startOffset != -1)
length -= startOffset;
else
length -= line->offset;
if (ByteAt((line + 1)->offset - 1) == B_ENTER)
length--;
view->MovePenTo(startLeft,
line->origin + line->ascent + fTextRect.top + 1);
if (erase) {
eraseRect.top = line->origin + fTextRect.top;
eraseRect.bottom = (line + 1)->origin + fTextRect.top;
view->FillRect(eraseRect, B_SOLID_LOW);
}
if (length <= 0)
return;
bool foundTab = false;
int32 tabChars = 0;
int32 numTabs = 0;
int32 offset = startOffset != -1 ? startOffset : line->offset;
const BFont* font = NULL;
const rgb_color* color = NULL;
int32 numBytes;
drawing_mode defaultTextRenderingMode = DrawingMode();
while ((numBytes = fStyles->Iterate(offset, length, fInline, &font,
&color)) != 0) {
view->SetFont(font);
view->SetHighColor(*color);
tabChars = std::min(numBytes, length);
do {
foundTab = fText->FindChar(B_TAB, offset, &tabChars);
if (foundTab) {
do {
numTabs++;
if (ByteAt(offset + tabChars + numTabs) != B_TAB)
break;
} while ((tabChars + numTabs) < numBytes);
}
drawing_mode textRenderingMode = defaultTextRenderingMode;
if (inputRegion.CountRects() > 0
&& ((offset <= fInline->Offset()
&& fInline->Offset() < offset + tabChars)
|| (fInline->Offset() <= offset
&& offset < fInline->Offset() + fInline->Length()))) {
textRenderingMode = B_OP_OVER;
BRegion textRegion;
GetTextRegion(offset, offset + length, &textRegion);
textRegion.IntersectWith(&inputRegion);
view->PushState();
view->SetHighColor(kBlueInputColor);
view->FillRect(textRegion.Frame());
if (fInline->SelectionLength() > 0) {
BRegion selectedRegion;
GetTextRegion(fInline->Offset()
+ fInline->SelectionOffset(), fInline->Offset()
+ fInline->SelectionOffset()
+ fInline->SelectionLength(), &selectedRegion);
textRegion.IntersectWith(&selectedRegion);
view->SetHighColor(kRedInputColor);
view->FillRect(textRegion.Frame());
}
view->PopState();
}
int32 size = tabChars;
const char* stringToDraw = fText->GetString(offset, &size);
view->SetDrawingMode(textRenderingMode);
view->DrawString(stringToDraw, size);
if (foundTab) {
float penPos = PenLocation().x - fTextRect.left;
switch (fAlignment) {
default:
case B_ALIGN_LEFT:
break;
case B_ALIGN_RIGHT:
penPos -= fTextRect.Width() - LineWidth(lineNum);
break;
case B_ALIGN_CENTER:
penPos -= floorf((fTextRect.Width() + 1
- LineWidth(lineNum)) / 2);
break;
}
float tabWidth = _ActualTabWidth(penPos);
tabWidth += ((numTabs - 1) * fTabWidth);
view->MovePenBy(tabWidth, 0.0);
tabChars += numTabs;
}
offset += tabChars;
length -= tabChars;
numBytes -= tabChars;
tabChars = std::min(numBytes, length);
numTabs = 0;
} while (foundTab && tabChars > 0);
}
}
void
BTextView::_DrawLines(int32 startLine, int32 endLine, int32 startOffset,
bool erase)
{
if (!Window())
return;
const BRect bounds(Bounds());
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);
ConstrainClippingRegion(&newClip);
SetLowColor(ViewColor());
BView* view = NULL;
if (fOffscreen == NULL)
view = this;
else {
fOffscreen->Lock();
view = fOffscreen->ChildAt(0);
view->SetLowColor(ViewColor());
view->FillRect(view->Bounds(), B_SOLID_LOW);
}
long maxLine = fLines->NumLines() - 1;
if (startLine < 0)
startLine = 0;
if (endLine > maxLine)
endLine = maxLine;
if (fAlignment != B_ALIGN_LEFT)
erase = true;
BRect eraseRect = clipRect;
int32 startEraseLine = startLine;
STELine* line = (*fLines)[startLine];
if (erase && startOffset != -1 && fAlignment == B_ALIGN_LEFT) {
startEraseLine++;
int32 startErase = startOffset;
BPoint erasePoint = PointAt(startErase);
eraseRect.left = erasePoint.x;
eraseRect.top = erasePoint.y;
eraseRect.bottom = (line + 1)->origin + fTextRect.top;
view->FillRect(eraseRect, B_SOLID_LOW);
eraseRect = clipRect;
}
BRegion inputRegion;
if (fInline != NULL && fInline->IsActive()) {
GetTextRegion(fInline->Offset(), fInline->Offset() + fInline->Length(),
&inputRegion);
}
for (int32 lineNum = startLine; lineNum <= endLine; lineNum++) {
const bool eraseThisLine = erase && lineNum >= startEraseLine;
_DrawLine(view, lineNum, startOffset, eraseThisLine, eraseRect,
inputRegion);
startOffset = -1;
}
if (fActive) {
if (fSelStart != fSelEnd) {
if (fSelectable)
Highlight(fSelStart, fSelEnd);
} else {
if (fCaretVisible)
_DrawCaret(fSelStart, true);
}
}
if (fOffscreen != NULL) {
view->Sync();
BRect drawRect(leftTop.x, leftTop.y, penLocation.x, penLocation.y);
DrawBitmap(fOffscreen, drawRect, drawRect);*/
fOffscreen->Unlock();
}
ConstrainClippingRegion(NULL);
}
void
BTextView::_RequestDrawLines(int32 startLine, int32 endLine)
{
if (!Window())
return;
long maxLine = fLines->NumLines() - 1;
STELine* from = (*fLines)[startLine];
STELine* to = endLine == maxLine ? NULL : (*fLines)[endLine + 1];
BRect invalidRect(Bounds().left, from->origin + fTextRect.top,
Bounds().right,
to != NULL ? to->origin + fTextRect.top : fTextRect.bottom);
Invalidate(invalidRect);
}
void
BTextView::_DrawCaret(int32 offset, bool visible)
{
float lineHeight;
BPoint caretPoint = PointAt(offset, &lineHeight);
caretPoint.x = std::min(caretPoint.x, fTextRect.right);
BRect caretRect;
caretRect.left = caretRect.right = caretPoint.x;
caretRect.top = caretPoint.y;
caretRect.bottom = caretPoint.y + lineHeight - 1;
if (visible)
InvertRect(caretRect);
else
Invalidate(caretRect);
}
inline void
BTextView::_ShowCaret()
{
if (fActive && !fCaretVisible && fEditable && fSelStart == fSelEnd)
_InvertCaret();
}
inline void
BTextView::_HideCaret()
{
if (fCaretVisible && fSelStart == fSelEnd)
_InvertCaret();
}
void
BTextView::_InvertCaret()
{
fCaretVisible = !fCaretVisible;
_DrawCaret(fSelStart, fCaretVisible);
fCaretTime = system_time();
}
\param offset The offset (zero based within the object's text) where to
place the dragging caret. If it's -1, hide the caret.
*/
void
BTextView::_DragCaret(int32 offset)
{
if (offset == fDragOffset)
return;
if (fDragOffset != -1)
_DrawCaret(fDragOffset, false);
if (offset != -1) {
if (fActive) {
if (offset >= fSelStart && offset <= fSelEnd) {
fDragOffset = -1;
return;
}
}
_DrawCaret(offset, true);
}
fDragOffset = offset;
}
void
BTextView::_StopMouseTracking()
{
delete fTrackingMouse;
fTrackingMouse = NULL;
}
bool
BTextView::_PerformMouseUp(BPoint where)
{
if (fTrackingMouse == NULL)
return false;
if (fTrackingMouse->selectionRect.IsValid())
Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset);
_StopMouseTracking();
_TrackMouse(where, NULL, true);
return true;
}
bool
BTextView::_PerformMouseMoved(BPoint where, uint32 code)
{
fWhere = where;
if (fTrackingMouse == NULL)
return false;
int32 currentOffset = OffsetAt(where);
if (fTrackingMouse->selectionRect.IsValid()) {
if (currentOffset != fTrackingMouse->clickOffset
|| fabs(fTrackingMouse->where.x - where.x) > 3
|| fabs(fTrackingMouse->where.y - where.y) > 3) {
_StopMouseTracking();
_InitiateDrag();
return true;
}
return false;
}
switch (fClickCount) {
case 3:
if (currentOffset <= fTrackingMouse->anchor) {
fTrackingMouse->selStart
= (*fLines)[_LineAt(currentOffset)]->offset;
fTrackingMouse->selEnd = fTrackingMouse->shiftDown
? fSelEnd
: (*fLines)[_LineAt(fTrackingMouse->anchor) + 1]->offset;
} else {
fTrackingMouse->selStart
= fTrackingMouse->shiftDown
? fSelStart
: (*fLines)[_LineAt(fTrackingMouse->anchor)]->offset;
fTrackingMouse->selEnd
= (*fLines)[_LineAt(currentOffset) + 1]->offset;
}
break;
case 2:
if (currentOffset <= fTrackingMouse->anchor) {
fTrackingMouse->selStart = _PreviousWordBoundary(currentOffset);
fTrackingMouse->selEnd
= fTrackingMouse->shiftDown
? fSelEnd
: _NextWordBoundary(fTrackingMouse->anchor);
} else {
fTrackingMouse->selStart
= fTrackingMouse->shiftDown
? fSelStart
: _PreviousWordBoundary(fTrackingMouse->anchor);
fTrackingMouse->selEnd = _NextWordBoundary(currentOffset);
}
break;
default:
if (currentOffset <= fTrackingMouse->anchor) {
fTrackingMouse->selStart = currentOffset;
fTrackingMouse->selEnd
= fTrackingMouse->shiftDown
? fSelEnd : fTrackingMouse->anchor;
} else {
fTrackingMouse->selStart
= fTrackingMouse->shiftDown
? fSelStart : fTrackingMouse->anchor;
fTrackingMouse->selEnd = currentOffset;
}
break;
}
if (fTrackingMouse->selEnd != fSelEnd)
fCaretOffset = fTrackingMouse->selEnd;
else if (fTrackingMouse->selStart != fSelStart)
fCaretOffset = fTrackingMouse->selStart;
Select(fTrackingMouse->selStart, fTrackingMouse->selEnd);
_TrackMouse(where, NULL);
return true;
}
view cursor.
\param where The point where the mouse has moved.
\param message The dragging message, if there is any.
\param force Passed as second parameter of SetViewCursor()
*/
void
BTextView::_TrackMouse(BPoint where, const BMessage* message, bool force)
{
BRegion textRegion;
GetTextRegion(fSelStart, fSelEnd, &textRegion);
if (message && AcceptsDrop(message))
_TrackDrag(where);
else if ((fSelectable || fEditable)
&& (fTrackingMouse != NULL || !textRegion.Contains(where))) {
SetViewCursor(B_CURSOR_I_BEAM, force);
} else
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, force);
}
void
BTextView::_TrackDrag(BPoint where)
{
CALLED();
if (Bounds().Contains(where))
_DragCaret(OffsetAt(where));
}
void
BTextView::_InitiateDrag()
{
BMessage dragMessage(B_MIME_DATA);
BBitmap* dragBitmap = NULL;
BPoint bitmapPoint;
BHandler* dragHandler = NULL;
GetDragParameters(&dragMessage, &dragBitmap, &bitmapPoint, &dragHandler);
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
if (dragBitmap != NULL)
DragMessage(&dragMessage, dragBitmap, bitmapPoint, dragHandler);
else {
BRegion region;
GetTextRegion(fSelStart, fSelEnd, ®ion);
BRect bounds = Bounds();
BRect dragRect = region.Frame();
if (!bounds.Contains(dragRect))
dragRect = bounds & dragRect;
DragMessage(&dragMessage, dragRect, dragHandler);
}
BMessenger messenger(this);
BMessage message(_DISPOSE_DRAG_);
fDragRunner = new (nothrow) BMessageRunner(messenger, &message, 100000);
}
bool
BTextView::_MessageDropped(BMessage* message, BPoint where, BPoint offset)
{
ASSERT(message);
void* from = NULL;
bool internalDrop = false;
if (message->FindPointer("be:originator", &from) == B_OK
&& from == this && fSelEnd != fSelStart)
internalDrop = true;
_DragCaret(-1);
delete fDragRunner;
fDragRunner = NULL;
_TrackMouse(where, NULL);
if (!AcceptsDrop(message))
return false;
int32 dropOffset = OffsetAt(where);
if (dropOffset > fText->Length())
dropOffset = fText->Length();
if (internalDrop) {
if (dropOffset >= fSelStart && dropOffset <= fSelEnd)
return true;
}
ssize_t dataLength = 0;
const char* text = NULL;
entry_ref ref;
if (message->FindData("text/plain", B_MIME_TYPE, (const void**)&text,
&dataLength) == B_OK) {
text_run_array* runArray = NULL;
ssize_t runLength = 0;
if (fStylable) {
message->FindData("application/x-vnd.Be-text_run_array",
B_MIME_TYPE, (const void**)&runArray, &runLength);
}
_FilterDisallowedChars((char*)text, dataLength, runArray);
if (dataLength < 1) {
beep();
return true;
}
if (fUndo) {
delete fUndo;
fUndo = new DropUndoBuffer(this, text, dataLength, runArray,
runLength, dropOffset, internalDrop);
}
if (internalDrop) {
if (dropOffset > fSelEnd)
dropOffset -= dataLength;
Delete();
}
Insert(dropOffset, text, dataLength, runArray);
if (IsFocus())
Select(dropOffset, dropOffset + dataLength);
}
return true;
}
void
BTextView::_PerformAutoScrolling()
{
BRect bounds = Bounds();
BPoint scrollBy(B_ORIGIN);
if (fWhere.x > bounds.right)
scrollBy.x = fWhere.x - bounds.right;
else if (fWhere.x < bounds.left)
scrollBy.x = fWhere.x - bounds.left;
if (fTextRect.left > bounds.left && fTextRect.right < bounds.right)
scrollBy.x = 0;
if (CountLines() > 1) {
if (fWhere.y > bounds.bottom)
scrollBy.y = fWhere.y - bounds.bottom;
else if (fWhere.y < bounds.top)
scrollBy.y = fWhere.y - bounds.top;
}
if (fTextRect.top > bounds.top && fTextRect.bottom < bounds.bottom)
scrollBy.y = 0;
if (scrollBy != B_ORIGIN)
_ScrollBy(scrollBy.x, scrollBy.y);
}
void
BTextView::_UpdateScrollbars()
{
BRect bounds(Bounds());
BScrollBar* horizontalScrollBar = ScrollBar(B_HORIZONTAL);
BScrollBar* verticalScrollBar = ScrollBar(B_VERTICAL);
if (horizontalScrollBar != NULL) {
long viewWidth = bounds.IntegerWidth();
long dataWidth = (long)ceilf(_TextWidth());
long maxRange = dataWidth - viewWidth;
maxRange = std::max(maxRange, 0l);
horizontalScrollBar->SetRange(0, (float)maxRange);
horizontalScrollBar->SetProportion((float)viewWidth
/ (float)dataWidth);
horizontalScrollBar->SetSteps(kHorizontalScrollBarStep,
dataWidth / 10);
}
if (verticalScrollBar != NULL) {
long viewHeight = bounds.IntegerHeight();
long dataHeight = (long)ceilf(_TextHeight());
long maxRange = dataHeight - viewHeight;
maxRange = std::max(maxRange, 0l);
verticalScrollBar->SetRange(0, maxRange);
verticalScrollBar->SetProportion((float)viewHeight
/ (float)dataHeight);
verticalScrollBar->SetSteps(kVerticalScrollBarStep,
viewHeight);
}
}
void
BTextView::_ScrollBy(float horizontal, float vertical)
{
BRect bounds = Bounds();
_ScrollTo(bounds.left + horizontal, bounds.top + vertical);
}
void
BTextView::_ScrollTo(float x, float y)
{
BRect bounds = Bounds();
long viewWidth = bounds.IntegerWidth();
long viewHeight = bounds.IntegerHeight();
float minWidth = fTextRect.left - fLayoutData->leftInset;
float maxWidth = fTextRect.right + fLayoutData->rightInset - viewWidth;
float minHeight = fTextRect.top - fLayoutData->topInset;
float maxHeight = fTextRect.bottom + fLayoutData->bottomInset - viewHeight;
if (x > maxWidth)
x = maxWidth;
if (x < minWidth)
x = minWidth;
if (y > maxHeight)
y = maxHeight;
if (y < minHeight)
y = minHeight;
ScrollTo(x, y);
}
void
BTextView::_AutoResize(bool redraw)
{
if (!fResizable || fContainerView == NULL)
return;
float oldWidth = Bounds().Width();
float newWidth = _TextWidth();
float right = oldWidth - newWidth;
if (fAlignment == B_ALIGN_CENTER)
fContainerView->MoveBy(roundf(right / 2), 0);
else if (fAlignment == B_ALIGN_RIGHT)
fContainerView->MoveBy(right, 0);
float grow = newWidth - oldWidth;
fContainerView->ResizeBy(grow, 0);
fTextRect.OffsetTo(fLayoutData->leftInset, fLayoutData->topInset);
ScrollToOffset(0);
if (redraw)
_RequestDrawLines(0, 0);
}
void
BTextView::_NewOffscreen(float padding)
{
if (fOffscreen != NULL)
_DeleteOffscreen();
#if USE_DOUBLEBUFFERING
BRect bitmapRect(0, 0, fTextRect.Width() + padding, fTextRect.Height());
fOffscreen = new BBitmap(bitmapRect, fColorSpace, true, false);
if (fOffscreen != NULL && fOffscreen->Lock()) {
BView* bufferView = new BView(bitmapRect, "drawing view", 0, 0);
fOffscreen->AddChild(bufferView);
fOffscreen->Unlock();
}
#endif
}
void
BTextView::_DeleteOffscreen()
{
if (fOffscreen != NULL && fOffscreen->Lock()) {
delete fOffscreen;
fOffscreen = NULL;
}
}
cursor to \c B_CURSOR_I_BEAM.
*/
void
BTextView::_Activate()
{
fActive = true;
_NewOffscreen();
if (fSelStart != fSelEnd) {
if (fSelectable)
Highlight(fSelStart, fSelEnd);
} else
_ShowCaret();
BPoint where;
uint32 buttons;
GetMouse(&where, &buttons, false);
if (Bounds().Contains(where))
_TrackMouse(where, NULL);
if (Window() != NULL) {
BMessage* message;
if (!Window()->HasShortcut(B_LEFT_ARROW, B_COMMAND_KEY)
&& !Window()->HasShortcut(B_RIGHT_ARROW, B_COMMAND_KEY)) {
message = new BMessage(kMsgNavigateArrow);
message->AddInt32("key", B_LEFT_ARROW);
message->AddInt32("modifiers", B_COMMAND_KEY);
Window()->AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY, message, this);
message = new BMessage(kMsgNavigateArrow);
message->AddInt32("key", B_RIGHT_ARROW);
message->AddInt32("modifiers", B_COMMAND_KEY);
Window()->AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY, message, this);
fInstalledNavigateCommandWordwiseShortcuts = true;
}
if (!Window()->HasShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY)
&& !Window()->HasShortcut(B_RIGHT_ARROW,
B_COMMAND_KEY | B_SHIFT_KEY)) {
message = new BMessage(kMsgNavigateArrow);
message->AddInt32("key", B_LEFT_ARROW);
message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY);
Window()->AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY,
message, this);
message = new BMessage(kMsgNavigateArrow);
message->AddInt32("key", B_RIGHT_ARROW);
message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY);
Window()->AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY,
message, this);
fInstalledSelectCommandWordwiseShortcuts = true;
}
if (!Window()->HasShortcut(B_DELETE, B_COMMAND_KEY)
&& !Window()->HasShortcut(B_BACKSPACE, B_COMMAND_KEY)) {
message = new BMessage(kMsgRemoveWord);
message->AddInt32("key", B_DELETE);
message->AddInt32("modifiers", B_COMMAND_KEY);
Window()->AddShortcut(B_DELETE, B_COMMAND_KEY, message, this);
message = new BMessage(kMsgRemoveWord);
message->AddInt32("key", B_BACKSPACE);
message->AddInt32("modifiers", B_COMMAND_KEY);
Window()->AddShortcut(B_BACKSPACE, B_COMMAND_KEY, message, this);
fInstalledRemoveCommandWordwiseShortcuts = true;
}
if (!Window()->HasShortcut(B_LEFT_ARROW, B_OPTION_KEY)
&& !Window()->HasShortcut(B_RIGHT_ARROW, B_OPTION_KEY)) {
message = new BMessage(kMsgNavigateArrow);
message->AddInt32("key", B_LEFT_ARROW);
message->AddInt32("modifiers", B_OPTION_KEY);
Window()->AddShortcut(B_LEFT_ARROW, B_OPTION_KEY, message, this);
message = new BMessage(kMsgNavigateArrow);
message->AddInt32("key", B_RIGHT_ARROW);
message->AddInt32("modifiers", B_OPTION_KEY);
Window()->AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY, message, this);
fInstalledNavigateOptionWordwiseShortcuts = true;
}
if (!Window()->HasShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_SHIFT_KEY)
&& !Window()->HasShortcut(B_RIGHT_ARROW,
B_OPTION_KEY | B_SHIFT_KEY)) {
message = new BMessage(kMsgNavigateArrow);
message->AddInt32("key", B_LEFT_ARROW);
message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY);
Window()->AddShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_SHIFT_KEY,
message, this);
message = new BMessage(kMsgNavigateArrow);
message->AddInt32("key", B_RIGHT_ARROW);
message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY);
Window()->AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_SHIFT_KEY,
message, this);
fInstalledSelectOptionWordwiseShortcuts = true;
}
if (!Window()->HasShortcut(B_DELETE, B_OPTION_KEY)
&& !Window()->HasShortcut(B_BACKSPACE, B_OPTION_KEY)) {
message = new BMessage(kMsgRemoveWord);
message->AddInt32("key", B_DELETE);
message->AddInt32("modifiers", B_OPTION_KEY);
Window()->AddShortcut(B_DELETE, B_OPTION_KEY, message, this);
message = new BMessage(kMsgRemoveWord);
message->AddInt32("key", B_BACKSPACE);
message->AddInt32("modifiers", B_OPTION_KEY);
Window()->AddShortcut(B_BACKSPACE, B_OPTION_KEY, message, this);
fInstalledRemoveOptionWordwiseShortcuts = true;
}
if (!Window()->HasShortcut(B_UP_ARROW, B_OPTION_KEY)
&& !Window()->HasShortcut(B_DOWN_ARROW, B_OPTION_KEY)) {
message = new BMessage(kMsgNavigateArrow);
message->AddInt32("key", B_UP_ARROW);
message->AddInt32("modifiers", B_OPTION_KEY);
Window()->AddShortcut(B_UP_ARROW, B_OPTION_KEY, message, this);
message = new BMessage(kMsgNavigateArrow);
message->AddInt32("key", B_DOWN_ARROW);
message->AddInt32("modifiers", B_OPTION_KEY);
Window()->AddShortcut(B_DOWN_ARROW, B_OPTION_KEY, message, this);
fInstalledNavigateOptionLinewiseShortcuts = true;
}
if (!Window()->HasShortcut(B_UP_ARROW, B_OPTION_KEY | B_SHIFT_KEY)
&& !Window()->HasShortcut(B_DOWN_ARROW,
B_OPTION_KEY | B_SHIFT_KEY)) {
message = new BMessage(kMsgNavigateArrow);
message->AddInt32("key", B_UP_ARROW);
message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY);
Window()->AddShortcut(B_UP_ARROW, B_OPTION_KEY | B_SHIFT_KEY,
message, this);
message = new BMessage(kMsgNavigateArrow);
message->AddInt32("key", B_DOWN_ARROW);
message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY);
Window()->AddShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_SHIFT_KEY,
message, this);
fInstalledSelectOptionLinewiseShortcuts = true;
}
if (!Window()->HasShortcut(B_HOME, B_COMMAND_KEY)
&& !Window()->HasShortcut(B_END, B_COMMAND_KEY)) {
message = new BMessage(kMsgNavigatePage);
message->AddInt32("key", B_HOME);
message->AddInt32("modifiers", B_COMMAND_KEY);
Window()->AddShortcut(B_HOME, B_COMMAND_KEY, message, this);
message = new BMessage(kMsgNavigatePage);
message->AddInt32("key", B_END);
message->AddInt32("modifiers", B_COMMAND_KEY);
Window()->AddShortcut(B_END, B_COMMAND_KEY, message, this);
fInstalledNavigateHomeEndDocwiseShortcuts = true;
}
if (!Window()->HasShortcut(B_HOME, B_COMMAND_KEY | B_SHIFT_KEY)
&& !Window()->HasShortcut(B_END, B_COMMAND_KEY | B_SHIFT_KEY)) {
message = new BMessage(kMsgNavigatePage);
message->AddInt32("key", B_HOME);
message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY);
Window()->AddShortcut(B_HOME, B_COMMAND_KEY | B_SHIFT_KEY,
message, this);
message = new BMessage(kMsgNavigatePage);
message->AddInt32("key", B_END);
message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY);
Window()->AddShortcut(B_END, B_COMMAND_KEY | B_SHIFT_KEY,
message, this);
fInstalledSelectHomeEndDocwiseShortcuts = true;
}
}
}
void
BTextView::_Deactivate()
{
fActive = false;
_CancelInputMethod();
_DeleteOffscreen();
if (fSelStart != fSelEnd) {
if (fSelectable)
Highlight(fSelStart, fSelEnd);
} else
_HideCaret();
if (Window() != NULL) {
if (fInstalledNavigateCommandWordwiseShortcuts) {
Window()->RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY);
Window()->RemoveShortcut(B_RIGHT_ARROW, B_COMMAND_KEY);
fInstalledNavigateCommandWordwiseShortcuts = false;
}
if (fInstalledSelectCommandWordwiseShortcuts) {
Window()->RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY);
Window()->RemoveShortcut(B_RIGHT_ARROW,
B_COMMAND_KEY | B_SHIFT_KEY);
fInstalledSelectCommandWordwiseShortcuts = false;
}
if (fInstalledRemoveCommandWordwiseShortcuts) {
Window()->RemoveShortcut(B_DELETE, B_COMMAND_KEY);
Window()->RemoveShortcut(B_BACKSPACE, B_COMMAND_KEY);
fInstalledRemoveCommandWordwiseShortcuts = false;
}
if (fInstalledNavigateOptionWordwiseShortcuts) {
Window()->RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY);
Window()->RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY);
fInstalledNavigateOptionWordwiseShortcuts = false;
}
if (fInstalledSelectOptionWordwiseShortcuts) {
Window()->RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_SHIFT_KEY);
Window()->RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_SHIFT_KEY);
fInstalledSelectOptionWordwiseShortcuts = false;
}
if (fInstalledRemoveOptionWordwiseShortcuts) {
Window()->RemoveShortcut(B_DELETE, B_OPTION_KEY);
Window()->RemoveShortcut(B_BACKSPACE, B_OPTION_KEY);
fInstalledRemoveOptionWordwiseShortcuts = false;
}
if (fInstalledNavigateOptionLinewiseShortcuts) {
Window()->RemoveShortcut(B_UP_ARROW, B_OPTION_KEY);
Window()->RemoveShortcut(B_DOWN_ARROW, B_OPTION_KEY);
fInstalledNavigateOptionLinewiseShortcuts = false;
}
if (fInstalledSelectOptionLinewiseShortcuts) {
Window()->RemoveShortcut(B_UP_ARROW, B_OPTION_KEY | B_SHIFT_KEY);
Window()->RemoveShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_SHIFT_KEY);
fInstalledSelectOptionLinewiseShortcuts = false;
}
if (fInstalledNavigateHomeEndDocwiseShortcuts) {
Window()->RemoveShortcut(B_HOME, B_COMMAND_KEY);
Window()->RemoveShortcut(B_END, B_COMMAND_KEY);
fInstalledNavigateHomeEndDocwiseShortcuts = false;
}
if (fInstalledSelectHomeEndDocwiseShortcuts) {
Window()->RemoveShortcut(B_HOME, B_COMMAND_KEY | B_SHIFT_KEY);
Window()->RemoveShortcut(B_END, B_COMMAND_KEY | B_SHIFT_KEY);
fInstalledSelectHomeEndDocwiseShortcuts = false;
}
}
}
Set font rotation to 0, removes any font flag, set font spacing
to \c B_BITMAP_SPACING and font encoding to \c B_UNICODE_UTF8.
*/
void
BTextView::_NormalizeFont(BFont* font)
{
if (font) {
font->SetRotation(0.0f);
font->SetFlags(0);
font->SetSpacing(B_BITMAP_SPACING);
font->SetEncoding(B_UNICODE_UTF8);
}
}
void
BTextView::_SetRunArray(int32 startOffset, int32 endOffset,
const text_run_array* runs)
{
const int32 numStyles = runs->count;
if (numStyles > 0) {
const text_run* theRun = &runs->runs[0];
for (int32 index = 0; index < numStyles; index++) {
int32 fromOffset = theRun->offset + startOffset;
int32 toOffset = endOffset;
if (index + 1 < numStyles) {
toOffset = (theRun + 1)->offset + startOffset;
toOffset = (toOffset > endOffset) ? endOffset : toOffset;
}
_ApplyStyleRange(fromOffset, toOffset, B_FONT_ALL, &theRun->font,
&theRun->color, false);
theRun++;
}
fStyles->InvalidateNullStyle();
}
}
\param offset The offset where the wanted character can be found.
\return A value which represents the character's classification.
*/
uint32
BTextView::_CharClassification(int32 offset) const
{
switch (fText->RealCharAt(offset)) {
case '\0':
return CHAR_CLASS_END_OF_TEXT;
case B_SPACE:
case B_TAB:
case B_ENTER:
return CHAR_CLASS_WHITESPACE;
case '=':
case '+':
case '@':
case '#':
case '$':
case '%':
case '^':
case '&':
case '*':
case '\\':
case '|':
case '<':
case '>':
case '/':
case '~':
return CHAR_CLASS_GRAPHICAL;
case '\'':
case '"':
return CHAR_CLASS_QUOTE;
case ',':
case '.':
case '?':
case '!':
case ';':
case ':':
case '-':
return CHAR_CLASS_PUNCTUATION;
case '(':
case '[':
case '{':
return CHAR_CLASS_PARENS_OPEN;
case ')':
case ']':
case '}':
return CHAR_CLASS_PARENS_CLOSE;
default:
return CHAR_CLASS_DEFAULT;
}
}
\param offset The offset where to start looking.
\return The offset of the next UTF-8 character.
*/
int32
BTextView::_NextInitialByte(int32 offset) const
{
if (offset >= fText->Length())
return offset;
for (++offset; (ByteAt(offset) & 0xC0) == 0x80; ++offset)
;
return offset;
}
\param offset The offset where to start looking.
\return The offset of the previous UTF-8 character.
*/
int32
BTextView::_PreviousInitialByte(int32 offset) const
{
if (offset <= 0)
return 0;
int32 count = 6;
for (--offset; offset > 0 && count; --offset, --count) {
if ((ByteAt(offset) & 0xC0) != 0x80)
break;
}
return count ? offset : 0;
}
bool
BTextView::_GetProperty(BMessage* message, BMessage* specifier,
const char* property, BMessage* reply)
{
CALLED();
if (strcmp(property, "selection") == 0) {
reply->what = B_REPLY;
reply->AddInt32("result", fSelStart);
reply->AddInt32("result", fSelEnd);
reply->AddInt32("error", B_OK);
return true;
} else if (strcmp(property, "Text") == 0) {
if (IsTypingHidden()) {
beep();
return false;
}
int32 index, range;
specifier->FindInt32("index", &index);
specifier->FindInt32("range", &range);
char* buffer = new char[range + 1];
GetText(index, range, buffer);
reply->what = B_REPLY;
reply->AddString("result", buffer);
reply->AddInt32("error", B_OK);
delete[] buffer;
return true;
} else if (strcmp(property, "text_run_array") == 0)
return false;
return false;
}
bool
BTextView::_SetProperty(BMessage* message, BMessage* specifier,
const char* property, BMessage* reply)
{
CALLED();
if (strcmp(property, "selection") == 0) {
int32 index, range;
specifier->FindInt32("index", &index);
specifier->FindInt32("range", &range);
Select(index, index + range);
reply->what = B_REPLY;
reply->AddInt32("error", B_OK);
return true;
} else if (strcmp(property, "Text") == 0) {
int32 index, range;
specifier->FindInt32("index", &index);
specifier->FindInt32("range", &range);
const char* buffer = NULL;
if (message->FindString("data", &buffer) == B_OK) {
InsertText(buffer, range, index, NULL);
_Refresh(index, index + range);
} else {
DeleteText(index, index + range);
_Refresh(index, index);
}
reply->what = B_REPLY;
reply->AddInt32("error", B_OK);
return true;
} else if (strcmp(property, "text_run_array") == 0)
return false;
return false;
}
bool
BTextView::_CountProperties(BMessage* message, BMessage* specifier,
const char* property, BMessage* reply)
{
CALLED();
if (strcmp(property, "Text") == 0) {
reply->what = B_REPLY;
reply->AddInt32("result", fText->Length());
reply->AddInt32("error", B_OK);
return true;
}
return false;
}
void
BTextView::_HandleInputMethodChanged(BMessage* message)
{
ASSERT(fInline != NULL);
const char* string = NULL;
if (message->FindString("be:string", &string) < B_OK || string == NULL)
return;
_HideCaret();
if (IsFocus())
be_app->ObscureCursor();
bool confirmed;
if (message->FindBool("be:confirmed", &confirmed) != B_OK)
confirmed = false;
if (fInline->IsActive()) {
const int32 oldOffset = fInline->Offset();
DeleteText(oldOffset, oldOffset + fInline->Length());
if (confirmed)
fInline->SetActive(false);
fCaretOffset = fSelStart = fSelEnd = oldOffset;
}
const int32 stringLen = strlen(string);
fInline->SetOffset(fSelStart);
fInline->SetLength(stringLen);
fInline->ResetClauses();
if (!confirmed && !fInline->IsActive())
fInline->SetActive(true);
int32 clauseCount = 0;
int32 clauseStart;
int32 clauseEnd;
while (message->FindInt32("be:clause_start", clauseCount, &clauseStart)
== B_OK
&& message->FindInt32("be:clause_end", clauseCount, &clauseEnd)
== B_OK) {
if (!fInline->AddClause(clauseStart, clauseEnd))
break;
clauseCount++;
}
if (confirmed) {
_Refresh(fSelStart, fSelEnd, fSelEnd);
_ShowCaret();
const char* currPos = string;
const char* prevPos = currPos;
while (*currPos != '\0') {
if ((*currPos & 0xC0) == 0xC0) {
++currPos;
while ((*currPos & 0xC0) == 0x80)
++currPos;
} else if ((*currPos & 0xC0) == 0x80) {
prevPos = ++currPos;
} else {
++currPos;
}
KeyDown(prevPos, currPos - prevPos);
prevPos = currPos;
}
_Refresh(fSelStart, fSelEnd, fSelEnd);
} else {
int32 selectionStart = 0;
int32 selectionEnd = 0;
message->FindInt32("be:selection", 0, &selectionStart);
message->FindInt32("be:selection", 1, &selectionEnd);
fInline->SetSelectionOffset(selectionStart);
fInline->SetSelectionLength(selectionEnd - selectionStart);
const int32 inlineOffset = fInline->Offset();
InsertText(string, stringLen, fSelStart, NULL);
_Refresh(inlineOffset, fSelEnd, fSelEnd);
_ShowCaret();
}
}
message.
*/
void
BTextView::_HandleInputMethodLocationRequest()
{
ASSERT(fInline != NULL);
int32 offset = fInline->Offset();
const int32 limit = offset + fInline->Length();
BMessage message(B_INPUT_METHOD_EVENT);
message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST);
while (offset < limit) {
float height;
BPoint where = PointAt(offset, &height);
ConvertToScreen(&where);
message.AddPoint("be:location_reply", where);
message.AddFloat("be:height_reply", height);
offset = _NextInitialByte(offset);
}
fInline->Method()->SendMessage(&message);
}
void
BTextView::_CancelInputMethod()
{
if (!fInline)
return;
InlineInput* inlineInput = fInline;
fInline = NULL;
if (inlineInput->IsActive() && Window()) {
_Refresh(inlineInput->Offset(), fText->Length()
- inlineInput->Offset());
BMessage message(B_INPUT_METHOD_EVENT);
message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED);
inlineInput->Method()->SendMessage(&message);
}
delete inlineInput;
}
\note This will never return the last line (use LineAt() if you
need to be correct about that.) N.B.
\param offset The offset of the wanted character.
\return The line number of the character at the given \a offset as an int32.
*/
int32
BTextView::_LineAt(int32 offset) const
{
return fLines->OffsetToLine(offset);
}
\note This will never return the last line (use LineAt() if you
need to be correct about that.) N.B.
\param point The \a point the get the line number of.
\return The line number of the given \a point as an int32.
*/
int32
BTextView::_LineAt(const BPoint& point) const
{
return fLines->PixelToLine(point.y - fTextRect.top);
}
of the buffer.
*/
bool
BTextView::_IsOnEmptyLastLine(int32 offset) const
{
return (offset == fText->Length() && offset > 0
&& fText->RealCharAt(offset - 1) == B_ENTER);
}
void
BTextView::_ApplyStyleRange(int32 fromOffset, int32 toOffset, uint32 mode,
const BFont* font, const rgb_color* color, bool syncNullStyle)
{
BFont normalized;
if (font != NULL) {
normalized = *font;
_NormalizeFont(&normalized);
font = &normalized;
}
if (!fStylable) {
fromOffset = 0;
toOffset = fText->Length();
}
if (syncNullStyle)
fStyles->SyncNullStyle(fromOffset);
fStyles->SetStyleRange(fromOffset, toOffset, fText->Length(), mode,
font, color);
}
float
BTextView::_NullStyleHeight() const
{
const BFont* font = NULL;
fStyles->GetNullStyle(&font, NULL);
font_height fontHeight;
font->GetHeight(&fontHeight);
return ceilf(fontHeight.ascent + fontHeight.descent + 1);
}
void
BTextView::_ShowContextMenu(BPoint where)
{
bool isRedo;
undo_state state = UndoState(&isRedo);
bool isUndo = state != B_UNDO_UNAVAILABLE && !isRedo;
int32 start;
int32 finish;
GetSelection(&start, &finish);
bool canEdit = IsEditable();
int32 length = fText->Length();
BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
BLayoutBuilder::Menu<>(menu)
.AddItem(TRANSLATE("Undo"), B_UNDO)
.SetEnabled(canEdit && isUndo)
.AddItem(TRANSLATE("Redo"), B_UNDO)
.SetEnabled(canEdit && isRedo)
.AddSeparator()
.AddItem(TRANSLATE("Cut"), B_CUT, 'X')
.SetEnabled(canEdit && start != finish)
.AddItem(TRANSLATE("Copy"), B_COPY, 'C')
.SetEnabled(start != finish)
.AddItem(TRANSLATE("Paste"), B_PASTE, 'V')
.SetEnabled(canEdit && be_clipboard->SystemCount() > 0)
.AddSeparator()
.AddItem(TRANSLATE("Select all"), B_SELECT_ALL, 'A')
.SetEnabled(!(start == 0 && finish == length))
;
menu->SetTargetForItems(this);
ConvertToScreen(&where);
menu->Go(where, true, true, true);
}
void
BTextView::_FilterDisallowedChars(char* text, ssize_t& length,
text_run_array* runArray)
{
if (!fDisallowedChars)
return;
if (fDisallowedChars->IsEmpty() || !text)
return;
ssize_t stringIndex = 0;
if (runArray) {
ssize_t remNext = 0;
for (int i = 0; i < runArray->count; i++) {
runArray->runs[i].offset -= remNext;
while (stringIndex < runArray->runs[i].offset
&& stringIndex < length) {
if (fDisallowedChars->HasItem(
reinterpret_cast<void*>(text[stringIndex]))) {
memmove(text + stringIndex, text + stringIndex + 1,
length - stringIndex - 1);
length--;
runArray->runs[i].offset--;
remNext++;
} else
stringIndex++;
}
}
}
while (stringIndex < length) {
if (fDisallowedChars->HasItem(
reinterpret_cast<void*>(text[stringIndex]))) {
memmove(text + stringIndex, text + stringIndex + 1,
length - stringIndex - 1);
length--;
} else
stringIndex++;
}
}
void
BTextView::_UpdateInsets(const BRect& rect)
{
if (fLayoutData->overridden)
return;
const BRect& bounds = Bounds();
fLayoutData->leftInset = rect.left >= bounds.left
? rect.left - bounds.left : 0;
fLayoutData->topInset = rect.top >= bounds.top
? rect.top - bounds.top : 0;
fLayoutData->rightInset = bounds.right >= rect.right
? bounds.right - rect.right : 0;
fLayoutData->bottomInset = bounds.bottom >= rect.bottom
? bounds.bottom - rect.bottom : 0;
if (rect == bounds && (fEditable || fSelectable)) {
float hPadding = be_control_look->DefaultLabelSpacing();
float hInset = floorf(hPadding / 2.0f);
float vInset = 1;
fLayoutData->leftInset += hInset;
fLayoutData->topInset += vInset;
fLayoutData->rightInset += hInset;
fLayoutData->bottomInset += vInset;
}
}
float
BTextView::_ViewWidth()
{
return Bounds().Width()
- fLayoutData->leftInset
- fLayoutData->rightInset;
}
float
BTextView::_ViewHeight()
{
return Bounds().Height()
- fLayoutData->topInset
- fLayoutData->bottomInset;
}
BRect
BTextView::_ViewRect()
{
BRect rect(Bounds());
rect.left += fLayoutData->leftInset;
rect.top += fLayoutData->topInset;
rect.right -= fLayoutData->rightInset;
rect.bottom -= fLayoutData->bottomInset;
return rect;
}
float
BTextView::_TextWidth()
{
return fTextRect.Width()
+ fLayoutData->leftInset
+ fLayoutData->rightInset;
}
float
BTextView::_TextHeight()
{
return fTextRect.Height()
+ fLayoutData->topInset
+ fLayoutData->bottomInset;
}
BRect
BTextView::_TextRect()
{
BRect rect(fTextRect);
rect.left -= fLayoutData->leftInset;
rect.top -= fLayoutData->topInset;
rect.right += fLayoutData->rightInset;
rect.bottom += fLayoutData->bottomInset;
return rect;
}
float
BTextView::_UneditableTint() const
{
return ui_color(B_DOCUMENT_BACKGROUND_COLOR).IsLight() ? B_DARKEN_1_TINT : 0.853;
}
BTextView::TextTrackState::TextTrackState(BMessenger messenger)
:
clickOffset(0),
shiftDown(false),
anchor(0),
selStart(0),
selEnd(0),
fRunner(NULL)
{
BMessage message(_PING_);
const bigtime_t scrollSpeed = 25 * 1000;
fRunner = new (nothrow) BMessageRunner(messenger, &message, scrollSpeed);
}
BTextView::TextTrackState::~TextTrackState()
{
delete fRunner;
}
void
BTextView::TextTrackState::SimulateMouseMovement(BTextView* textView)
{
BPoint where;
uint32 buttons;
textView->GetMouse(&where, &buttons);
textView->_PerformMouseMoved(where, B_INSIDE_VIEW);
}
extern "C" void
B_IF_GCC_2(InvalidateLayout__9BTextViewb, _ZN9BTextView16InvalidateLayoutEb)(
BTextView* view, bool descendants)
{
perform_data_layout_invalidated data;
data.descendants = descendants;
view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data);
}