- Fixed text moving - Almost completely implemented Undo capabilities - in BTextView::GetSupportedSuites(BMessage *data): added the suites name - fixed BTextView::Insert(int32, const char *, int32, const text_run_array *), it didn't take offset into account - undo is disabled by default (even if the BeBook says the opposite) - added B_SELECT_ALL message handler - now it doesn't select anything if the object isn't selectable - drag message is now filled in BTextView::GetDragMessage() as it should, and not in BTextView::InitiateDrag() - changed all deprecated SetCursor() calls to SetViewCursor() - added some NormalizeFont() calls, while trying to fix the multi-byte insertion bug git-svn-id: file:///srv/svn/repos/haiku/trunk/current@5009 a95241bf-73f2-0310-859d-f6bbb57e9c96
3388 lines
83 KiB
C++
3388 lines
83 KiB
C++
//------------------------------------------------------------------------------
|
|
// Copyright (c) 2001-2003, OpenBeOS
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the "Software"),
|
|
// to deal in the Software without restriction, including without limitation
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
// and/or sell copies of the Software, and to permit persons to whom the
|
|
// Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
//
|
|
// File Name: TextView.cpp
|
|
// Author: Marc Flerackers ([email protected])
|
|
// Description: BTextView displays and manages styled text.
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Standard Includes -----------------------------------------------------------
|
|
|
|
// System Includes -------------------------------------------------------------
|
|
#include <Application.h>
|
|
#include <Bitmap.h>
|
|
#include <Clipboard.h>
|
|
#include <Errors.h>
|
|
#include <Message.h>
|
|
#include <PropertyInfo.h>
|
|
#include <Region.h>
|
|
#include <ScrollBar.h>
|
|
#include <TextView.h>
|
|
#include <Window.h>
|
|
|
|
//#include <CRTDBG.H>
|
|
|
|
// Project Includes ------------------------------------------------------------
|
|
|
|
// Local Includes --------------------------------------------------------------
|
|
#include "LineBuffer.h"
|
|
#include "StyleBuffer.h"
|
|
#include "TextGapBuffer.h"
|
|
#include "UndoBuffer.h"
|
|
|
|
// Local Defines ---------------------------------------------------------------
|
|
|
|
struct flattened_text_run {
|
|
int32 offset;
|
|
char family[64];
|
|
char style[64];
|
|
float size;
|
|
float shear; /* typically 90.0 */
|
|
uint16 face; /* typically 0 */
|
|
uint8 red;
|
|
uint8 green;
|
|
uint8 blue;
|
|
uint8 alpha; /* 255 == opaque */
|
|
uint16 _reserved_; /* 0 */
|
|
};
|
|
|
|
struct flattened_text_run_array {
|
|
uchar magic[4]; /* 41 6c 69 21 */
|
|
uchar version[4]; /* 00 00 00 00 */
|
|
int32 count;
|
|
flattened_text_run styles[1];
|
|
};
|
|
|
|
// _BTextTrackState_ class -----------------------------------------------------
|
|
class _BTextTrackState_ {
|
|
|
|
public:
|
|
_BTextTrackState_(bool inSelection)
|
|
: fMoved(false),
|
|
fInSelection(inSelection)
|
|
{}
|
|
|
|
bool fMoved;
|
|
bool fInSelection;
|
|
};
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Globals ---------------------------------------------------------------------
|
|
static property_info
|
|
prop_list[] =
|
|
{
|
|
{
|
|
"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 }
|
|
};
|
|
|
|
sem_id BTextView::sWidthSem = B_BAD_SEM_ID; // Created/deleted by init/fini_interface_kit
|
|
int32 BTextView::sWidthAtom = 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),
|
|
fText(NULL),
|
|
fLines(NULL),
|
|
fStyles(NULL),
|
|
fTextRect(textRect),
|
|
fSelStart(0),
|
|
fSelEnd(0),
|
|
fCaretVisible(false),
|
|
fCaretTime(0),
|
|
fClickOffset(-1),
|
|
fClickCount(0),
|
|
fClickTime(0),
|
|
fDragOffset(-1),
|
|
//fCursor
|
|
fActive(false),
|
|
fStylable(false),
|
|
fTabWidth(28.0f),
|
|
fSelectable(true),
|
|
fEditable(true),
|
|
fWrap(true),
|
|
fMaxBytes(0),
|
|
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),
|
|
//fWhere
|
|
fTrackingMouse(NULL),
|
|
fTextChange(NULL)
|
|
{
|
|
InitObject(textRect, NULL, NULL);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
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),
|
|
fText(NULL),
|
|
fLines(NULL),
|
|
fStyles(NULL),
|
|
fTextRect(textRect),
|
|
fSelStart(0),
|
|
fSelEnd(0),
|
|
fCaretVisible(false),
|
|
fCaretTime(0),
|
|
fClickOffset(-1),
|
|
fClickCount(0),
|
|
fClickTime(0),
|
|
fDragOffset(-1),
|
|
//fCursor
|
|
fActive(false),
|
|
fStylable(false),
|
|
fTabWidth(28.0f),
|
|
fSelectable(true),
|
|
fEditable(true),
|
|
fWrap(true),
|
|
fMaxBytes(0),
|
|
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),
|
|
//fWhere
|
|
fTrackingMouse(NULL),
|
|
fTextChange(NULL)
|
|
{
|
|
InitObject(textRect, initialFont, initialColor);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
BTextView::BTextView(BMessage *archive)
|
|
: BView(archive)
|
|
{
|
|
const char *text;
|
|
int32 flag, flag2;
|
|
float value;
|
|
BRect rect;
|
|
bool toggle;
|
|
|
|
if (archive->FindString("_text", &text) == B_OK)
|
|
SetText(text);
|
|
|
|
if (archive->FindInt32("_align", &flag) == B_OK)
|
|
SetAlignment((alignment)flag);
|
|
|
|
if (archive->FindFloat("_tab", &value) == B_OK)
|
|
SetTabWidth(value);
|
|
|
|
if (archive->FindInt32("_col_sp", &flag) == B_OK)
|
|
SetColorSpace((color_space)flag);
|
|
|
|
if (archive->FindRect("_trect", &rect) == B_OK)
|
|
SetTextRect(rect);
|
|
|
|
if (archive->FindInt32("_max", &flag) == B_OK)
|
|
SetMaxBytes(value);
|
|
|
|
if (archive->FindInt32("_sel", &flag) == B_OK &&
|
|
archive->FindInt32("_sel", &flag2) == B_OK)
|
|
Select(flag, flag2);
|
|
|
|
//"_dis_ch" (array) B_RAW_TYPE Disallowed characters.
|
|
//"_runs" B_RAW_TYPE Flattened run array.
|
|
|
|
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);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
BTextView::~BTextView()
|
|
{
|
|
delete fText;
|
|
delete fLines;
|
|
delete fStyles;
|
|
delete fDisallowedChars;
|
|
delete fUndo;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
BArchivable *
|
|
BTextView::Instantiate(BMessage *archive)
|
|
{
|
|
if (validate_instantiation(archive, "BTextView"))
|
|
return new BTextView(archive);
|
|
else
|
|
return NULL;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
status_t
|
|
BTextView::Archive(BMessage *data, bool deep) const
|
|
{
|
|
status_t err = BView::Archive(data, deep);
|
|
|
|
data->AddString("_text", Text());
|
|
data->AddInt32("_align", fAlignment);
|
|
data->AddFloat("_tab", fTabWidth);
|
|
data->AddInt32("_col_sp", fColorSpace);
|
|
data->AddRect("_trect", fTextRect);
|
|
data->AddInt32("_max", fMaxBytes);
|
|
data->AddInt32("_sel", fSelStart);
|
|
data->AddInt32("_sel", fSelEnd);
|
|
//"_dis_ch" (array) B_RAW_TYPE Disallowed characters.
|
|
//"_runs" B_RAW_TYPE Flattened run array.
|
|
data->AddBool("_stylable", fStylable);
|
|
data->AddBool("_auto_in", fAutoindent);
|
|
data->AddBool("_wrap", fWrap);
|
|
data->AddBool("_nsel", !fSelectable);
|
|
data->AddBool("_nedit", !fEditable);
|
|
|
|
return err;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::AttachedToWindow()
|
|
{
|
|
BView::AttachedToWindow();
|
|
|
|
fCaretVisible = false;
|
|
fCaretTime = 0;
|
|
fClickCount = 0;
|
|
fClickTime = 0;
|
|
fDragOffset = -1;
|
|
fActive = false;
|
|
|
|
Window()->SetPulseRate(50000);
|
|
|
|
Refresh(0, LONG_MAX, true, false);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::DetachedFromWindow()
|
|
{
|
|
BView::DetachedFromWindow();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Draw(BRect updateRect)
|
|
{
|
|
// what lines need to be drawn?
|
|
long startLine = LineAt(BPoint(0.0f, updateRect.top));
|
|
long endLine = LineAt(BPoint(0.0f, updateRect.bottom));
|
|
|
|
DrawLines(startLine, endLine);
|
|
|
|
// draw the caret/hilite the selection
|
|
if (fActive) {
|
|
if (fSelStart != fSelEnd)
|
|
Highlight(fSelStart, fSelEnd);
|
|
else {
|
|
if (fCaretVisible)
|
|
DrawCaret(fSelStart);
|
|
}
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::MouseDown(BPoint where)
|
|
{
|
|
// should we even bother?
|
|
if (!fEditable && !fSelectable)
|
|
return;
|
|
|
|
if (!IsFocus()) {
|
|
MakeFocus();
|
|
return;
|
|
}
|
|
|
|
// hide the caret if it's visible
|
|
if (fCaretVisible)
|
|
InvertCaret();
|
|
|
|
int32 mouseOffset = OffsetAt(where);
|
|
bool shiftDown = modifiers() & B_SHIFT_KEY;
|
|
|
|
// should we initiate a drag?
|
|
if ((fSelStart != fSelEnd) && (!shiftDown)) {
|
|
BPoint loc;
|
|
ulong buttons;
|
|
GetMouse(&loc, &buttons);
|
|
// was the secondary button clicked?
|
|
if (buttons == B_SECONDARY_MOUSE_BUTTON) {
|
|
// was the click within the selection range?
|
|
if ((mouseOffset >= fSelStart) && (mouseOffset <= fSelEnd)) {
|
|
InitiateDrag();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// get the system-wide click speed
|
|
bigtime_t clickSpeed = 0;
|
|
get_click_speed(&clickSpeed);
|
|
|
|
// is this a double/triple click, or is it a new click?
|
|
if ( (clickSpeed > (system_time() - fClickTime)) &&
|
|
(mouseOffset == fClickOffset) ) {
|
|
if (fClickCount > 1) {
|
|
// triple click
|
|
fClickCount = 0;
|
|
fClickTime = 0;
|
|
} else {
|
|
// double click
|
|
fClickCount = 2;
|
|
fClickTime = system_time();
|
|
}
|
|
}
|
|
else {
|
|
// new click
|
|
fClickOffset = mouseOffset;
|
|
fClickCount = 1;
|
|
fClickTime = system_time();
|
|
|
|
if (!shiftDown) {
|
|
Select(mouseOffset, mouseOffset);
|
|
if (fEditable)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
|
|
// no need to track the mouse if we can't select
|
|
if (!fSelectable)
|
|
return;
|
|
|
|
// track the mouse while it's down
|
|
long start = 0;
|
|
long end = 0;
|
|
long anchor = (mouseOffset > fSelStart) ? fSelStart : fSelEnd;
|
|
BPoint curMouse = where;
|
|
ulong buttons = 0;
|
|
do {
|
|
if (mouseOffset > anchor) {
|
|
start = anchor;
|
|
end = mouseOffset;
|
|
}
|
|
else {
|
|
start = mouseOffset;
|
|
end = anchor;
|
|
}
|
|
|
|
switch (fClickCount) {
|
|
case 0:
|
|
// triple click, select line by line
|
|
start = (*fLines)[LineAt(start)]->offset;
|
|
end = (*fLines)[LineAt(end) + 1]->offset;
|
|
break;
|
|
|
|
case 2:
|
|
{
|
|
// double click, select word by word
|
|
FindWord(mouseOffset, &start, &end);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
// new click, select char by char
|
|
break;
|
|
}
|
|
if (shiftDown) {
|
|
if (mouseOffset > anchor)
|
|
start = anchor;
|
|
else
|
|
end = anchor;
|
|
}
|
|
Select(start, end);
|
|
|
|
// Should we scroll the view?
|
|
if (!Bounds().Contains(curMouse)) {
|
|
float hDelta = 0;
|
|
float vDelta = 0;
|
|
if (ScrollBar(B_HORIZONTAL) != NULL) {
|
|
if (curMouse.x < Bounds().left)
|
|
hDelta = curMouse.x - Bounds().left;
|
|
else {
|
|
if (curMouse.x > Bounds().right)
|
|
hDelta = curMouse.x - Bounds().right;
|
|
}
|
|
if (hDelta != 0)
|
|
ScrollBar(B_HORIZONTAL)->SetValue(ScrollBar(B_HORIZONTAL)->Value() + hDelta);
|
|
}
|
|
if (ScrollBar(B_VERTICAL) != NULL) {
|
|
if (curMouse.y < Bounds().top)
|
|
vDelta = curMouse.y - Bounds().top;
|
|
else if (curMouse.y > Bounds().bottom)
|
|
vDelta = curMouse.y - Bounds().bottom;
|
|
if (vDelta != 0)
|
|
ScrollBar(B_VERTICAL)->SetValue(ScrollBar(B_VERTICAL)->Value() + vDelta);
|
|
}
|
|
if ((hDelta != 0) || (vDelta != 0))
|
|
Window()->UpdateIfNeeded();
|
|
}
|
|
|
|
// Zzzz...
|
|
snooze(30000);
|
|
|
|
GetMouse(&curMouse, &buttons);
|
|
mouseOffset = OffsetAt(curMouse);
|
|
} while (buttons != 0);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::MouseUp(BPoint where)
|
|
{
|
|
PerformMouseUp(where);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::MouseMoved(BPoint where, uint32 code, const BMessage *message)
|
|
{
|
|
switch (code) {
|
|
case B_ENTERED_VIEW:
|
|
if ((fActive) && (message == NULL))
|
|
SetViewCursor(B_CURSOR_I_BEAM);
|
|
break;
|
|
|
|
case B_INSIDE_VIEW:
|
|
if (message != NULL) {
|
|
if (AcceptsDrop(message))
|
|
DragCaret(OffsetAt(where));
|
|
}
|
|
break;
|
|
|
|
case B_EXITED_VIEW:
|
|
DragCaret(-1);
|
|
if (fActive)
|
|
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
|
|
break;
|
|
|
|
default:
|
|
BView::MouseMoved(where, code, message);
|
|
break;
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::WindowActivated(bool state)
|
|
{
|
|
BView::WindowActivated(state);
|
|
|
|
if (state && IsFocus()) {
|
|
if (!fActive)
|
|
Activate();
|
|
}
|
|
else {
|
|
if (fActive)
|
|
Deactivate();
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::KeyDown(const char *bytes, int32 numBytes)
|
|
{
|
|
if (!fEditable)
|
|
return;
|
|
|
|
const char keyPressed = bytes[0];
|
|
|
|
// hide the cursor and caret
|
|
be_app->ObscureCursor();
|
|
if (fCaretVisible)
|
|
InvertCaret();
|
|
|
|
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:
|
|
// ignore, pass it up to superclass
|
|
BView::KeyDown(bytes, numBytes);
|
|
break;
|
|
|
|
default:
|
|
HandleAlphaKey(bytes, numBytes);
|
|
break;
|
|
}
|
|
|
|
// draw the caret
|
|
if (fSelStart == fSelEnd) {
|
|
if (!fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Pulse()
|
|
{
|
|
if ((fActive) && (fEditable) && (fSelStart == fSelEnd)) {
|
|
if (system_time() > (fCaretTime + 500000.0))
|
|
InvertCaret();
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::FrameResized(float width, float height)
|
|
{
|
|
BView::FrameResized(width, height);
|
|
|
|
UpdateScrollbars();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::MakeFocus(bool focusState)
|
|
{
|
|
BView::MakeFocus(focusState);
|
|
|
|
if (focusState && Window()->IsActive()) {
|
|
if (!fActive)
|
|
Activate();
|
|
}
|
|
else {
|
|
if (fActive)
|
|
Deactivate();
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::MessageReceived(BMessage *message)
|
|
{
|
|
// was this message dropped?
|
|
if (message->WasDropped()) {
|
|
BPoint dropLoc;
|
|
BPoint offset;
|
|
|
|
dropLoc = message->DropPoint(&offset);
|
|
ConvertFromScreen(&dropLoc);
|
|
ConvertFromScreen(&offset);
|
|
if (!MessageDropped(message, dropLoc, offset))
|
|
BView::MessageReceived(message);
|
|
|
|
return;
|
|
}
|
|
|
|
switch (message->what) {
|
|
case B_CUT:
|
|
Cut(be_clipboard);
|
|
break;
|
|
|
|
case B_COPY:
|
|
Copy(be_clipboard);
|
|
break;
|
|
|
|
case B_PASTE:
|
|
Paste(be_clipboard);
|
|
break;
|
|
|
|
case B_UNDO:
|
|
Undo(be_clipboard);
|
|
break;
|
|
|
|
case B_SELECT_ALL:
|
|
SelectAll();
|
|
break;
|
|
|
|
case B_SET_PROPERTY:
|
|
case B_GET_PROPERTY:
|
|
case B_COUNT_PROPERTIES:
|
|
{
|
|
BPropertyInfo propInfo(prop_list);
|
|
BMessage specifier;
|
|
const char *property;
|
|
|
|
if (message->GetCurrentSpecifier(NULL, &specifier) != B_OK ||
|
|
specifier.FindString("property", &property) != B_OK)
|
|
return;
|
|
|
|
if (propInfo.FindMatch(message, 0, &specifier, specifier.what,
|
|
property) == B_ERROR)
|
|
{
|
|
BView::MessageReceived(message);
|
|
break;
|
|
}
|
|
|
|
switch(message->what)
|
|
{
|
|
case B_GET_PROPERTY:
|
|
{
|
|
BMessage reply;
|
|
|
|
GetProperty(&specifier, specifier.what, property, &reply);
|
|
|
|
message->SendReply(&reply);
|
|
|
|
break;
|
|
}
|
|
case B_SET_PROPERTY:
|
|
{
|
|
BMessage reply;
|
|
|
|
SetProperty(&specifier, specifier.what, property, &reply);
|
|
|
|
message->SendReply(&reply);
|
|
|
|
break;
|
|
}
|
|
case B_COUNT_PROPERTIES:
|
|
{
|
|
BMessage reply;
|
|
|
|
CountProperties(&specifier, specifier.what, property, &reply);
|
|
|
|
message->SendReply(&reply);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
BView::MessageReceived(message);
|
|
break;
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
BHandler *
|
|
BTextView::ResolveSpecifier(BMessage *message, int32 index,
|
|
BMessage *specifier, int32 form,
|
|
const char *property)
|
|
{
|
|
BPropertyInfo propInfo(prop_list);
|
|
BHandler *target = NULL;
|
|
|
|
switch (propInfo.FindMatch(message, 0, specifier, form, property))
|
|
{
|
|
case B_ERROR:
|
|
break;
|
|
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
target = this;
|
|
break;
|
|
}
|
|
|
|
if (!target)
|
|
target = BView::ResolveSpecifier(message, index, specifier, form,
|
|
property);
|
|
|
|
return target;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
status_t
|
|
BTextView::GetSupportedSuites(BMessage *data)
|
|
{
|
|
status_t err;
|
|
|
|
if (data == NULL)
|
|
return B_BAD_VALUE;
|
|
|
|
err = data->AddString("suites", "suite/vnd.Be-text-view");
|
|
|
|
if (err != B_OK)
|
|
return err;
|
|
|
|
BPropertyInfo prop_info(prop_list);
|
|
err = data->AddFlat("messages", &prop_info);
|
|
|
|
if (err != B_OK)
|
|
return err;
|
|
|
|
return BView::GetSupportedSuites(data);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
status_t
|
|
BTextView::Perform(perform_code d, void *arg)
|
|
{
|
|
return BView::Perform(d, arg);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::SetText(const char *inText, const text_run_array *inRuns)
|
|
{
|
|
// hide the caret/unhilite the selection
|
|
if (fActive) {
|
|
if (fSelStart != fSelEnd)
|
|
Highlight(fSelStart, fSelEnd);
|
|
else {
|
|
if (fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
|
|
// remove data from buffer
|
|
if (fText->Length() > 0)
|
|
DeleteText(0, fText->Length()); // TODO: was fText->Length() - 1
|
|
|
|
int len = (inText) ? strlen(inText) : 0;
|
|
|
|
if (inText != NULL && len > 0)
|
|
InsertText(inText, len, 0, inRuns);
|
|
|
|
fSelStart = fSelEnd = 0;
|
|
|
|
// recalc line breaks and draw the text
|
|
Refresh(0, len, true, true);
|
|
|
|
// draw the caret
|
|
if (fActive) {
|
|
if (!fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::SetText(const char *inText, int32 inLength,
|
|
const text_run_array *inRuns)
|
|
{
|
|
// hide the caret/unhilite the selection
|
|
if (fActive) {
|
|
if (fSelStart != fSelEnd)
|
|
Highlight(fSelStart, fSelEnd);
|
|
else {
|
|
if (fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
|
|
// remove data from buffer
|
|
if (fText->Length() > 0)
|
|
DeleteText(0, fText->Length()); // TODO: was fText->Length() - 1
|
|
|
|
if (inText != NULL && inLength > 0)
|
|
InsertText(inText, inLength, 0, inRuns);
|
|
|
|
fSelStart = fSelEnd = 0;
|
|
|
|
// recalc line breaks and draw the text
|
|
Refresh(0, inLength, true, true);
|
|
|
|
// draw the caret
|
|
if (fActive) {
|
|
if (!fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::SetText (BFile *inFile, int32 startOffset, int32 inLength,
|
|
const text_run_array *inRuns)
|
|
{
|
|
// TODO:
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Insert(const char *inText, const text_run_array *inRuns)
|
|
{
|
|
Insert(fSelStart, inText, strlen(inText), inRuns);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Insert(const char *inText, int32 inLength,
|
|
const text_run_array *inRuns)
|
|
{
|
|
Insert(fSelStart, inText, inLength, inRuns);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Insert(int32 startOffset, const char *inText, int32 inLength,
|
|
const text_run_array *inRuns)
|
|
{
|
|
// do we really need to do anything?
|
|
if (inLength < 1)
|
|
return;
|
|
|
|
// hide the caret/unhilite the selection
|
|
if (fActive) {
|
|
if (fSelStart != fSelEnd)
|
|
Highlight(fSelStart, fSelEnd);
|
|
else {
|
|
if (fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
|
|
if (startOffset > TextLength())
|
|
startOffset = TextLength();
|
|
|
|
// copy data into buffer
|
|
InsertText(inText, inLength, startOffset, inRuns);
|
|
|
|
// offset the caret/selection
|
|
long saveStart = fSelStart;
|
|
fSelStart += inLength;
|
|
fSelEnd += inLength;
|
|
|
|
// recalc line breaks and draw the text
|
|
Refresh(saveStart, startOffset, true, true);
|
|
|
|
// draw the caret/hilite the selection
|
|
if (fActive) {
|
|
if (fSelStart != fSelEnd)
|
|
Highlight(fSelStart, fSelEnd);
|
|
else {
|
|
if (!fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Delete()
|
|
{
|
|
// anything to delete?
|
|
if (fSelStart == fSelEnd)
|
|
return;
|
|
|
|
// hide the caret/unhilite the selection
|
|
if (fActive) {
|
|
if (fSelStart != fSelEnd)
|
|
Highlight(fSelStart, fSelEnd);
|
|
else {
|
|
if (fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
|
|
// remove data from buffer
|
|
DeleteText(fSelStart, fSelEnd);
|
|
|
|
// collapse the selection
|
|
fSelEnd = fSelStart;
|
|
|
|
// recalc line breaks and draw what's left
|
|
Refresh(fSelStart, fSelEnd, true, true);
|
|
|
|
// draw the caret
|
|
if (fActive) {
|
|
if (!fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Delete(int32 startOffset, int32 endOffset)
|
|
{
|
|
// anything to delete?
|
|
if (startOffset == endOffset)
|
|
return;
|
|
|
|
// hide the caret/unhilite the selection
|
|
if (fActive) {
|
|
if (startOffset != endOffset)
|
|
Highlight(startOffset, endOffset);
|
|
else {
|
|
if (fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
|
|
// remove data from buffer
|
|
DeleteText(startOffset, endOffset);
|
|
|
|
// recalc line breaks and draw what's left
|
|
Refresh(startOffset, endOffset, true, true);
|
|
|
|
// draw the caret
|
|
if (fActive) {
|
|
if (!fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
const char *
|
|
BTextView::Text() const
|
|
{
|
|
return fText->Text();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
int32
|
|
BTextView::TextLength() const
|
|
{
|
|
return fText->Length();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::GetText(int32 offset, int32 length, char *buffer) const
|
|
{
|
|
int32 textLen = fText->Length();
|
|
if ((offset < 0) || (offset > (textLen - 1))) {
|
|
buffer[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
length = ((offset + length) > textLen) ? textLen - offset : length;
|
|
for (int32 i = 0; i < length; i++)
|
|
buffer[i] = (*fText)[i + offset];
|
|
buffer[length] = '\0';
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
uchar
|
|
BTextView::ByteAt(int32 offset) const
|
|
{
|
|
if ((offset < 0) || (offset > (fText->Length() - 1)))
|
|
return ('\0');
|
|
|
|
return (*fText)[offset];
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
int32
|
|
BTextView::CountLines() const
|
|
{
|
|
return fLines->NumLines();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
int32
|
|
BTextView::CurrentLine() const
|
|
{
|
|
return LineAt(fSelStart);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::GoToLine(int32 index)
|
|
{
|
|
fSelStart = fSelEnd = OffsetAt(index);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Cut(BClipboard *clipboard)
|
|
{
|
|
if (fUndo) {
|
|
delete fUndo;
|
|
fUndo = new _BCutUndoBuffer_(this);
|
|
}
|
|
Copy(clipboard);
|
|
Delete();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Copy(BClipboard *clipboard)
|
|
{
|
|
BMessage *clip = NULL;
|
|
|
|
if (clipboard->Lock()) {
|
|
clipboard->Clear();
|
|
|
|
if ((clip = clipboard->Data()) != NULL) {
|
|
clip->AddData("text/plain", B_MIME_TYPE, Text() + fSelStart,
|
|
fSelEnd - fSelStart);
|
|
|
|
int32 size;
|
|
if (IsStylable()) {
|
|
text_run_array *runArray = RunArray(fSelStart, fSelEnd, &size);
|
|
clip->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE,
|
|
runArray, size);
|
|
free(runArray);
|
|
}
|
|
clipboard->Commit();
|
|
}
|
|
clipboard->Unlock();
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Paste(BClipboard *clipboard)
|
|
{
|
|
BMessage *clip = NULL;
|
|
|
|
if (clipboard->Lock()) {
|
|
if ((clip = clipboard->Data()) != NULL) {
|
|
const char *text = NULL;
|
|
ssize_t len = 0;
|
|
|
|
if (clip->FindData("text/plain", B_MIME_TYPE,
|
|
(const void **)&text, &len) == B_OK) {
|
|
text_run_array *runArray = NULL;
|
|
ssize_t runLen = 0;
|
|
|
|
if (IsStylable())
|
|
clip->FindData("application/x-vnd.Be-text_run_array", B_MIME_TYPE,
|
|
(const void **)&runArray, &runLen);
|
|
|
|
if (fUndo) {
|
|
delete fUndo;
|
|
fUndo = new _BPasteUndoBuffer_(this, text, len, runArray, runLen);
|
|
}
|
|
if (fSelStart != fSelEnd)
|
|
Delete();
|
|
|
|
Insert(text, len, runArray);
|
|
}
|
|
}
|
|
|
|
clipboard->Unlock();
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Clear()
|
|
{
|
|
Delete();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool
|
|
BTextView::AcceptsPaste(BClipboard *clipboard)
|
|
{
|
|
if (!fEditable)
|
|
return false;
|
|
|
|
bool result = false;
|
|
|
|
if (clipboard->Lock()) {
|
|
BMessage *data = clipboard->Data();
|
|
result = data && data->HasData("text/plain", B_MIME_TYPE);
|
|
clipboard->Unlock();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool
|
|
BTextView::AcceptsDrop(const BMessage *inMessage)
|
|
{
|
|
if (fEditable && inMessage->HasData("text/plain", B_MIME_TYPE))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Select(int32 startOffset, int32 endOffset)
|
|
{
|
|
if (!fSelectable)
|
|
return;
|
|
|
|
// a negative selection?
|
|
if (startOffset > endOffset)
|
|
return;
|
|
|
|
// is the new selection any different from the current selection?
|
|
if ((startOffset == fSelStart) && (endOffset == fSelEnd))
|
|
return;
|
|
|
|
fStyles->InvalidateNullStyle();
|
|
|
|
// pin offsets at reasonable values
|
|
startOffset = (startOffset < 0) ? 0 : startOffset;
|
|
endOffset = (endOffset < 0) ? 0 : endOffset;
|
|
endOffset = (endOffset > fText->Length()) ? fText->Length() : endOffset;
|
|
|
|
// hide the caret
|
|
if (fCaretVisible)
|
|
InvertCaret();
|
|
|
|
if (startOffset == endOffset) {
|
|
if (fSelStart != fSelEnd) {
|
|
// unhilite the selection
|
|
if (fActive)
|
|
Highlight(fSelStart, fSelEnd);
|
|
}
|
|
fSelStart = fSelEnd = startOffset;
|
|
}
|
|
else {
|
|
if (fActive) {
|
|
// draw only those ranges that are different
|
|
long start, end;
|
|
if (startOffset != fSelStart) {
|
|
// start of selection has changed
|
|
if (startOffset > fSelStart) {
|
|
start = fSelStart;
|
|
end = startOffset;
|
|
}
|
|
else {
|
|
start = startOffset;
|
|
end = fSelStart;
|
|
}
|
|
Highlight(start, end);
|
|
}
|
|
|
|
if (endOffset != fSelEnd) {
|
|
// end of selection has changed
|
|
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 *outStart, int32 *outEnd) const
|
|
{
|
|
if (fSelectable) {
|
|
*outStart = fSelStart;
|
|
*outEnd = fSelEnd;
|
|
} else {
|
|
*outStart = 0;
|
|
*outEnd = 0;
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::SetFontAndColor(const BFont *inFont, uint32 inMode,
|
|
const rgb_color *inColor)
|
|
{
|
|
// hide the caret/unhilite the selection
|
|
if (fActive) {
|
|
if (fSelStart != fSelEnd)
|
|
Highlight(fSelStart, fSelEnd);
|
|
else {
|
|
if (fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
|
|
BFont newFont = *inFont;
|
|
NormalizeFont(&newFont);
|
|
|
|
// add the style to the style buffer
|
|
fStyles->SetStyleRange(fSelStart, fSelEnd, fText->Length(),
|
|
inMode, &newFont, inColor);
|
|
|
|
if ((inMode & doFont) || (inMode & doSize))
|
|
// recalc the line breaks and redraw with new style
|
|
Refresh(fSelStart, fSelEnd, fSelStart != fSelEnd, false);
|
|
else
|
|
// the line breaks wont change, simply redraw
|
|
DrawLines(LineAt(fSelStart), LineAt(fSelEnd), fSelStart, true);
|
|
|
|
// draw the caret/hilite the selection
|
|
if (fActive) {
|
|
if (fSelStart != fSelEnd)
|
|
Highlight(fSelStart, fSelEnd);
|
|
else {
|
|
if (!fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::SetFontAndColor(int32 startOffset, int32 endOffset,
|
|
const BFont *inFont, uint32 inMode,
|
|
const rgb_color *inColor)
|
|
{
|
|
// hide the caret/unhilite the selection
|
|
if (fActive) {
|
|
if (startOffset != endOffset)
|
|
Highlight(startOffset, endOffset);
|
|
else {
|
|
if (fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
|
|
BFont newFont = *inFont;
|
|
NormalizeFont(&newFont);
|
|
|
|
// add the style to the style buffer
|
|
fStyles->SetStyleRange(startOffset, endOffset, fText->Length(),
|
|
inMode, &newFont, inColor);
|
|
|
|
if ((inMode & doFont) || (inMode & doSize))
|
|
// recalc the line breaks and redraw with new style
|
|
Refresh(startOffset, endOffset, startOffset != endOffset, false);
|
|
else
|
|
// the line breaks wont change, simply redraw
|
|
DrawLines(LineAt(startOffset), LineAt(endOffset), startOffset, true);
|
|
|
|
// draw the caret/hilite the selection
|
|
if (fActive) {
|
|
if (startOffset != endOffset)
|
|
Highlight(startOffset, endOffset);
|
|
else {
|
|
if (!fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::GetFontAndColor(int32 inOffset, BFont *outFont,
|
|
rgb_color *outColor) const
|
|
{
|
|
fStyles->GetStyle(inOffset, outFont, outColor);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::GetFontAndColor(BFont *outFont, uint32 *outMode,
|
|
rgb_color *outColor, bool *outEqColor) const
|
|
{
|
|
// TODO fill in outMode and outEqColor
|
|
fStyles->GetStyle(fSelStart, outFont, outColor);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::SetRunArray(int32 startOffset, int32 endOffset,
|
|
const text_run_array *inRuns)
|
|
{
|
|
// pin offsets at reasonable values
|
|
startOffset = (startOffset < 0) ? 0 : startOffset;
|
|
endOffset = (endOffset < 0) ? 0 : endOffset;
|
|
endOffset = (endOffset > fText->Length()) ? fText->Length() : endOffset;
|
|
|
|
long numStyles = inRuns->count;
|
|
if (numStyles > 0) {
|
|
int32 textLength = fText->Length();
|
|
const text_run *theRun = &inRuns->runs[0];
|
|
for (long index = 0; index < numStyles; index++) {
|
|
long fromOffset = theRun->offset + startOffset;
|
|
long toOffset = endOffset;
|
|
if ((index + 1) < numStyles) {
|
|
toOffset = (theRun + 1)->offset + startOffset;
|
|
toOffset = (toOffset > endOffset) ? endOffset : toOffset;
|
|
}
|
|
|
|
fStyles->SetStyleRange(fromOffset, toOffset, textLength,
|
|
doAll, &theRun->font, &theRun->color);
|
|
|
|
theRun++;
|
|
}
|
|
fStyles->InvalidateNullStyle();
|
|
|
|
Refresh(startOffset, endOffset, true, false);
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
text_run_array *
|
|
BTextView::RunArray(int32 startOffset, int32 endOffset,
|
|
int32 *outSize) const
|
|
{
|
|
STEStyleRangePtr result = fStyles->GetStyleRange(startOffset, endOffset - 1);
|
|
|
|
if (result == NULL)
|
|
return NULL;
|
|
|
|
text_run_array *res = (text_run_array*)malloc(sizeof(int32) +
|
|
(sizeof(text_run) * result->count));
|
|
|
|
res->count = result->count;
|
|
|
|
for (int32 i = 0; i < res->count; i++) {
|
|
res->runs[i].offset = result->runs[i].offset;
|
|
res->runs[i].font = &result->runs[i].style.font;
|
|
res->runs[i].color = result->runs[i].style.color;
|
|
}
|
|
|
|
if (outSize != NULL)
|
|
*outSize = sizeof(int32) + (sizeof(text_run) * res->count);
|
|
|
|
return res;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
int32
|
|
BTextView::LineAt(int32 offset) const
|
|
{
|
|
return fLines->OffsetToLine(offset);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
int32
|
|
BTextView::LineAt(BPoint point) const
|
|
{
|
|
return fLines->PixelToLine(point.y - fTextRect.top);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
BPoint
|
|
BTextView::PointAt(int32 inOffset, float *outHeight) const
|
|
{
|
|
BPoint result;
|
|
int32 textLength = fText->Length();
|
|
int32 lineNum = LineAt(inOffset);
|
|
STELinePtr line = (*fLines)[lineNum];
|
|
float height = (line + 1)->origin - line->origin;
|
|
|
|
result.x = 0.0;
|
|
result.y = line->origin + fTextRect.top;
|
|
|
|
// special case: go down one line if inOffset is a newline
|
|
if ((inOffset == textLength) && ((*fText)[textLength - 1] == '\n')) {
|
|
float ascent, descent;
|
|
StyledWidth(inOffset, 1, &ascent, &descent);
|
|
|
|
result.y += height;
|
|
height = ascent + descent;
|
|
}
|
|
else {
|
|
int32 offset = line->offset;
|
|
int32 length = inOffset - line->offset;
|
|
int32 numChars = length;
|
|
bool foundTab = false;
|
|
do {
|
|
foundTab = fText->FindChar('\t', offset, &numChars);
|
|
|
|
result.x += StyledWidth(offset, numChars);
|
|
|
|
if (foundTab) {
|
|
result.x += fTabWidth - fmod(result.x, fTabWidth);
|
|
numChars++;
|
|
}
|
|
|
|
offset += numChars;
|
|
length -= numChars;
|
|
numChars = length;
|
|
} while ((foundTab) && (length > 0));
|
|
}
|
|
|
|
// convert from text rect coordinates
|
|
result.x += fTextRect.left - 1.0;
|
|
|
|
// round up
|
|
result.x = ceil(result.x);
|
|
result.y = ceil(result.y);
|
|
if (outHeight != NULL)
|
|
*outHeight = height;
|
|
|
|
return result;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
int32
|
|
BTextView::OffsetAt(BPoint point) const
|
|
{
|
|
// should we even bother?
|
|
if (point.y >= fTextRect.bottom)
|
|
return (fText->Length());
|
|
else {
|
|
if (point.y < fTextRect.top)
|
|
return (0);
|
|
}
|
|
|
|
int32 lineNum = LineAt(point);
|
|
STELinePtr line = (*fLines)[lineNum];
|
|
|
|
// special case: if point is within the text rect and PixelToLine()
|
|
// tells us that it's on the last line, but if point is actually
|
|
// lower than the bottom of the last line, return the last offset
|
|
// (can happen for newlines)
|
|
if (lineNum == (fLines->NumLines() - 1)) {
|
|
if (point.y >= ((line + 1)->origin + fTextRect.top))
|
|
return (fText->Length());
|
|
}
|
|
|
|
// convert to text rect coordinates
|
|
point.x -= fTextRect.left;
|
|
point.x = (point.x < 0.0) ? 0.0 : point.x;
|
|
|
|
// do a pseudo-binary search of the character widths on the line
|
|
// that PixelToLine() gave us
|
|
// note: the right half of a character returns its offset + 1
|
|
int32 offset = line->offset;
|
|
int32 saveOffset = offset;
|
|
int32 delta = 0;
|
|
int32 limit = (line + 1)->offset;
|
|
int32 length = limit - line->offset;
|
|
float sigmaWidth = 0.0;
|
|
float tabWidth = 0.0;
|
|
int32 numChars = length;
|
|
bool done = false;
|
|
bool foundTab = false;
|
|
do {
|
|
saveOffset = offset;
|
|
|
|
if (foundTab) {
|
|
// is the point in the right-half of the tab?
|
|
if ((point.x >= (sigmaWidth - (tabWidth / 2))) &&
|
|
(point.x < sigmaWidth))
|
|
break;
|
|
}
|
|
|
|
// any more tabs?
|
|
foundTab = fText->FindChar('\t', offset, &numChars);
|
|
|
|
delta = numChars / 2;
|
|
delta = (delta < 1) ? 1 : delta;
|
|
|
|
if (numChars > 1) {
|
|
do {
|
|
float deltaWidth = StyledWidth(offset, delta);
|
|
float leftWidth = StyledWidth(offset + delta - 1, 1);
|
|
sigmaWidth += deltaWidth;
|
|
|
|
if (point.x >= (sigmaWidth - (leftWidth / 2))) {
|
|
// we're to the left of the point
|
|
float rightWidth = StyledWidth(offset + delta, 1);
|
|
if (point.x < (sigmaWidth + (rightWidth / 2))) {
|
|
// the next character is to the right, we're done!
|
|
offset += delta;
|
|
done = true;
|
|
break;
|
|
}
|
|
else {
|
|
// still too far to the left, measure some more
|
|
offset += delta;
|
|
delta /= 2;
|
|
delta = (delta < 1) ? 1 : delta;
|
|
}
|
|
}
|
|
else {
|
|
// oops, we overshot the point, go back some
|
|
sigmaWidth -= deltaWidth;
|
|
|
|
if (delta == 1) {
|
|
done = true;
|
|
break;
|
|
}
|
|
|
|
delta /= 2;
|
|
delta = (delta < 1) ? 1 : delta;
|
|
|
|
}
|
|
} while (offset < (numChars + saveOffset));
|
|
}
|
|
|
|
if (done || (offset >= limit))
|
|
break;
|
|
|
|
if (foundTab) {
|
|
tabWidth = fTabWidth - fmod(sigmaWidth, fTabWidth);
|
|
|
|
// is the point in the left-half of the tab?
|
|
if (point.x < (sigmaWidth + (tabWidth / 2)))
|
|
break;
|
|
|
|
sigmaWidth += tabWidth;
|
|
numChars++;
|
|
}
|
|
|
|
offset = saveOffset + numChars;
|
|
length -= numChars;
|
|
numChars = length;
|
|
} while ((foundTab) && (length > 0));
|
|
|
|
if (offset == (line + 1)->offset) {
|
|
// special case: newlines aren't visible
|
|
// return the offset of the character preceding the newline
|
|
if ((*fText)[offset - 1] == '\n')
|
|
return (--offset);
|
|
|
|
// special case: return the offset preceding any spaces that
|
|
// aren't at the end of the buffer
|
|
if ((offset != fText->Length()) && ((*fText)[offset - 1] == ' '))
|
|
return (--offset);
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
int32
|
|
BTextView::OffsetAt(int32 line) const
|
|
{
|
|
if (line > fLines->NumLines())
|
|
return fText->Length();
|
|
|
|
return (*fLines)[line]->offset;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
enum { B_SEPARATOR_CHARACTER, B_OTHER_CHARACTER };
|
|
|
|
void
|
|
BTextView::FindWord(int32 inOffset, int32 *outFromOffset,
|
|
int32 *outToOffset)
|
|
{
|
|
int32 offset;
|
|
|
|
// check to the left
|
|
for (offset = inOffset; offset > 0; offset--) {
|
|
if (CharClassification(offset - 1) == B_SEPARATOR_CHARACTER)
|
|
break;
|
|
}
|
|
|
|
*outFromOffset = offset;
|
|
|
|
// check to the right
|
|
int32 textLen = TextLength();
|
|
for (offset = inOffset; offset < textLen; offset++) {
|
|
if (CharClassification(offset) == B_SEPARATOR_CHARACTER)
|
|
break;
|
|
}
|
|
|
|
*outToOffset = offset;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool
|
|
BTextView::CanEndLine(int32 offset)
|
|
{
|
|
switch ((*fText)[offset]) {
|
|
case '\0':
|
|
case '\t':
|
|
case '\n':
|
|
case ' ':
|
|
case '&':
|
|
case '*':
|
|
case '+':
|
|
case '-':
|
|
case '/':
|
|
case '<':
|
|
case '=':
|
|
case '>':
|
|
case '\\':
|
|
case '^':
|
|
case '|':
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
float
|
|
BTextView::LineWidth(int32 lineNum) const
|
|
{
|
|
if (lineNum < 0)
|
|
return (*fLines)[0]->width;
|
|
else if (lineNum > fLines->fItemCount - 2)
|
|
return (*fLines)[fLines->fItemCount - 2]->width;
|
|
else
|
|
return (*fLines)[lineNum]->width;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
float
|
|
BTextView::LineHeight(int32 lineNum) const
|
|
{
|
|
if (lineNum < 0)
|
|
return (*fLines)[0]->ascent;
|
|
else if (lineNum > fLines->fItemCount - 2)
|
|
return (*fLines)[fLines->fItemCount - 2]->ascent;
|
|
else
|
|
return (*fLines)[lineNum]->ascent;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
float
|
|
BTextView::TextHeight(int32 startLine, int32 endLine) const
|
|
{
|
|
int32 numLines = fLines->NumLines();
|
|
startLine = (startLine < 0) ? 0 : startLine;
|
|
endLine = (endLine > numLines - 1) ? numLines - 1 : endLine;
|
|
|
|
float height = (*fLines)[endLine + 1]->origin -
|
|
(*fLines)[startLine]->origin;
|
|
|
|
if ((endLine == numLines - 1) && ((*fText)[fText->Length() - 1] == '\n'))
|
|
height += (*fLines)[endLine + 1]->origin - (*fLines)[endLine]->origin;
|
|
|
|
return height;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::GetTextRegion(int32 startOffset, int32 endOffset,
|
|
BRegion *outRegion) const
|
|
{
|
|
outRegion->MakeEmpty();
|
|
|
|
// return an empty region if the range is invalid
|
|
if (startOffset >= endOffset)
|
|
return;
|
|
|
|
float startLineHeight = 0.0;
|
|
float endLineHeight = 0.0;
|
|
BPoint startPt = PointAt(startOffset, &startLineHeight);
|
|
BPoint endPt = PointAt(endOffset, &endLineHeight);
|
|
BRect selRect;
|
|
|
|
if (startPt.y == endPt.y) {
|
|
// this is a one-line region
|
|
selRect.left = (startPt.x < fTextRect.left) ? fTextRect.left : startPt.x;
|
|
selRect.top = startPt.y;
|
|
selRect.right = endPt.x - 1.0;
|
|
selRect.bottom = endPt.y + endLineHeight - 1.0;
|
|
outRegion->Include(selRect);
|
|
}
|
|
else {
|
|
// more than one line in the specified offset range
|
|
selRect.left = (startPt.x < fTextRect.left) ? fTextRect.left : startPt.x;
|
|
selRect.top = startPt.y;
|
|
selRect.right = fTextRect.right;
|
|
selRect.bottom = startPt.y + startLineHeight - 1.0;
|
|
outRegion->Include(selRect);
|
|
|
|
if ((startPt.y + startLineHeight) < endPt.y) {
|
|
// more than two lines in the range
|
|
selRect.left = fTextRect.left;
|
|
selRect.top = startPt.y + startLineHeight;
|
|
selRect.right = fTextRect.right;
|
|
selRect.bottom = endPt.y - 1.0;
|
|
outRegion->Include(selRect);
|
|
}
|
|
|
|
selRect.left = fTextRect.left;
|
|
selRect.top = endPt.y;
|
|
selRect.right = endPt.x - 1.0;
|
|
selRect.bottom = endPt.y + endLineHeight - 1.0;
|
|
outRegion->Include(selRect);
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::ScrollToOffset(int32 inOffset)
|
|
{
|
|
BRect bounds = Bounds();
|
|
//STELinePtr line = (*fLines)[LineAt(inOffset)];
|
|
float lineHeight = 0.0;
|
|
BPoint point = PointAt(inOffset, &lineHeight);
|
|
|
|
if (ScrollBar(B_HORIZONTAL) != NULL) {
|
|
if ((point.x < bounds.left) || (point.x >= bounds.right))
|
|
ScrollBar(B_HORIZONTAL)->SetValue(point.x - (bounds.IntegerWidth() / 2));
|
|
}
|
|
|
|
if (ScrollBar(B_VERTICAL) != NULL) {
|
|
if ((point.y < bounds.top) || ((point.y + lineHeight) >= bounds.bottom))
|
|
ScrollBar(B_VERTICAL)->SetValue(point.y - (bounds.IntegerHeight() / 2));
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::ScrollToSelection()
|
|
{
|
|
ScrollToOffset(fSelStart);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Highlight(int32 startOffset, int32 endOffset)
|
|
{
|
|
// get real
|
|
if (startOffset >= endOffset)
|
|
return;
|
|
|
|
BRegion selRegion;
|
|
GetTextRegion(startOffset, endOffset, &selRegion);
|
|
|
|
ConstrainClippingRegion(&selRegion);
|
|
InvertRect(selRegion.Frame());
|
|
ConstrainClippingRegion(NULL);
|
|
|
|
Flush(); ////
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::SetTextRect(BRect rect)
|
|
{
|
|
if (rect == fTextRect)
|
|
return;
|
|
|
|
fTextRect = rect;
|
|
|
|
if (Window() != NULL)
|
|
Refresh(0, LONG_MAX, true, false);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
BRect
|
|
BTextView::TextRect() const
|
|
{
|
|
return fTextRect;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
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, LONG_MAX, true, false);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
float
|
|
BTextView::TabWidth() const
|
|
{
|
|
return fTabWidth;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::MakeSelectable(bool selectable)
|
|
{
|
|
if (selectable == fSelectable)
|
|
return;
|
|
|
|
fSelectable = selectable;
|
|
|
|
if (Window() != NULL) {
|
|
if (fActive) {
|
|
// show/hide the caret, hilite/unhilite the selection
|
|
if (fSelStart != fSelEnd)
|
|
Highlight(fSelStart, fSelEnd);
|
|
else
|
|
InvertCaret();
|
|
}
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool
|
|
BTextView::IsSelectable() const
|
|
{
|
|
return fSelectable;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::MakeEditable(bool editable)
|
|
{
|
|
if (editable == fEditable)
|
|
return;
|
|
|
|
fEditable = editable;
|
|
|
|
if (Window() != NULL) {
|
|
if (fActive) {
|
|
if ((!fEditable) && (fCaretVisible))
|
|
InvertCaret();
|
|
}
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool
|
|
BTextView::IsEditable() const
|
|
{
|
|
return fEditable;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::SetWordWrap(bool wrap)
|
|
{
|
|
if (wrap == fWrap)
|
|
return;
|
|
|
|
fWrap = wrap;
|
|
|
|
if (Window() != NULL) {
|
|
if (fActive) {
|
|
// hide the caret, unhilite the selection
|
|
if (fSelStart != fSelEnd)
|
|
Highlight(fSelStart, fSelEnd);
|
|
else {
|
|
if (fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
|
|
Refresh(0, LONG_MAX, true, false);
|
|
|
|
if (fActive) {
|
|
// show the caret, hilite the selection
|
|
if (fSelStart != fSelEnd)
|
|
Highlight(fSelStart, fSelEnd);
|
|
else {
|
|
if (!fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool
|
|
BTextView::DoesWordWrap() const
|
|
{
|
|
return fWrap;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::SetMaxBytes(int32 max)
|
|
{
|
|
fMaxBytes = max;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
int32
|
|
BTextView::MaxBytes() const
|
|
{
|
|
return fMaxBytes;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::DisallowChar(uint32 aChar)
|
|
{
|
|
if (fDisallowedChars == NULL)
|
|
fDisallowedChars = new BList;
|
|
if (!fDisallowedChars->HasItem(reinterpret_cast<void *>(aChar)))
|
|
fDisallowedChars->AddItem(reinterpret_cast<void *>(aChar));
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::AllowChar(uint32 aChar)
|
|
{
|
|
if (fDisallowedChars != NULL)
|
|
fDisallowedChars->RemoveItem(reinterpret_cast<void *>(aChar));
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::SetAlignment(alignment flag)
|
|
{
|
|
fAlignment = flag;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
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)
|
|
{
|
|
fColorSpace = colors;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
color_space
|
|
BTextView::ColorSpace() const
|
|
{
|
|
return fColorSpace;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::MakeResizable(bool resize, BView *resizeView)
|
|
{
|
|
fResizable = resize;
|
|
fContainerView = resizeView;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool
|
|
BTextView::IsResizable () const
|
|
{
|
|
return fResizable;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::SetDoesUndo(bool undo)
|
|
{
|
|
if (undo && fUndo == NULL)
|
|
fUndo = new _BUndoBuffer_(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)
|
|
{
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool
|
|
BTextView::IsTypingHidden() const
|
|
{
|
|
return fText->PasswordMode();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::ResizeToPreferred()
|
|
{
|
|
float widht, height;
|
|
GetPreferredSize(&widht, &height);
|
|
BView::ResizeTo(widht, height);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::GetPreferredSize(float *width, float *height)
|
|
{
|
|
BView::GetPreferredSize(width, height);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::AllAttached()
|
|
{
|
|
BView::AllAttached();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::AllDetached()
|
|
{
|
|
BView::AllDetached();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void *
|
|
BTextView::FlattenRunArray(const text_run_array *inArray, int32 *outSize)
|
|
{
|
|
int32 size = sizeof(flattened_text_run_array) + (inArray->count - 1) *
|
|
sizeof(flattened_text_run_array);
|
|
|
|
flattened_text_run_array *array = (flattened_text_run_array*)malloc(size);
|
|
|
|
array->magic[0] = 0x41;
|
|
array->magic[1] = 0x6c;
|
|
array->magic[2] = 0x69;
|
|
array->magic[3] = 0x21;
|
|
array->version[0] = 0x00;
|
|
array->version[1] = 0x00;
|
|
array->version[2] = 0x00;
|
|
array->version[3] = 0x00;
|
|
array->count = inArray->count;
|
|
|
|
for (int32 i = 0; i < inArray->count; i++) {
|
|
array->styles[i].offset = inArray->runs[i].offset;
|
|
inArray->runs[i].font.GetFamilyAndStyle(&array->styles[i].family,
|
|
&array->styles[i].style);
|
|
array->styles[i].size = inArray->runs[i].font.Size();
|
|
array->styles[i].shear = inArray->runs[i].font.Shear();
|
|
array->styles[i].face = inArray->runs[i].font.Face();
|
|
array->styles[i].red = inArray->runs[i].color.red;
|
|
array->styles[i].green = inArray->runs[i].color.green;
|
|
array->styles[i].blue = inArray->runs[i].color.blue;
|
|
array->styles[i].alpha = 255;
|
|
array->styles[i]._reserved_ = 0;
|
|
}
|
|
|
|
if (outSize)
|
|
*outSize = size;
|
|
|
|
return array;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
text_run_array *
|
|
BTextView::UnflattenRunArray(const void *data, int32 *outSize)
|
|
{
|
|
flattened_text_run_array *array = (flattened_text_run_array*)data;
|
|
|
|
if (array->magic[0] != 0x41 || array->magic[1] != 0x6c ||
|
|
array->magic[2] != 0x69 || array->magic[3] != 0x21 ||
|
|
array->version[0] != 0x00 || array->version[1] != 0x00 ||
|
|
array->version[2] != 0x00 || array->version[3] != 0x00) {
|
|
|
|
if (outSize)
|
|
*outSize = 0;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int32 size = sizeof(text_run_array) + (array->count - 1) * sizeof(text_run);
|
|
|
|
text_run_array *run_array = (text_run_array*)malloc(size);
|
|
|
|
run_array->count = array->count;
|
|
|
|
for (int32 i = 0; i < array->count; i++) {
|
|
run_array->runs[i].font = new BFont;
|
|
run_array->runs[i].font.SetFamilyAndStyle(array->styles[i].family,
|
|
array->styles[i].style);
|
|
run_array->runs[i].font.SetSize(array->styles[i].size);
|
|
run_array->runs[i].font.SetShear(array->styles[i].shear);
|
|
run_array->runs[i].font.SetFace(array->styles[i].face);
|
|
run_array->runs[i].color.red = array->styles[i].red;
|
|
run_array->runs[i].color.green = array->styles[i].green;
|
|
run_array->runs[i].color.blue = array->styles[i].blue;
|
|
run_array->runs[i].color.alpha = array->styles[i].alpha;
|
|
}
|
|
|
|
if (outSize)
|
|
*outSize = size;
|
|
|
|
return run_array;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::InsertText(const char *inText, int32 inLength, int32 inOffset,
|
|
const text_run_array *inRuns)
|
|
{
|
|
//_ASSERT ( _CrtCheckMemory () );
|
|
|
|
// why add nothing?
|
|
if (inLength < 1)
|
|
return;
|
|
|
|
// add the text to the buffer
|
|
fText->InsertText(inText, inLength, inOffset);
|
|
|
|
// update the start offsets of each line below offset
|
|
fLines->BumpOffset(inLength, LineAt(inOffset) + 1);
|
|
|
|
// update the style runs
|
|
fStyles->BumpOffset(inLength, fStyles->OffsetToRun(inOffset - 1) + 1);
|
|
|
|
if (inRuns != NULL)
|
|
//SetStyleRange(inOffset, inOffset + inLength, inStyles, false);
|
|
SetRunArray(inOffset, inOffset + inLength, inRuns);
|
|
else {
|
|
// apply nullStyle to inserted text
|
|
fStyles->SyncNullStyle(inOffset);
|
|
fStyles->SetStyleRange(inOffset, inOffset + inLength,
|
|
fText->Length(), doAll, NULL, NULL);
|
|
}
|
|
|
|
//_ASSERT ( _CrtCheckMemory () );
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::DeleteText(int32 fromOffset, int32 toOffset)
|
|
{
|
|
//_ASSERT ( _CrtCheckMemory () );
|
|
|
|
// sanity checking
|
|
if ((fromOffset >= toOffset) || (fromOffset < 0) || (toOffset < 0))
|
|
return;
|
|
|
|
// set nullStyle to style at beginning of range
|
|
fStyles->InvalidateNullStyle();
|
|
fStyles->SyncNullStyle(fromOffset);
|
|
|
|
// remove from the text buffer
|
|
fText->RemoveRange(fromOffset, toOffset);
|
|
|
|
// remove any lines that have been obliterated
|
|
fLines->RemoveLineRange(fromOffset, toOffset);
|
|
|
|
// remove any style runs that have been obliterated
|
|
fStyles->RemoveStyleRange(fromOffset, toOffset);
|
|
|
|
//_ASSERT ( _CrtCheckMemory () );
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
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)
|
|
{
|
|
// What is this for ?
|
|
drag->AddInt32("be_actions", B_TRASH_TARGET);
|
|
|
|
// add the text
|
|
drag->AddData("text/plain", B_MIME_TYPE, fText->Text() + fSelStart,
|
|
fSelEnd - fSelStart);
|
|
|
|
// add the corresponding styles
|
|
int32 size = 0;
|
|
text_run_array *styles = RunArray(fSelStart, fSelEnd, &size);
|
|
|
|
drag->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE,
|
|
styles, size);
|
|
|
|
// add the message originator
|
|
drag->AddPointer("be:originator", this);
|
|
|
|
free(styles);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
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() {}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::InitObject(BRect textRect, const BFont *initialFont,
|
|
const rgb_color *initialColor)
|
|
{
|
|
fTextRect = textRect;
|
|
|
|
BFont font;
|
|
if (initialFont == NULL)
|
|
GetFont(&font);
|
|
else
|
|
font = *initialFont;
|
|
|
|
NormalizeFont(&font);
|
|
|
|
rgb_color black = {0, 0, 0, 255};
|
|
if (initialColor == NULL)
|
|
initialColor = &black;
|
|
|
|
fText = new _BTextGapBuffer_;
|
|
fLines = new _BLineBuffer_;
|
|
fStyles = new _BStyleBuffer_(&font, initialColor);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::HandleBackspace()
|
|
{
|
|
if (fSelStart == fSelEnd) {
|
|
if (fSelStart == 0)
|
|
return;
|
|
else
|
|
fSelStart--;
|
|
}
|
|
else
|
|
Highlight(fSelStart, fSelEnd);
|
|
|
|
if (fUndo) {
|
|
_BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>(fUndo);
|
|
if (!undoBuffer) {
|
|
delete fUndo;
|
|
fUndo = undoBuffer = new _BTypingUndoBuffer_(this);
|
|
}
|
|
undoBuffer->BackwardErase();
|
|
}
|
|
|
|
DeleteText(fSelStart, fSelEnd);
|
|
fSelEnd = fSelStart;
|
|
|
|
Refresh(fSelStart, fSelEnd, true, false);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::HandleArrowKey(uint32 inArrowKey)
|
|
{
|
|
// return if there's nowhere to go
|
|
if (fText->Length() == 0)
|
|
return;
|
|
|
|
int32 selStart = fSelStart;
|
|
int32 selEnd = fSelEnd;
|
|
//int32 delta = 0;
|
|
int32 scrollToOffset = 0;
|
|
bool shiftDown = modifiers() & B_SHIFT_KEY;
|
|
|
|
switch (inArrowKey) {
|
|
case B_LEFT_ARROW:
|
|
if (shiftDown) {
|
|
if (selStart > 0)
|
|
selStart = PreviousInitialByte(selStart);
|
|
}
|
|
else {
|
|
if (selStart == selEnd) {
|
|
if (selStart > 0)
|
|
selEnd = selStart = PreviousInitialByte(selStart);
|
|
}
|
|
else
|
|
selEnd = selStart;
|
|
}
|
|
scrollToOffset = selStart;
|
|
break;
|
|
|
|
case B_RIGHT_ARROW:
|
|
if (shiftDown) {
|
|
if (selEnd < fText->Length())
|
|
selEnd = NextInitialByte(selEnd);
|
|
}
|
|
else {
|
|
if (selStart == selEnd) {
|
|
if (selStart < fText->Length())
|
|
selStart = selEnd = NextInitialByte(selEnd);
|
|
}
|
|
else
|
|
selStart = selEnd;
|
|
}
|
|
scrollToOffset = selEnd;
|
|
break;
|
|
|
|
case B_UP_ARROW:
|
|
{
|
|
BPoint point = PointAt(selStart);
|
|
point.y--;
|
|
selStart = OffsetAt(point);
|
|
if (!shiftDown)
|
|
selEnd = selStart;
|
|
scrollToOffset = selStart;
|
|
break;
|
|
}
|
|
|
|
case B_DOWN_ARROW:
|
|
{
|
|
float height;
|
|
BPoint point = PointAt(selEnd, &height);
|
|
point.y += height;
|
|
selEnd = OffsetAt(point);
|
|
if (!shiftDown)
|
|
selStart = selEnd;
|
|
scrollToOffset = selEnd;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// invalidate the null style
|
|
fStyles->InvalidateNullStyle();
|
|
|
|
Select(selStart, selEnd);
|
|
|
|
// scroll if needed
|
|
ScrollToOffset(scrollToOffset);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::HandleDelete()
|
|
{
|
|
if (fSelStart == fSelEnd) {
|
|
if (fSelEnd == fText->Length())
|
|
return;
|
|
else
|
|
fSelEnd = NextInitialByte(fSelEnd);
|
|
}
|
|
else
|
|
Highlight(fSelStart, fSelEnd);
|
|
|
|
if (fUndo) {
|
|
_BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>(fUndo);
|
|
if (!undoBuffer) {
|
|
delete fUndo;
|
|
fUndo = undoBuffer = new _BTypingUndoBuffer_(this);
|
|
}
|
|
undoBuffer->ForwardErase();
|
|
}
|
|
|
|
DeleteText(fSelStart, fSelEnd);
|
|
|
|
fSelEnd = fSelStart;
|
|
|
|
Refresh(fSelStart, fSelEnd, true, true);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::HandlePageKey(uint32 inPageKey)
|
|
{
|
|
switch (inPageKey) {
|
|
case B_HOME:
|
|
case B_END:
|
|
ScrollToOffset((inPageKey == B_HOME) ? 0 : fText->Length());
|
|
break;
|
|
|
|
case B_PAGE_UP:
|
|
case B_PAGE_DOWN:
|
|
{
|
|
if (ScrollBar(B_VERTICAL) != NULL) {
|
|
float delta = Bounds().Height();
|
|
delta = (inPageKey == B_PAGE_UP) ? -delta : delta;
|
|
ScrollBar(B_VERTICAL)->SetValue(ScrollBar(B_VERTICAL)->Value() + delta);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::HandleAlphaKey(const char *bytes, int32 numBytes)
|
|
{
|
|
bool refresh = fSelStart != fText->Length();
|
|
|
|
if (fUndo) {
|
|
_BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>(fUndo);
|
|
if (!undoBuffer) {
|
|
delete fUndo;
|
|
fUndo = undoBuffer = new _BTypingUndoBuffer_(this);
|
|
}
|
|
undoBuffer->InputCharacter(numBytes);
|
|
}
|
|
|
|
if (fSelStart != fSelEnd) {
|
|
Highlight(fSelStart, fSelEnd);
|
|
DeleteText(fSelStart, fSelEnd);
|
|
refresh = true;
|
|
}
|
|
|
|
/* if (fAutoindent && numBytes == 1 && *bytes == B_ENTER)
|
|
{
|
|
int32 start, offset;
|
|
start = offset = OffsetAt(LineAt(fSelStart));
|
|
const char *text = Text();
|
|
|
|
while (*(text + offset) != '\0' &&
|
|
*(text + offset) == '\t' || *(text + offset) == ' ')
|
|
offset++;
|
|
|
|
if (start != offset)
|
|
InsertText(text + start, offset - start, fSelStart, NULL);
|
|
|
|
InsertText(bytes, numBytes, fSelStart, NULL);
|
|
numBytes += offset - start;
|
|
}
|
|
else*/
|
|
InsertText(bytes, numBytes, fSelStart, NULL);
|
|
|
|
fSelEnd = fSelStart = fSelStart + numBytes;
|
|
|
|
Refresh(fSelStart, fSelEnd, refresh, true);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Refresh(int32 fromOffset, int32 toOffset, bool erase,
|
|
bool scroll)
|
|
{
|
|
float saveHeight = fTextRect.Height();
|
|
int32 fromLine = LineAt(fromOffset);
|
|
int32 toLine = LineAt(toOffset);
|
|
int32 saveFromLine = fromLine;
|
|
int32 saveToLine = toLine;
|
|
float saveLineHeight = LineHeight(fromLine);
|
|
BRect bounds = Bounds();
|
|
|
|
RecalLineBreaks(&fromLine, &toLine);
|
|
|
|
float newHeight = fTextRect.Height();
|
|
|
|
// if the line breaks have changed, force an erase
|
|
if ( (fromLine != saveFromLine) || (toLine != saveToLine) ||
|
|
(newHeight != saveHeight) )
|
|
erase = true;
|
|
|
|
if (newHeight != saveHeight) {
|
|
// the text area has changed
|
|
if (newHeight < saveHeight)
|
|
toLine = LineAt(BPoint(0.0f, saveHeight + fTextRect.top));
|
|
else
|
|
toLine = LineAt(BPoint(0.0f, newHeight + fTextRect.top));
|
|
}
|
|
|
|
// draw only those lines that are visible
|
|
int32 fromVisible = LineAt(BPoint(0.0f, bounds.top));
|
|
int32 toVisible = LineAt(BPoint(0.0f, bounds.bottom));
|
|
fromLine = (fromVisible > fromLine) ? fromVisible : fromLine;
|
|
toLine = (toLine > toVisible) ? toVisible : toLine;
|
|
|
|
int32 drawOffset = fromOffset;
|
|
if ( (LineHeight(fromLine) != saveLineHeight) ||
|
|
(newHeight < saveHeight) || (fromLine < saveFromLine) )
|
|
drawOffset = (*fLines)[fromLine]->offset;
|
|
|
|
DrawLines(fromLine, toLine, drawOffset, erase);
|
|
|
|
// erase the area below the text
|
|
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);
|
|
}
|
|
|
|
// update the scroll bars if the text area has changed
|
|
if (newHeight != saveHeight)
|
|
UpdateScrollbars();
|
|
|
|
if (scroll)
|
|
ScrollToOffset(fSelEnd);
|
|
|
|
Flush(); ////
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::RecalLineBreaks(int32 *startLine, int32 *endLine)
|
|
{
|
|
// are we insane?
|
|
*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;
|
|
float width = fTextRect.Width();
|
|
STELinePtr curLine = (*fLines)[lineIndex];
|
|
STELinePtr nextLine = curLine + 1;
|
|
|
|
do {
|
|
float ascent, descent;
|
|
int32 fromOffset = curLine->offset;
|
|
int32 toOffset = FindLineBreak(fromOffset, &ascent,
|
|
&descent, &width);
|
|
|
|
// we want to advance at least by one character
|
|
if ((toOffset == fromOffset) && (fromOffset < textLength))
|
|
toOffset = NextInitialByte(toOffset);
|
|
|
|
// set the ascent of this line
|
|
curLine->ascent = ascent;
|
|
|
|
lineIndex++;
|
|
STELine saveLine = *nextLine;
|
|
if ( (lineIndex > fLines->NumLines()) ||
|
|
(toOffset < nextLine->offset) ) {
|
|
// the new line comes before the old line start, add a line
|
|
STELine newLine;
|
|
newLine.offset = toOffset;
|
|
newLine.origin = curLine->origin + ascent + descent;
|
|
newLine.ascent = 0;
|
|
fLines->InsertLine(&newLine, lineIndex);
|
|
}
|
|
else {
|
|
// update the exising line
|
|
nextLine->offset = toOffset;
|
|
nextLine->origin = curLine->origin + ascent + descent;
|
|
|
|
// remove any lines that start before the current line
|
|
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);
|
|
|
|
// update the text rect
|
|
float newHeight = TextHeight(0, fLines->NumLines() - 1);
|
|
fTextRect.bottom = fTextRect.top + newHeight;
|
|
|
|
*endLine = lineIndex - 1;
|
|
*startLine = (*startLine > *endLine) ? *endLine : *startLine;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
int32
|
|
BTextView::FindLineBreak(int32 fromOffset, float *outAscent,
|
|
float *outDescent, float *ioWidth)
|
|
{
|
|
*outAscent = 0.0;
|
|
*outDescent = 0.0;
|
|
|
|
const int32 limit = fText->Length();
|
|
|
|
// is fromOffset at the end?
|
|
if (fromOffset >= limit) {
|
|
// try to return valid height info anyway
|
|
if (fStyles->NumRuns() > 0)
|
|
fStyles->Iterate(fromOffset, 1, NULL, NULL, outAscent, outDescent);
|
|
else {
|
|
if (fStyles->IsValidNullStyle()) {
|
|
const BFont *font = NULL;
|
|
fStyles->GetNullStyle(&font, NULL);
|
|
|
|
font_height fh;
|
|
font->GetHeight(&fh);
|
|
*outAscent = fh.ascent;
|
|
*outDescent = fh.descent + fh.leading;
|
|
}
|
|
}
|
|
|
|
return limit;
|
|
}
|
|
|
|
bool done = false;
|
|
float ascent = 0.0;
|
|
float descent = 0.0;
|
|
int32 offset = fromOffset;
|
|
int32 delta = 0;
|
|
float deltaWidth = 0.0;
|
|
float tabWidth = 0.0;
|
|
float strWidth = 0.0;
|
|
|
|
// do we need to wrap the text?
|
|
if (!fWrap) {
|
|
offset = limit - fromOffset;
|
|
fText->FindChar('\n', fromOffset, &offset);
|
|
offset += fromOffset;
|
|
offset = (offset < limit) ? offset + 1 : limit;
|
|
|
|
// iterate through the style runs
|
|
int32 length = offset;
|
|
int32 startOffset = fromOffset;
|
|
int32 numChars;
|
|
while ((numChars = fStyles->Iterate(startOffset, length, NULL, NULL, &ascent, &descent) != 0)) {
|
|
*outAscent = (ascent > *outAscent) ? ascent : *outAscent;
|
|
*outDescent = (descent > *outDescent) ? descent : *outDescent;
|
|
|
|
startOffset += numChars;
|
|
length -= numChars;
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
// wrap the text
|
|
do {
|
|
bool foundTab = false;
|
|
|
|
// find the next line break candidate
|
|
for ( ; (offset + delta) < limit ; delta++) {
|
|
if (CanEndLine(offset + delta))
|
|
break;
|
|
}
|
|
for ( ; (offset + delta) < limit; delta++) {
|
|
uchar theChar = (*fText)[offset + delta];
|
|
if (!CanEndLine(offset + delta))
|
|
break;
|
|
|
|
if (theChar == '\n') {
|
|
// found a newline, we're done!
|
|
done = true;
|
|
delta++;
|
|
break;
|
|
}
|
|
else {
|
|
// include all trailing spaces and tabs,
|
|
// but not spaces after tabs
|
|
if ((theChar != ' ') && (theChar != '\t'))
|
|
break;
|
|
else {
|
|
if ((theChar == ' ') && (foundTab))
|
|
break;
|
|
else {
|
|
if (theChar == '\t')
|
|
foundTab = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
delta = (delta < 1) ? 1 : delta;
|
|
|
|
deltaWidth = StyledWidth(offset, delta, &ascent, &descent);
|
|
strWidth += deltaWidth;
|
|
|
|
if (!foundTab)
|
|
tabWidth = 0.0;
|
|
else {
|
|
int32 tabCount = 0;
|
|
for (int32 i = delta - 1; (*fText)[offset + i] == '\t'; i--)
|
|
tabCount++;
|
|
|
|
tabWidth = fTabWidth - fmod(strWidth, fTabWidth);
|
|
if (tabCount > 1)
|
|
tabWidth += ((tabCount - 1) * fTabWidth);
|
|
strWidth += tabWidth;
|
|
}
|
|
|
|
if (strWidth >= *ioWidth) {
|
|
// we've found where the line will wrap
|
|
bool foundNewline = done;
|
|
done = true;
|
|
int32 pos = delta - 1;
|
|
if (((*fText)[offset + pos] != ' ') &&
|
|
((*fText)[offset + pos] != '\t') &&
|
|
((*fText)[offset + pos] != '\n'))
|
|
break;
|
|
|
|
strWidth -= (deltaWidth + tabWidth);
|
|
|
|
for ( ; ((offset + pos) > offset); pos--) {
|
|
uchar theChar = (*fText)[offset + pos];
|
|
if ((theChar != ' ') &&
|
|
(theChar != '\t') &&
|
|
(theChar != '\n'))
|
|
break;
|
|
}
|
|
|
|
strWidth += StyledWidth(offset, pos + 1, &ascent, &descent);
|
|
if (strWidth >= *ioWidth)
|
|
break;
|
|
|
|
if (!foundNewline) {
|
|
for ( ; (offset + delta) < limit; delta++) {
|
|
if (((*fText)[offset + delta] != ' ') &&
|
|
((*fText)[offset + delta] != '\t'))
|
|
break;
|
|
}
|
|
if ( ((offset + delta) < limit) &&
|
|
((*fText)[offset + delta] == '\n') )
|
|
delta++;
|
|
}
|
|
// get the ascent and descent of the spaces/tabs
|
|
StyledWidth(offset, delta, &ascent, &descent);
|
|
}
|
|
|
|
*outAscent = (ascent > *outAscent) ? ascent : *outAscent;
|
|
*outDescent = (descent > *outDescent) ? descent : *outDescent;
|
|
|
|
offset += delta;
|
|
delta = 0;
|
|
} while ((offset < limit) && (!done));
|
|
|
|
if ((offset - fromOffset) < 1) {
|
|
// there weren't any words that fit entirely in this line
|
|
// force a break in the middle of a word
|
|
*outAscent = 0.0;
|
|
*outDescent = 0.0;
|
|
strWidth = 0.0;
|
|
|
|
for (offset = fromOffset; offset < limit; offset++) {
|
|
strWidth += StyledWidth(offset, 1, &ascent, &descent);
|
|
|
|
if (strWidth >= *ioWidth)
|
|
break;
|
|
|
|
*outAscent = (ascent > *outAscent) ? ascent : *outAscent;
|
|
*outDescent = (descent > *outDescent) ? descent : *outDescent;
|
|
}
|
|
}
|
|
|
|
offset = (offset < limit) ? offset : limit;
|
|
|
|
return offset;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
float
|
|
BTextView::StyledWidth(int32 fromOffset, int32 length, float *outAscent,
|
|
float *outDescent) const
|
|
{
|
|
float result = 0.0;
|
|
float ascent = 0.0;
|
|
float descent = 0.0;
|
|
float maxAscent = 0.0;
|
|
float maxDescent = 0.0;
|
|
|
|
// iterate through the style runs
|
|
const BFont *font = NULL;
|
|
int32 numChars;
|
|
while ((numChars = fStyles->Iterate(fromOffset, length, &font, NULL, &ascent, &descent) != 0)) {
|
|
maxAscent = (ascent > maxAscent) ? ascent : maxAscent;
|
|
maxDescent = (descent > maxDescent) ? descent : maxDescent;
|
|
|
|
result += font->StringWidth(fText->Text() + fromOffset, numChars);
|
|
|
|
fromOffset += numChars;
|
|
length -= numChars;
|
|
}
|
|
|
|
if (outAscent != NULL)
|
|
*outAscent = maxAscent;
|
|
if (outDescent != NULL)
|
|
*outDescent = maxDescent;
|
|
|
|
return result;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
float
|
|
BTextView::ActualTabWidth(float location) const
|
|
{
|
|
return 0.0f;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::DoInsertText(const char *inText, int32 inLength, int32 inOffset,
|
|
const text_run_array *inRuns,
|
|
_BTextChangeResult_ *outResult)
|
|
{
|
|
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::DoDeleteText(int32 fromOffset, int32 toOffset,
|
|
_BTextChangeResult_ *outResult)
|
|
{
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::DrawLines(int32 startLine, int32 endLine, int32 startOffset,
|
|
bool erase)
|
|
{
|
|
// clip the text
|
|
BRect clipRect = Bounds() & fTextRect;
|
|
clipRect.InsetBy(-1, -1);
|
|
BRegion newClip;
|
|
newClip.Set(clipRect);
|
|
ConstrainClippingRegion(&newClip);
|
|
|
|
// set the low color to the view color so that
|
|
// drawing to a non-white background will work
|
|
SetLowColor(ViewColor());
|
|
|
|
long maxLine = fLines->NumLines() - 1;
|
|
startLine = (startLine < 0) ? 0 : startLine;
|
|
endLine = (endLine > maxLine) ? maxLine : endLine;
|
|
|
|
BRect eraseRect = clipRect;
|
|
long startEraseLine = startLine;
|
|
STELinePtr line = (*fLines)[startLine];
|
|
|
|
if ((erase) && (startOffset != -1)) {
|
|
// erase only to the right of startOffset
|
|
startEraseLine++;
|
|
long startErase = startOffset;
|
|
if (startErase > line->offset) {
|
|
for ( ; ((*fText)[startErase] != ' ') && ((*fText)[startErase] != '\t'); startErase--) {
|
|
if (startErase <= line->offset)
|
|
break;
|
|
}
|
|
if (startErase > line->offset)
|
|
startErase--;
|
|
}
|
|
|
|
eraseRect.left = PointAt(startErase).x;
|
|
eraseRect.top = line->origin + fTextRect.top;
|
|
eraseRect.bottom = (line + 1)->origin + fTextRect.top;
|
|
|
|
FillRect(eraseRect, B_SOLID_LOW);
|
|
|
|
eraseRect = clipRect;
|
|
}
|
|
|
|
for (long i = startLine; i <= endLine; i++) {
|
|
long length = (line + 1)->offset - line->offset;
|
|
// DrawString() chokes if you draw a newline
|
|
if ((*fText)[(line + 1)->offset - 1] == '\n')
|
|
length--;
|
|
|
|
MovePenTo(fTextRect.left, line->origin + line->ascent + fTextRect.top);
|
|
|
|
if ((erase) && (i >= startEraseLine)) {
|
|
eraseRect.top = line->origin + fTextRect.top;
|
|
eraseRect.bottom = (line + 1)->origin + fTextRect.top;
|
|
|
|
FillRect(eraseRect, B_SOLID_LOW);
|
|
}
|
|
|
|
// do we have any text to draw?
|
|
if (length > 0) {
|
|
// iterate through each style on this line
|
|
//BPoint startPenLoc;
|
|
bool foundTab = false;
|
|
long tabChars = 0;
|
|
long numTabs = 0;
|
|
long offset = line->offset;
|
|
const BFont *font = NULL;
|
|
const rgb_color *color = NULL;
|
|
int32 numChars;
|
|
while ((numChars = fStyles->Iterate(offset, length, &font, &color) != 0)) {
|
|
SetFont(font);
|
|
SetHighColor(*color);
|
|
|
|
tabChars = numChars;
|
|
do {
|
|
//if (style->underline)
|
|
// startPenLoc = PenLocation();
|
|
|
|
foundTab = fText->FindChar('\t', offset, &tabChars);
|
|
if (foundTab) {
|
|
for (numTabs = 0; (tabChars + numTabs) < numChars; numTabs++) {
|
|
if ((*fText)[offset + tabChars + numTabs] != '\t')
|
|
break;
|
|
}
|
|
}
|
|
|
|
DrawString(fText->GetString(offset, tabChars), tabChars);
|
|
|
|
if (foundTab) {
|
|
float penPos = PenLocation().x - fTextRect.left;
|
|
float tabWidth = fTabWidth - fmod(penPos, fTabWidth);
|
|
if (numTabs > 1)
|
|
tabWidth += ((numTabs - 1) * fTabWidth);
|
|
|
|
MovePenBy(tabWidth, 0.0);
|
|
|
|
tabChars += numTabs;
|
|
}
|
|
|
|
/*if (style->underline) {
|
|
BPoint savePenLoc = PenLocation();
|
|
BPoint curPenLoc = savePenLoc;
|
|
startPenLoc.y += 1.0;
|
|
curPenLoc.y += 1.0;
|
|
|
|
StrokeLine(startPenLoc, curPenLoc);
|
|
|
|
MovePenTo(savePenLoc);
|
|
}*/
|
|
|
|
offset += tabChars;
|
|
length -= tabChars;
|
|
numChars -= tabChars;
|
|
tabChars = numChars;
|
|
numTabs = 0;
|
|
} while ((foundTab) && (tabChars > 0));
|
|
}
|
|
}
|
|
line++;
|
|
}
|
|
|
|
ConstrainClippingRegion(NULL);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::DrawCaret(int32 offset)
|
|
{
|
|
//long lineNum = LineAt(offset);
|
|
//STELinePtr line = (*fLines)[lineNum];
|
|
float lineHeight;
|
|
BPoint caretPoint = PointAt(offset, &lineHeight);
|
|
caretPoint.x = (caretPoint.x > fTextRect.right) ? fTextRect.right : caretPoint.x;
|
|
|
|
BRect caretRect;
|
|
caretRect.left = caretRect.right = caretPoint.x;
|
|
caretRect.top = caretPoint.y;
|
|
caretRect.bottom = caretPoint.y + lineHeight;
|
|
|
|
InvertRect(caretRect);
|
|
|
|
Flush(); ////
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::InvertCaret()
|
|
{
|
|
DrawCaret(fSelStart);
|
|
fCaretVisible = !fCaretVisible;
|
|
fCaretTime = system_time();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::DragCaret(int32 offset)
|
|
{
|
|
// does the caret need to move?
|
|
if (offset == fDragOffset)
|
|
return;
|
|
|
|
// hide the previous drag caret
|
|
if (fDragOffset != -1)
|
|
DrawCaret(fDragOffset);
|
|
|
|
// do we have a new location?
|
|
if (offset != -1) {
|
|
if (fActive) {
|
|
// ignore if offset is within active selection
|
|
if ((offset >= fSelStart) && (offset <= fSelEnd)) {
|
|
fDragOffset = -1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
DrawCaret(offset);
|
|
}
|
|
|
|
fDragOffset = offset;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::StopMouseTracking()
|
|
{
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool
|
|
BTextView::PerformMouseUp(BPoint where)
|
|
{
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool BTextView::PerformMouseMoved(BPoint where, uint32 code)
|
|
{
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::TrackMouse(BPoint where, const BMessage *message, bool force)
|
|
{
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::TrackDrag(BPoint where)
|
|
{
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::InitiateDrag()
|
|
{
|
|
BMessage *drag = new BMessage();
|
|
BBitmap *dragBitmap = NULL;
|
|
BPoint bitmapPoint;
|
|
BHandler *dragHandler = NULL;
|
|
|
|
GetDragParameters(drag, &dragBitmap, &bitmapPoint, &dragHandler);
|
|
|
|
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
|
|
|
|
if (dragBitmap != NULL)
|
|
DragMessage(drag, dragBitmap, bitmapPoint, dragHandler);
|
|
else {
|
|
BRegion hiliteRgn;
|
|
GetTextRegion(fSelStart, fSelEnd, &hiliteRgn);
|
|
BRect bounds = Bounds();
|
|
BRect dragRect = hiliteRgn.Frame();
|
|
if (!bounds.Contains(dragRect))
|
|
dragRect = bounds & dragRect;
|
|
|
|
DragMessage(drag, dragRect, dragHandler);
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool
|
|
BTextView::MessageDropped(BMessage *inMessage, BPoint where, BPoint offset)
|
|
{
|
|
if (fActive)
|
|
SetViewCursor(B_CURSOR_I_BEAM);
|
|
|
|
// are we sure we like this message?
|
|
if (!AcceptsDrop(inMessage))
|
|
return false;
|
|
|
|
long dropOffset = OffsetAt(where);
|
|
if (dropOffset > TextLength())
|
|
dropOffset = TextLength();
|
|
|
|
void *from;
|
|
inMessage->FindPointer("be:originator", &from);
|
|
|
|
bool internalDrop = (from == this && fSelEnd != fSelStart);
|
|
|
|
// if this view initiated the drag, move instead of copy
|
|
if (internalDrop) {
|
|
// dropping onto itself?
|
|
if (dropOffset >= fSelStart && dropOffset <= fSelEnd)
|
|
return true;
|
|
}
|
|
|
|
ssize_t dataLen = 0;
|
|
const char *text = NULL;
|
|
if (inMessage->FindData("text/plain", B_MIME_TYPE, (const void **)&text, &dataLen) == B_OK) {
|
|
|
|
text_run_array *runArray = NULL;
|
|
ssize_t runLen = 0;
|
|
if (IsStylable())
|
|
inMessage->FindData("application/x-vnd.Be-text_run_array", B_MIME_TYPE,
|
|
(const void **)&runArray, &runLen);
|
|
|
|
if (fUndo) {
|
|
delete fUndo;
|
|
fUndo = new _BDropUndoBuffer_(this, text, dataLen, runArray, runLen, dropOffset, internalDrop);
|
|
}
|
|
|
|
if (internalDrop)
|
|
Delete();
|
|
|
|
Insert(dropOffset, text, dataLen, runArray);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::UpdateScrollbars()
|
|
{
|
|
//BRect bounds(Bounds());
|
|
BScrollBar *hsb = ScrollBar(B_HORIZONTAL);
|
|
BScrollBar *vsb = ScrollBar(B_VERTICAL);
|
|
|
|
// do we have a horizontal scroll bar?
|
|
if (hsb != NULL) {
|
|
long viewWidth = Bounds().IntegerWidth();
|
|
long dataWidth = fTextRect.IntegerWidth();
|
|
dataWidth += (long)ceil(fTextRect.left) + 1;
|
|
|
|
long maxRange = dataWidth - viewWidth;
|
|
maxRange = (maxRange < 0) ? 0 : maxRange;
|
|
|
|
hsb->SetRange(0, (float)maxRange);
|
|
hsb->SetProportion((float)viewWidth / (float)dataWidth);
|
|
hsb->SetSteps(10, dataWidth / 10);
|
|
}
|
|
|
|
// how about a vertical scroll bar?
|
|
if (vsb != NULL) {
|
|
long viewHeight = Bounds().IntegerHeight();
|
|
long dataHeight = fTextRect.IntegerHeight();
|
|
dataHeight += (long)ceil(fTextRect.top) + 1;
|
|
|
|
long maxRange = dataHeight - viewHeight;
|
|
maxRange = (maxRange < 0) ? 0 : maxRange;
|
|
|
|
vsb->SetRange(0, maxRange);
|
|
vsb->SetProportion((float)viewHeight / (float)dataHeight);
|
|
vsb->SetSteps(12, viewHeight);
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::AutoResize(bool doredraw)
|
|
{
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::NewOffscreen(float padding)
|
|
{
|
|
if (fOffscreen)
|
|
DeleteOffscreen();
|
|
|
|
//TODO: Implement
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::DeleteOffscreen()
|
|
{
|
|
if (fOffscreen && fOffscreen->Lock()) {
|
|
delete fOffscreen;
|
|
fOffscreen = NULL;
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Activate()
|
|
{
|
|
fActive = true;
|
|
|
|
if (fSelStart != fSelEnd) {
|
|
if (fSelectable)
|
|
Highlight(fSelStart, fSelEnd);
|
|
}
|
|
else {
|
|
if (fEditable)
|
|
InvertCaret();
|
|
}
|
|
|
|
BPoint where;
|
|
ulong buttons;
|
|
GetMouse(&where, &buttons);
|
|
if (Bounds().Contains(where))
|
|
SetViewCursor(B_CURSOR_I_BEAM);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::Deactivate()
|
|
{
|
|
fActive = false;
|
|
|
|
if (fSelStart != fSelEnd) {
|
|
if (fSelectable)
|
|
Highlight(fSelStart, fSelEnd);
|
|
}
|
|
else {
|
|
if (fCaretVisible)
|
|
InvertCaret();
|
|
}
|
|
|
|
BPoint where;
|
|
ulong buttons;
|
|
GetMouse(&where, &buttons);
|
|
if (Bounds().Contains(where))
|
|
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::NormalizeFont(BFont *font)
|
|
{
|
|
font->SetRotation(0.0f);
|
|
font->SetFlags(font->Flags() & ~B_DISABLE_ANTIALIASING);
|
|
font->SetSpacing(B_BITMAP_SPACING);
|
|
font->SetEncoding(B_UNICODE_UTF8);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
uint32
|
|
BTextView::CharClassification(int32 offset) const
|
|
{
|
|
// Should check against a list of character containing also
|
|
// japanese word breakers
|
|
switch (fText->RealCharAt(offset)) {
|
|
case B_SPACE:
|
|
case '_':
|
|
case '.':
|
|
case '\0':
|
|
case '\t':
|
|
case '\n':
|
|
case '&':
|
|
case '*':
|
|
case '+':
|
|
case '-':
|
|
case '/':
|
|
case '<':
|
|
case '=':
|
|
case '>':
|
|
case '\\':
|
|
case '^':
|
|
case '|':
|
|
return B_SEPARATOR_CHARACTER;
|
|
default:
|
|
return B_OTHER_CHARACTER;
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
int32
|
|
BTextView::NextInitialByte(int32 offset) const
|
|
{
|
|
const char *text = Text();
|
|
|
|
if (text == NULL)
|
|
return 0;
|
|
|
|
for (++offset; (*(text + offset) & 0xc0) == 0x80; ++offset)
|
|
;
|
|
|
|
return offset;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
int32
|
|
BTextView::PreviousInitialByte(int32 offset) const
|
|
{
|
|
const char *text = Text();
|
|
int count = 6;
|
|
|
|
for (--offset; (text + offset) > text && count; --offset, --count) {
|
|
if ((*(text + offset) & 0xc0 ) != 0x80)
|
|
break;
|
|
}
|
|
|
|
return count ? offset : 0;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool
|
|
BTextView::GetProperty(BMessage *specifier, int32 form,
|
|
const char *property, BMessage *reply)
|
|
{
|
|
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) {
|
|
int32 index, range;
|
|
char *buffer;
|
|
|
|
specifier->FindInt32("index", &index);
|
|
specifier->FindInt32("range", &range);
|
|
|
|
buffer = new char[range + 1];
|
|
GetText(index, range, buffer);
|
|
|
|
reply->what = B_REPLY;
|
|
reply->AddString("result", buffer);
|
|
delete buffer;
|
|
reply->AddInt32("error", B_OK);
|
|
return true;
|
|
} else if (strcmp(property, "text_run_array") == 0)
|
|
return false;
|
|
else
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool
|
|
BTextView::SetProperty(BMessage *specifier, int32 form,
|
|
const char *property, BMessage *reply)
|
|
{
|
|
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;
|
|
const char *buffer;
|
|
|
|
specifier->FindInt32("index", &index);
|
|
specifier->FindInt32("range", &range);
|
|
|
|
if (specifier->FindString("data", &buffer) == B_OK)
|
|
InsertText(buffer, range, index, NULL);
|
|
else
|
|
DeleteText(index, range);
|
|
|
|
reply->what = B_REPLY;
|
|
reply->AddInt32("error", B_OK);
|
|
|
|
return true;
|
|
}
|
|
else if (strcmp(property, "text_run_array") == 0)
|
|
return false;
|
|
|
|
else
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool
|
|
BTextView::CountProperties(BMessage *specifier, int32 form,
|
|
const char *property, BMessage *reply)
|
|
{
|
|
if (strcmp(property, "Text") == 0) {
|
|
reply->what = B_REPLY;
|
|
reply->AddInt32("result", TextLength());
|
|
reply->AddInt32("error", B_OK);
|
|
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::HandleInputMethodChanged(BMessage *message)
|
|
{
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::HandleInputMethodLocationRequest()
|
|
{
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::CancelInputMethod()
|
|
{
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::LockWidthBuffer()
|
|
{
|
|
if (atomic_add(&sWidthAtom, 1) > 0)
|
|
acquire_sem(sWidthSem);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
BTextView::UnlockWidthBuffer()
|
|
{
|
|
if (atomic_add(&sWidthAtom, -1) > 1)
|
|
release_sem(sWidthSem);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
/*
|
|
* $Log $
|
|
*
|
|
* $Id $
|
|
*
|
|
*/
|