Files
haiku-beta6/src/kits/interface/BTextView/TextView.cpp
T

4527 lines
100 KiB
C++
Raw Normal View History

//------------------------------------------------------------------------------
// Copyright (c) 2001-2004, Haiku, Inc.
//
// 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
// Authors: Hiroshi Lockheimer (BTextView is based on his STEEngine)
// Marc Flerackers ([email protected])
// Stefano Ceccherini ([email protected])
// Description: BTextView displays and manages styled text.
//------------------------------------------------------------------------------
// TODOs:
// - Finish documenting this class
// - Consider using BObjectList instead of BList
// for disallowed charachters (it would remove a lot of reinterpret_casts)
// - Asynchronous mouse tracking
// - Check for correctness and possible optimizations the calls to Refresh(),
// to refresh only changed parts of text (currently we often redraw the whole text)
2004-06-24 13:46:58 +00:00
// Known Bugs:
// - visual artifacts appears when you highlight some text which spans between
// multiple lines and with mixed sizes/styles (could be something in GetTextRegion())
// Standard Includes -----------------------------------------------------------
#include <cstdlib>
#include <cstdio>
// System Includes -------------------------------------------------------------
#include <Application.h>
#include <Beep.h>
2003-10-13 14:38:43 +00:00
#include <Bitmap.h>
#include <Clipboard.h>
#include <Debug.h>
2003-08-08 09:55:12 +00:00
#include <Errors.h>
#include <Input.h>
#include <Message.h>
#include <PropertyInfo.h>
2003-08-08 09:55:12 +00:00
#include <Region.h>
#include <ScrollBar.h>
#include <TextView.h>
#include <Window.h>
2003-08-08 09:55:12 +00:00
// Local Includes --------------------------------------------------------------
#include "InlineInput.h"
2003-10-13 14:38:43 +00:00
#include "LineBuffer.h"
#include "StyleBuffer.h"
#include "TextGapBuffer.h"
#include "UndoBuffer.h"
#include "WidthBuffer.h"
2003-08-08 09:55:12 +00:00
// Local Defines ---------------------------------------------------------------
//#define TRACE_TEXTVIEW
#ifdef TRACE_TEXTVIEW
#define CALLED() printf("%s\n", __PRETTY_FUNCTION__)
#else
#define CALLED()
#endif
2004-06-24 13:46:58 +00:00
2003-08-08 09:55:12 +00:00
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 */
};
2003-08-08 09:55:12 +00:00
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];
};
enum {
B_SEPARATOR_CHARACTER,
B_OTHER_CHARACTER
} separatorCharacters;
// _BTextTrackState_ class -----------------------------------------------------
class _BTextTrackState_ {
2004-06-24 13:46:58 +00:00
// TODO: Implement ?
// It's most probably used to keep track of asynchronous mouse
// movements
public:
_BTextTrackState_(bool inSelection)
: fMoved(false),
fInSelection(inSelection)
{}
bool fMoved;
bool fInSelection;
};
2004-06-24 13:46:58 +00:00
#ifndef COMPILE_FOR_R5
// Initialized/finalized by init/fini_interface_kit
_BWidthBuffer_* BTextView::sWidths = NULL;
sem_id BTextView::sWidthSem = B_BAD_SEM_ID;
int32 BTextView::sWidthAtom = 0;
#endif
const static rgb_color kBlueInputColor = { 152, 203, 255 };
const static rgb_color kRedInputColor = { 255, 152, 152 };
2003-08-08 09:55:12 +00:00
static property_info
sPropertyList[] = {
{
"Selection",
{ B_GET_PROPERTY, 0 },
{ B_DIRECT_SPECIFIER, 0 },
"Returns the current selection.", 0,
{ B_INT32_TYPE, 0 }
},
{
"Selection",
{ B_SET_PROPERTY, 0 },
{ B_DIRECT_SPECIFIER, 0 },
"Sets the current selection.", 0,
{ B_INT32_TYPE, 0 }
},
{
"Text",
{ B_COUNT_PROPERTIES, 0 },
{ B_DIRECT_SPECIFIER, 0 },
"Returns the length of the text in bytes.", 0,
{ B_INT32_TYPE, 0 }
},
{
"Text",
{ B_GET_PROPERTY, 0 },
{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
"Returns the text in the specified range in the BTextView.", 0,
{ B_STRING_TYPE, 0 }
},
{
"Text",
{ B_SET_PROPERTY, 0 },
{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
"Removes or inserts text into the specified range in the BTextView.", 0,
{ B_STRING_TYPE, 0 }
},
{
"text_run_array",
{ B_GET_PROPERTY, 0 },
{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
"Returns the style information for the text in the specified range in the BTextView.", 0,
{ B_RAW_TYPE, 0 },
},
{
"text_run_array",
{ B_SET_PROPERTY, 0 },
{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
"Sets the style information for the text in the specified range in the BTextView.", 0,
{ B_RAW_TYPE, 0 },
},
{ 0 }
};
/*! \brief Creates a BTextView object with the given charachteristics.
*/
BTextView::BTextView(BRect frame, const char *name, BRect textRect,
uint32 resizeMask, uint32 flags)
2004-06-24 13:46:58 +00:00
: BView(frame, name, resizeMask,
flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE)
{
CALLED();
InitObject(textRect, NULL, NULL);
}
/*! \brief Creates a BTextView object with the given charachteristics.
*/
BTextView::BTextView(BRect frame, const char *name, BRect textRect,
const BFont *initialFont, const rgb_color *initialColor,
uint32 resizeMask, uint32 flags)
2004-06-24 13:46:58 +00:00
: BView(frame, name, resizeMask,
flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE)
{
CALLED();
InitObject(textRect, initialFont, initialColor);
}
2004-06-24 13:46:58 +00:00
/*! \brief Creates a BTextView object from the passed BMessage.
\param archive The BMessage from which the object shall be created.
*/
BTextView::BTextView(BMessage *archive)
: BView(archive)
{
CALLED();
2003-08-08 09:55:12 +00:00
BRect rect;
if (archive->FindRect("_trect", &rect) != B_OK)
rect.Set(0, 0, 0, 0);
InitObject(rect, NULL, NULL);
const char *text = NULL;
2003-08-08 09:55:12 +00:00
if (archive->FindString("_text", &text) == B_OK)
SetText(text);
int32 flag, flag2;
2003-08-08 09:55:12 +00:00
if (archive->FindInt32("_align", &flag) == B_OK)
SetAlignment((alignment)flag);
float value;
2003-08-08 09:55:12 +00:00
if (archive->FindFloat("_tab", &value) == B_OK)
SetTabWidth(value);
2003-08-08 09:55:12 +00:00
if (archive->FindInt32("_col_sp", &flag) == B_OK)
SetColorSpace((color_space)flag);
if (archive->FindInt32("_max", &flag) == B_OK)
2004-05-26 16:43:58 +00:00
SetMaxBytes(flag);
2003-08-08 09:55:12 +00:00
if (archive->FindInt32("_sel", &flag) == B_OK &&
archive->FindInt32("_sel", &flag2) == B_OK)
Select(flag, flag2);
bool toggle;
2003-08-08 09:55:12 +00:00
if (archive->FindBool("_stylable", &toggle) == B_OK)
SetStylable(toggle);
if (archive->FindBool("_auto_in", &toggle) == B_OK)
SetAutoindent(toggle);
if (archive->FindBool("_wrap", &toggle) == B_OK)
SetWordWrap(toggle);
if (archive->FindBool("_nsel", &toggle) == B_OK)
MakeSelectable(!toggle);
if (archive->FindBool("_nedit", &toggle) == B_OK)
MakeEditable(!toggle);
ssize_t disallowedCount = 0;
const int32 *disallowedChars = NULL;
if (archive->FindData("_dis_ch", B_RAW_TYPE,
(const void **)&disallowedChars, &disallowedCount) == B_OK) {
fDisallowedChars = new BList;
disallowedCount /= sizeof(int32);
for (int32 x = 0; x < disallowedCount; x++)
fDisallowedChars->AddItem(reinterpret_cast<void *>(disallowedChars[x]));
}
ssize_t runSize = 0;
const void *flattenedRun = NULL;
if (archive->FindData("_runs", B_RAW_TYPE, &flattenedRun, &runSize) == B_OK) {
text_run_array *runArray = UnflattenRunArray(flattenedRun, (int32 *)&runSize);
if (runArray) {
SetRunArray(0, TextLength(), runArray);
free(runArray);
}
}
}
2004-06-24 13:46:58 +00:00
/*! \brief Frees the memory allocated and destroy the object created on construction.
*/
BTextView::~BTextView()
{
CALLED();
CancelInputMethod();
DeleteOffscreen();
delete fText;
delete fLines;
2003-08-08 09:55:12 +00:00
delete fStyles;
delete fDisallowedChars;
delete fUndo;
}
2004-06-24 13:46:58 +00:00
/*! \brief Static function used to istantiate a BTextView object from the given BMessage.
\param archive The BMessage from which the object shall be created.
\return A constructed BTextView as a BArchivable object.
*/
2003-08-08 09:55:12 +00:00
BArchivable *
BTextView::Instantiate(BMessage *archive)
{
CALLED();
if (validate_instantiation(archive, "BTextView"))
return new BTextView(archive);
else
return NULL;
}
2004-06-24 13:46:58 +00:00
/*! \brief Archives the object into the passed message.
\param data A pointer to the message where to archive the object.
\param deep ?
\return \c B_OK if everything went well, an error code if not.
*/
2003-08-08 09:55:12 +00:00
status_t
BTextView::Archive(BMessage *data, bool deep) const
{
CALLED();
2003-08-08 09:55:12 +00:00
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);
2003-08-08 09:55:12 +00:00
data->AddBool("_stylable", fStylable);
data->AddBool("_auto_in", fAutoindent);
data->AddBool("_wrap", fWrap);
data->AddBool("_nsel", !fSelectable);
data->AddBool("_nedit", !fEditable);
if (fDisallowedChars != NULL)
data->AddData("_dis_ch", B_RAW_TYPE, fDisallowedChars->Items(),
fDisallowedChars->CountItems() * sizeof(int32));
int32 runSize = 0;
text_run_array *runArray = RunArray(0, TextLength());
void *flattened = FlattenRunArray(runArray, &runSize);
if (flattened != NULL) {
data->AddData("_runs", B_RAW_TYPE, flattened, runSize);
free(flattened);
} else
err = B_NO_MEMORY;
free(runArray);
2003-08-08 09:55:12 +00:00
return err;
}
2004-06-24 13:46:58 +00:00
/*! \brief Hook function called when the BTextView is added to the
window's view hierarchy.
Set the window's pulse rate to 2 per second and adjust scrollbars if needed
*/
2003-08-08 09:55:12 +00:00
void
BTextView::AttachedToWindow()
{
CALLED();
2003-08-08 09:55:12 +00:00
BView::AttachedToWindow();
SetDrawingMode(B_OP_COPY);
2004-06-24 13:46:58 +00:00
Window()->SetPulseRate(500000);
2003-08-08 09:55:12 +00:00
fCaretVisible = false;
fCaretTime = 0;
fClickCount = 0;
fClickTime = 0;
fDragOffset = -1;
fActive = false;
if (fResizable)
AutoResize(true);
2003-10-13 14:38:43 +00:00
UpdateScrollbars();
if (!fCursor)
SetViewCursor(B_CURSOR_I_BEAM);
else
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
}
2004-06-24 13:46:58 +00:00
/*! \brief Hook function called when the BTextView is removed from the
window's view hierarchy.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::DetachedFromWindow()
{
CALLED();
2003-10-13 14:38:43 +00:00
BView::DetachedFromWindow();
}
2004-06-24 13:46:58 +00:00
/*! \brief Hook function called whenever
the contents of the BTextView need to be (re)drawn.
\param updateRect The rect which needs to be redrawn
*/
2003-08-08 09:55:12 +00:00
void
BTextView::Draw(BRect updateRect)
{
CALLED();
2003-08-08 09:55:12 +00:00
// what lines need to be drawn?
int32 startLine = LineAt(BPoint(0.0f, updateRect.top));
int32 endLine = LineAt(BPoint(0.0f, updateRect.bottom));
2003-08-08 09:55:12 +00:00
DrawLines(startLine, endLine);
// draw the caret/hilite the selection
if (fActive) {
if (fSelStart != fSelEnd)
Highlight(fSelStart, fSelEnd);
else {
if (fCaretVisible)
DrawCaret(fSelStart);
}
}
}
2004-06-24 13:46:58 +00:00
/*! \brief Hook function called when a mouse button is clicked while
the cursor is in the view.
\param where The location where the mouse button has been clicked.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::MouseDown(BPoint where)
{
CALLED();
2003-08-08 09:55:12 +00:00
// should we even bother?
2003-10-13 14:38:43 +00:00
if (!fEditable && !fSelectable)
2003-08-08 09:55:12 +00:00
return;
CancelInputMethod();
if (!IsFocus())
2003-08-08 09:55:12 +00:00
MakeFocus();
// hide the caret if it's visible
if (fCaretVisible)
InvertCaret();
int32 oldOffset = fClickOffset;
fClickOffset = OffsetAt(where);
2003-10-13 14:38:43 +00:00
bool shiftDown = modifiers() & B_SHIFT_KEY;
2003-08-08 09:55:12 +00:00
2004-06-24 13:46:58 +00:00
// TODO: Asynchronous mouse tracking
2003-08-08 09:55:12 +00:00
// should we initiate a drag?
if (fSelStart != fSelEnd && !shiftDown) {
BPoint loc;
ulong buttons;
2003-08-08 09:55:12 +00:00
GetMouse(&loc, &buttons);
// TODO: here we shouldn't check for B_SECONDARY_MOUSE_BUTTON,
// since dragging works also with the primary button.
2003-08-08 09:55:12 +00:00
if (buttons == B_SECONDARY_MOUSE_BUTTON) {
// was the click within the selection range?
if (fClickOffset >= fSelStart && fClickOffset <= fSelEnd) {
2003-08-08 09:55:12 +00:00
InitiateDrag();
return;
}
}
}
// Unhighlights the selection if needed
if (fSelStart != fSelEnd)
Select(fSelStart, fSelEnd);
2003-08-08 09:55:12 +00:00
// get the system-wide click speed
bigtime_t clickSpeed = 0;
get_click_speed(&clickSpeed);
// TODO: it looks like the input server sends the number of clicks in the
// B_MOUSE_DOWN message (the "clicks" field), so we could use it instead
// of doing this mess.
2003-08-08 09:55:12 +00:00
// is this a double/triple click, or is it a new click?
if (clickSpeed > (system_time() - fClickTime) &&
oldOffset == fClickOffset ) {
2003-08-08 09:55:12 +00:00
if (fClickCount > 1) {
// triple click
fClickCount = 0;
fClickTime = 0;
2003-10-13 14:38:43 +00:00
} else {
2003-08-08 09:55:12 +00:00
// double click
fClickCount = 2;
fClickTime = system_time();
}
} else {
2003-08-08 09:55:12 +00:00
// new click
fClickCount = 1;
fClickTime = system_time();
if (!shiftDown) {
Select(fClickOffset, fClickOffset);
2003-08-08 09:55:12 +00:00
if (fEditable)
InvertCaret();
}
}
// no need to track the mouse if we can't select
if (!fSelectable)
return;
2003-08-08 09:55:12 +00:00
// track the mouse while it's down
long start = 0;
long end = 0;
long anchor = (fClickOffset > fSelStart) ? fSelStart : fSelEnd;
BPoint curMouse = where;
ulong buttons = 0;
2003-08-08 09:55:12 +00:00
do {
if (fClickOffset > anchor) {
2003-08-08 09:55:12 +00:00
start = anchor;
end = fClickOffset;
} else {
start = fClickOffset;
2003-08-08 09:55:12 +00:00
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(fClickOffset, &start, &end);
2003-08-08 09:55:12 +00:00
break;
default:
// new click, select char by char
break;
}
2003-08-08 09:55:12 +00:00
if (shiftDown) {
if (fClickOffset > anchor)
2003-08-08 09:55:12 +00:00
start = anchor;
else
2003-08-08 09:55:12 +00:00
end = anchor;
}
2003-08-08 09:55:12 +00:00
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)
2003-08-08 09:55:12 +00:00
Window()->UpdateIfNeeded();
}
2003-08-08 09:55:12 +00:00
// Zzzz...
snooze(30000);
GetMouse(&curMouse, &buttons);
fClickOffset = OffsetAt(curMouse);
2003-08-08 09:55:12 +00:00
} while (buttons != 0);
}
2004-06-24 13:46:58 +00:00
/*! \brief Hook function called when a mouse button is released while
the cursor is in the view.
\param where The point where the mouse button has been released.
Doesn't do anything useful at the moment
*/
2003-08-08 09:55:12 +00:00
void
BTextView::MouseUp(BPoint where)
{
CALLED();
2003-10-13 14:38:43 +00:00
PerformMouseUp(where);
2003-08-08 09:55:12 +00:00
}
2004-06-24 13:46:58 +00:00
/*! \brief Hook function called whenever the mouse cursor enters, exits
or moves inside the view.
\param where The point where the mouse cursor has moved to.
\param code A code which tells if the mouse entered or exited the view
\param message The message containing dragged information, if any.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::MouseMoved(BPoint where, uint32 code, const BMessage *message)
{
CALLED();
bool sync = false;
2003-08-08 09:55:12 +00:00
switch (code) {
// We force a sync when the mouse enters the view
2003-08-08 09:55:12 +00:00
case B_ENTERED_VIEW:
sync = true;
// supposed to fall through
2003-08-08 09:55:12 +00:00
case B_INSIDE_VIEW:
TrackMouse(where, message, sync);
2003-08-08 09:55:12 +00:00
break;
case B_EXITED_VIEW:
DragCaret(-1);
if (Window()->IsActive() && message == NULL)
2003-10-13 14:38:43 +00:00
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
2003-08-08 09:55:12 +00:00
break;
default:
BView::MouseMoved(where, code, message);
break;
}
}
2004-06-24 13:46:58 +00:00
/*! \brief Hook function called when the window becomes the active window
or gives up that status.
\param state If true, window has just become active, if false, window has
just become inactive.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::WindowActivated(bool state)
{
CALLED();
2003-08-08 09:55:12 +00:00
BView::WindowActivated(state);
2003-08-08 09:55:12 +00:00
if (state && IsFocus()) {
if (!fActive)
Activate();
} else {
2003-08-08 09:55:12 +00:00
if (fActive)
Deactivate();
}
BPoint where;
ulong buttons;
GetMouse(&where, &buttons, true);
if (Bounds().Contains(where))
TrackMouse(where, NULL);
}
2004-06-24 13:46:58 +00:00
/*! \brief Hook function called whenever a key is pressed while the view is
the focus view of the active window.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::KeyDown(const char *bytes, int32 numBytes)
{
CALLED();
// TODO: Remove this and move it to specific key handlers ?
2004-08-17 13:52:18 +00:00
// moreover, we should check if ARROW keys work in case the object
// isn't editable
2003-08-08 09:55:12 +00:00
if (!fEditable)
return;
2003-10-13 14:38:43 +00:00
const char keyPressed = bytes[0];
// if the charachter is not allowed, bail out.
if (fDisallowedChars
&& fDisallowedChars->HasItem(reinterpret_cast<void *>((uint32)keyPressed))) {
beep();
return;
}
2003-08-08 09:55:12 +00:00
// hide the cursor and caret
be_app->ObscureCursor();
if (fCaretVisible)
InvertCaret();
2003-10-13 14:38:43 +00:00
switch (keyPressed) {
case B_BACKSPACE:
HandleBackspace();
break;
2003-08-08 09:55:12 +00:00
case B_LEFT_ARROW:
case B_RIGHT_ARROW:
2003-08-08 09:55:12 +00:00
case B_UP_ARROW:
case B_DOWN_ARROW:
2003-10-13 14:38:43 +00:00
HandleArrowKey(keyPressed);
break;
2003-08-08 09:55:12 +00:00
case B_DELETE:
HandleDelete();
break;
2003-08-08 09:55:12 +00:00
case B_HOME:
case B_END:
case B_PAGE_UP:
case B_PAGE_DOWN:
2003-10-13 14:38:43 +00:00
HandlePageKey(keyPressed);
2003-08-08 09:55:12 +00:00
break;
case B_ESCAPE:
case B_INSERT:
case B_FUNCTION_KEY:
// ignore, pass it up to superclass
BView::KeyDown(bytes, numBytes);
break;
2003-08-08 09:55:12 +00:00
default:
HandleAlphaKey(bytes, numBytes);
2003-08-08 09:55:12 +00:00
break;
}
// draw the caret
if (fSelStart == fSelEnd) {
if (!fCaretVisible)
InvertCaret();
}
}
2004-06-24 13:46:58 +00:00
/*! \brief Hook function called every x microseconds.
It's the function which makes the caret blink.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::Pulse()
{
CALLED();
if (fActive && fEditable && fSelStart == fSelEnd) {
2004-06-24 13:46:58 +00:00
// TODO: Is this check needed ?
2003-08-08 09:55:12 +00:00
if (system_time() > (fCaretTime + 500000.0))
InvertCaret();
}
}
2004-06-24 13:46:58 +00:00
/*! \brief Hook function called when the view's frame is resized.
\param width The new view's width.
\param height The new view's height.
Updates the associated scrollbars.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::FrameResized(float width, float height)
{
CALLED();
2003-08-08 09:55:12 +00:00
BView::FrameResized(width, height);
UpdateScrollbars();
}
2004-06-24 13:46:58 +00:00
/*! \brief Highlight/unhighlight the selection when the view gets
or looses the focus.
\param focusState The focus state: true, if the view is getting the focus,
false otherwise.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::MakeFocus(bool focusState)
{
CALLED();
BView::MakeFocus(focusState);
2003-08-08 09:55:12 +00:00
if (focusState && Window()->IsActive()) {
if (!fActive)
Activate();
} else {
2003-08-08 09:55:12 +00:00
if (fActive)
Deactivate();
}
}
2004-06-24 13:46:58 +00:00
/*! \brief Hook function executed every time the BTextView gets a message.
\param message The received message
*/
2003-08-08 09:55:12 +00:00
void
BTextView::MessageReceived(BMessage *message)
{
CALLED();
2003-08-08 09:55:12 +00:00
// 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;
2003-08-08 09:55:12 +00:00
case B_COPY:
Copy(be_clipboard);
break;
2003-08-08 09:55:12 +00:00
case B_PASTE:
Paste(be_clipboard);
break;
case B_UNDO:
Undo(be_clipboard);
break;
2003-10-13 14:38:43 +00:00
case B_SELECT_ALL:
SelectAll();
break;
case B_INPUT_METHOD_EVENT:
{
int32 opcode;
if (message->FindInt32("be:opcode", &opcode) == B_OK) {
switch (opcode) {
case B_INPUT_METHOD_STARTED:
{
BMessenger messenger;
if (message->FindMessenger("be:reply_to", &messenger) == B_OK) {
ASSERT(fInline == NULL);
fInline = new _BInlineInput_(messenger);
}
break;
}
case B_INPUT_METHOD_STOPPED:
delete fInline;
fInline = NULL;
break;
case B_INPUT_METHOD_CHANGED:
HandleInputMethodChanged(message);
break;
case B_INPUT_METHOD_LOCATION_REQUEST:
HandleInputMethodLocationRequest();
break;
default:
break;
}
}
break;
}
2003-08-08 09:55:12 +00:00
case B_SET_PROPERTY:
case B_GET_PROPERTY:
case B_COUNT_PROPERTIES:
{
BPropertyInfo propInfo(sPropertyList);
2003-08-08 09:55:12 +00:00
BMessage specifier;
const char *property;
2003-08-08 09:55:12 +00:00
if (message->GetCurrentSpecifier(NULL, &specifier) != B_OK ||
specifier.FindString("property", &property) != B_OK)
return;
2003-08-08 09:55:12 +00:00
if (propInfo.FindMatch(message, 0, &specifier, specifier.what,
property) == B_ERROR) {
2003-08-08 09:55:12 +00:00
BView::MessageReceived(message);
break;
}
switch(message->what) {
2003-08-08 09:55:12 +00:00
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;
}
default:
break;
}
break;
}
2004-08-17 13:52:18 +00:00
// TODO: Find out what these two do
case _PING_:
case _DISPOSE_DRAG_:
break;
default:
BView::MessageReceived(message);
2003-08-08 09:55:12 +00:00
break;
}
}
2004-06-24 13:46:58 +00:00
/*! \brief Returns the proper handler for the given scripting message.
\param message The scripting message which needs to be examined.
\param index The index of the specifier
\param specifier The message which contains the specifier
\param what The 'what' field of the specifier message.
\param property The name of the targetted property
\return The proper BHandler for the given scripting message.
*/
2003-08-08 09:55:12 +00:00
BHandler *
BTextView::ResolveSpecifier(BMessage *message, int32 index,
2004-06-24 13:46:58 +00:00
BMessage *specifier, int32 what,
const char *property)
{
CALLED();
BPropertyInfo propInfo(sPropertyList);
BHandler *target = NULL;
if (propInfo.FindMatch(message, index, specifier, what, property) > B_ERROR)
target = this;
if (!target)
target = BView::ResolveSpecifier(message, index, specifier, what, property);
return target;
}
2003-08-08 09:55:12 +00:00
status_t
BTextView::GetSupportedSuites(BMessage *data)
{
CALLED();
status_t err;
if (data == NULL)
return B_BAD_VALUE;
2003-10-13 14:38:43 +00:00
err = data->AddString("suites", "suite/vnd.Be-text-view");
if (err < B_OK)
return err;
BPropertyInfo prop_info(sPropertyList);
err = data->AddFlat("messages", &prop_info);
if (err < B_OK)
return err;
return BView::GetSupportedSuites(data);
}
2003-08-08 09:55:12 +00:00
status_t
BTextView::Perform(perform_code d, void *arg)
{
CALLED();
2003-10-13 14:38:43 +00:00
return BView::Perform(d, arg);
}
2003-08-08 09:55:12 +00:00
void
BTextView::SetText(const char *inText, const text_run_array *inRuns)
{
CALLED();
CancelInputMethod();
2003-08-08 09:55:12 +00:00
// 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
int32 len = inText ? strlen(inText) : 0;
2003-08-08 09:55:12 +00:00
if (inText != NULL && len > 0)
InsertText(inText, len, 0, inRuns);
fClickOffset = fSelStart = fSelEnd = 0;
2003-08-08 09:55:12 +00:00
// recalc line breaks and draw the text
Refresh(0, len, true, true);
// draw the caret
if (fActive) {
if (!fCaretVisible)
InvertCaret();
}
}
2003-08-08 09:55:12 +00:00
void
BTextView::SetText(const char *inText, int32 inLength,
const text_run_array *inRuns)
{
CALLED();
CancelInputMethod();
2003-08-08 09:55:12 +00:00
// 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);
fClickOffset = fSelStart = fSelEnd = 0;
2003-08-08 09:55:12 +00:00
// recalc line breaks and draw the text
Refresh(0, inLength, true, true);
// draw the caret
if (fActive) {
if (!fCaretVisible)
InvertCaret();
}
}
2003-08-08 09:55:12 +00:00
void
BTextView::SetText(BFile *inFile, int32 inOffset, int32 inLength,
const text_run_array *inRuns)
{
CALLED();
CancelInputMethod();
2004-08-17 13:52:18 +00:00
if (!inFile)
return;
if (fText->Length() > 0)
DeleteText(0, fText->Length());
fText->InsertText(inFile, inOffset, inLength, 0);
// 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)
SetRunArray(inOffset, inOffset + inLength, inRuns);
else {
// apply nullStyle to inserted text
fStyles->SyncNullStyle(inOffset);
fStyles->SetStyleRange(inOffset, inOffset + inLength,
fText->Length(), B_FONT_ALL, NULL, NULL);
}
2004-08-17 13:52:18 +00:00
fClickOffset = fSelStart = fSelEnd = 0;
// recalc line breaks and draw the text
Refresh(0, inLength, true, true);
// draw the caret
if (fActive) {
if (!fCaretVisible)
InvertCaret();
}
}
2003-08-08 09:55:12 +00:00
void
BTextView::Insert(const char *inText, const text_run_array *inRuns)
{
CALLED();
Insert(fSelStart, inText, strlen(inText), inRuns);
}
2003-08-08 09:55:12 +00:00
void
BTextView::Insert(const char *inText, int32 inLength,
const text_run_array *inRuns)
{
CALLED();
Insert(fSelStart, inText, inLength, inRuns);
}
2003-08-08 09:55:12 +00:00
void
BTextView::Insert(int32 startOffset, const char *inText, int32 inLength,
const text_run_array *inRuns)
{
CALLED();
CancelInputMethod();
2003-08-08 09:55:12 +00:00
// 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();
}
}
2003-10-13 14:38:43 +00:00
if (startOffset > TextLength())
startOffset = TextLength();
2003-08-08 09:55:12 +00:00
// copy data into buffer
2003-10-13 14:38:43 +00:00
InsertText(inText, inLength, startOffset, inRuns);
2003-08-08 09:55:12 +00:00
// offset the caret/selection
2004-08-17 13:52:18 +00:00
int32 saveStart = fSelStart;
2003-08-08 09:55:12 +00:00
fSelStart += inLength;
fSelEnd += inLength;
// recalc line breaks and draw the text
2003-10-13 14:38:43 +00:00
Refresh(saveStart, startOffset, true, true);
2003-08-08 09:55:12 +00:00
// draw the caret/hilite the selection
if (fActive) {
if (fSelStart != fSelEnd)
Highlight(fSelStart, fSelEnd);
else {
if (!fCaretVisible)
InvertCaret();
}
}
}
/*! \brief Deletes the selected text.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::Delete()
{
CALLED();
Delete(fSelStart, fSelEnd);
}
/*! \brief Delets the text comprised within the given offsets.
\param startOffset The offset of the text to delete.
\param endOffset The offset where the text to delete ends.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::Delete(int32 startOffset, int32 endOffset)
{
CALLED();
2003-08-08 09:55:12 +00:00
// anything to delete?
if (startOffset == endOffset)
return;
// hide the caret/unhilite the selection
if (fActive) {
if (fSelStart != fSelEnd)
Highlight(fSelStart, fSelEnd);
2003-08-08 09:55:12 +00:00
else {
if (fCaretVisible)
InvertCaret();
}
}
// remove data from buffer
DeleteText(startOffset, endOffset);
// Check if the caret needs to be moved
if (fClickOffset >= endOffset)
fClickOffset -= (endOffset - startOffset);
else if (fClickOffset >= startOffset && fClickOffset < endOffset)
fClickOffset = startOffset;
fSelEnd = fSelStart = fClickOffset;
2003-08-08 09:55:12 +00:00
// recalc line breaks and draw what's left
Refresh(startOffset, endOffset, true, true);
// draw the caret
if (fActive) {
2003-08-08 09:55:12 +00:00
if (!fCaretVisible)
InvertCaret();
}
}
/*! \brief Returns the BTextView text as a C string.
\return A pointer to the text.
It is possible that the BTextView object had to do some operations
on the text, to be able to return it as a C string.
If you need to call Text() repeatedly, you'd better use GetText().
*/
2003-08-08 09:55:12 +00:00
const char *
BTextView::Text() const
{
CALLED();
return fText->Text();
}
/*! \brief Returns the length of the BTextView's text.
\return The length of the text.
*/
2003-08-08 09:55:12 +00:00
int32
BTextView::TextLength() const
{
CALLED();
2003-08-08 09:55:12 +00:00
return fText->Length();
}
2003-08-08 09:55:12 +00:00
void
BTextView::GetText(int32 offset, int32 length, char *buffer) const
{
CALLED();
if (buffer != NULL)
fText->GetString(offset, length, buffer);
}
/*! \brief Returns the charachter at the given offset.
\param offset The offset of the wanted charachter.
\return The charachter at the given offset.
*/
2003-08-08 09:55:12 +00:00
uchar
BTextView::ByteAt(int32 offset) const
{
CALLED();
if (offset < 0 || offset > (fText->Length() - 1))
return '\0';
2003-08-08 09:55:12 +00:00
return (*fText)[offset];
}
/*! \brief Returns the number of lines that the object contains.
\return The number of lines contained in the BTextView object.
*/
2003-08-08 09:55:12 +00:00
int32
BTextView::CountLines() const
{
CALLED();
2003-08-08 09:55:12 +00:00
return fLines->NumLines();
}
/*! \brief Returns the index of the current line.
\return The index of the current line.
*/
2003-08-08 09:55:12 +00:00
int32
BTextView::CurrentLine() const
{
CALLED();
return LineAt(fSelStart);
}
2004-08-17 13:52:18 +00:00
/*! \brief Move the caret to the specified line.
\param index The index of the line.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::GoToLine(int32 index)
{
CALLED();
CancelInputMethod();
fSelStart = fSelEnd = fClickOffset = OffsetAt(index);
}
/*! \brief Cuts the current selection to the clipboard.
\param clipboard The clipboard where to copy the cutted text.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::Cut(BClipboard *clipboard)
{
CALLED();
CancelInputMethod();
2003-10-13 14:38:43 +00:00
if (fUndo) {
delete fUndo;
fUndo = new _BCutUndoBuffer_(this);
}
Copy(clipboard);
2003-10-13 14:38:43 +00:00
Delete();
}
/*! \brief Copies the current selection to the clipboard.
\param clipboard The clipboard where to copy the selected text.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::Copy(BClipboard *clipboard)
{
CALLED();
CancelInputMethod();
BMessage *clip = NULL;
if (clipboard->Lock()) {
clipboard->Clear();
if ((clip = clipboard->Data()) != NULL) {
clip->AddData("text/plain", B_MIME_TYPE, Text() + fSelStart,
2003-10-13 14:38:43 +00:00
fSelEnd - fSelStart);
int32 size;
if (fStylable) {
2003-10-13 14:38:43 +00:00
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();
}
2003-10-13 14:38:43 +00:00
clipboard->Unlock();
}
}
/*! \brief Paste the text contained in the clipboard to the BTextView.
\param clipboard A pointer to the clipboard.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::Paste(BClipboard *clipboard)
{
CALLED();
CancelInputMethod();
BMessage *clip = NULL;
if (clipboard->Lock()) {
clip = clipboard->Data();
if (clip != NULL) {
const char *text = NULL;
ssize_t len = 0;
2003-10-13 14:38:43 +00:00
if (clip->FindData("text/plain", B_MIME_TYPE,
(const void **)&text, &len) == B_OK) {
2003-10-13 14:38:43 +00:00
text_run_array *runArray = NULL;
ssize_t runLen = 0;
if (fStylable)
2003-10-13 14:38:43 +00:00
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);
}
2003-10-13 14:38:43 +00:00
if (fSelStart != fSelEnd)
Delete();
Insert(text, len, runArray);
}
}
clipboard->Unlock();
}
}
2004-08-17 13:52:18 +00:00
/*! \brief Deletes the currently selected text.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::Clear()
{
CALLED();
delete fUndo;
fUndo = new _BClearUndoBuffer_(this);
2003-08-08 09:55:12 +00:00
Delete();
}
2003-08-08 09:55:12 +00:00
bool
BTextView::AcceptsPaste(BClipboard *clipboard)
{
CALLED();
2003-08-08 09:55:12 +00:00
if (!fEditable)
return false;
bool result = false;
2003-10-13 14:38:43 +00:00
if (clipboard->Lock()) {
BMessage *data = clipboard->Data();
result = data && data->HasData("text/plain", B_MIME_TYPE);
clipboard->Unlock();
}
return result;
}
2003-08-08 09:55:12 +00:00
bool
BTextView::AcceptsDrop(const BMessage *inMessage)
{
CALLED();
if (fEditable && inMessage && inMessage->HasData("text/plain", B_MIME_TYPE))
2003-08-08 09:55:12 +00:00
return true;
2003-10-13 14:38:43 +00:00
2003-08-08 09:55:12 +00:00
return false;
}
/*! \brief Selects the text within the given offsets.
\param startOffset The offset of the text to select.
\param endOffset The offset where the text ends.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::Select(int32 startOffset, int32 endOffset)
{
CALLED();
2003-10-13 14:38:43 +00:00
if (!fSelectable)
return;
CancelInputMethod();
2003-10-13 14:38:43 +00:00
2003-08-08 09:55:12 +00:00
// a negative selection?
if (startOffset > endOffset)
return;
// is the new selection any different from the current selection?
if (startOffset == fSelStart && endOffset == fSelEnd)
2003-08-08 09:55:12 +00:00
return;
2003-08-08 09:55:12 +00:00
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 {
2003-08-08 09:55:12 +00:00
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 {
2003-08-08 09:55:12 +00:00
start = startOffset;
end = fSelStart;
}
Highlight(start, end);
}
if (endOffset != fSelEnd) {
// end of selection has changed
if (endOffset > fSelEnd) {
start = fSelEnd;
end = endOffset;
} else {
2003-08-08 09:55:12 +00:00
start = endOffset;
end = fSelEnd;
}
Highlight(start, end);
}
}
fSelStart = startOffset;
fSelEnd = endOffset;
}
}
/*! \brief Selects all the text in the BTextView.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::SelectAll()
{
CALLED();
2003-08-08 09:55:12 +00:00
Select(0, fText->Length());
}
/*! \brief Gets the current selection.
\param outStart A pointer to an int32 which will contain the selection start's offset.
\param outEnd A pointer to an int32 which will contain the selection end's offset.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::GetSelection(int32 *outStart, int32 *outEnd) const
{
CALLED();
int32 start = 0, end = 0;
2003-10-13 14:38:43 +00:00
if (fSelectable) {
start = fSelStart;
end = fSelEnd;
2003-10-13 14:38:43 +00:00
}
if (outStart)
*outStart = start;
if (outEnd)
*outEnd = end;
}
2003-08-08 09:55:12 +00:00
void
BTextView::SetFontAndColor(const BFont *inFont, uint32 inMode,
const rgb_color *inColor)
{
CALLED();
CancelInputMethod();
2003-08-08 09:55:12 +00:00
// hide the caret/unhilite the selection
if (fActive) {
if (fSelStart != fSelEnd)
Highlight(fSelStart, fSelEnd);
else {
if (fCaretVisible)
InvertCaret();
}
}
2003-10-13 14:38:43 +00:00
BFont newFont = *inFont;
NormalizeFont(&newFont);
2003-08-08 09:55:12 +00:00
// add the style to the style buffer
fStyles->SetStyleRange(fSelStart, fSelEnd, fText->Length(),
2003-10-13 14:38:43 +00:00
inMode, &newFont, inColor);
2003-08-08 09:55:12 +00:00
if (inMode & B_FONT_FAMILY_AND_STYLE || inMode & B_FONT_SIZE)
2003-08-08 09:55:12 +00:00
// 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();
}
}
}
2003-08-08 09:55:12 +00:00
void
BTextView::SetFontAndColor(int32 startOffset, int32 endOffset,
const BFont *inFont, uint32 inMode,
const rgb_color *inColor)
{
CALLED();
2003-08-08 09:55:12 +00:00
// hide the caret/unhilite the selection
if (fActive) {
if (startOffset != endOffset)
Highlight(startOffset, endOffset);
else {
if (fCaretVisible)
InvertCaret();
}
}
2003-10-13 14:38:43 +00:00
BFont newFont = *inFont;
NormalizeFont(&newFont);
2003-08-08 09:55:12 +00:00
// add the style to the style buffer
fStyles->SetStyleRange(startOffset, endOffset, fText->Length(),
2003-10-13 14:38:43 +00:00
inMode, &newFont, inColor);
2003-08-08 09:55:12 +00:00
if (inMode & B_FONT_FAMILY_AND_STYLE || inMode & B_FONT_SIZE)
2003-08-08 09:55:12 +00:00
// 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();
}
}
}
2003-08-08 09:55:12 +00:00
void
2003-10-13 14:38:43 +00:00
BTextView::GetFontAndColor(int32 inOffset, BFont *outFont,
rgb_color *outColor) const
{
CALLED();
2003-08-08 09:55:12 +00:00
fStyles->GetStyle(inOffset, outFont, outColor);
}
2003-08-08 09:55:12 +00:00
void
BTextView::GetFontAndColor(BFont *outFont, uint32 *outMode,
rgb_color *outColor, bool *outEqColor) const
{
CALLED();
fStyles->ContinuousGetStyle(outFont, outMode, outColor, outEqColor,
fSelStart, fSelEnd);
}
2003-08-08 09:55:12 +00:00
void
BTextView::SetRunArray(int32 startOffset, int32 endOffset,
const text_run_array *inRuns)
{
CALLED();
CancelInputMethod();
2003-08-08 09:55:12 +00:00
// 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) {
2003-10-13 14:38:43 +00:00
int32 textLength = fText->Length();
2003-08-08 09:55:12 +00:00
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,
B_FONT_ALL, &theRun->font, &theRun->color);
2003-08-08 09:55:12 +00:00
theRun++;
}
fStyles->InvalidateNullStyle();
Refresh(startOffset, endOffset, true, false);
}
}
/*! \brief Returns a RunArray for the text within the given offsets.
\param startOffset The offset where to start.
\param endOffset The offset where the wanted text ends.
\param outSize A pointer to an int32 which will contain the size
of the run array.
\return A text_run_array for the text in the given offsets.
The returned text_run_array belongs to the caller, so you better
free it as soon as you don't need it.
*/
2003-08-08 09:55:12 +00:00
text_run_array *
BTextView::RunArray(int32 startOffset, int32 endOffset,
int32 *outSize) const
{
CALLED();
STEStyleRangePtr styleRange = fStyles->GetStyleRange(startOffset, endOffset - 1);
2003-08-08 09:55:12 +00:00
if (styleRange == NULL)
2003-08-08 09:55:12 +00:00
return NULL;
text_run_array *res = (text_run_array *)malloc(sizeof(int32) +
(sizeof(text_run) * styleRange->count));
if (!res) {
if (outSize)
*outSize = 0;
return NULL;
}
res->count = styleRange->count;
2003-10-13 14:38:43 +00:00
for (int32 i = 0; i < res->count; i++) {
res->runs[i].offset = styleRange->runs[i].offset;
res->runs[i].font = &styleRange->runs[i].style.font;
res->runs[i].color = styleRange->runs[i].style.color;
2003-08-08 09:55:12 +00:00
}
2003-08-08 09:55:12 +00:00
if (outSize != NULL)
*outSize = sizeof(int32) + (sizeof(text_run) * res->count);
return res;
}
/*! \brief Returns the line number for the charachter at the given offset.
\param offset The offset of the wanted charachter.
\return A line number.
*/
2003-08-08 09:55:12 +00:00
int32
BTextView::LineAt(int32 offset) const
{
CALLED();
return fLines->OffsetToLine(offset);
}
/*! \brief Returns the line number for the given point.
\param point A point.
\return A line number.
*/
2003-08-08 09:55:12 +00:00
int32
BTextView::LineAt(BPoint point) const
{
CALLED();
return fLines->PixelToLine(point.y - fTextRect.top);
}
/*! \brief Returns the location of the charachter at the given offset.
\param inOffset The offset of the charachter.
\param outHeight Here the function will put the height of the charachter at the
given offset.
\return A BPoint which is the location of the charachter.
*/
2003-08-08 09:55:12 +00:00
BPoint
BTextView::PointAt(int32 inOffset, float *outHeight) const
{
// TODO: when alignment is != than B_ALIGN_LEFT,
// this function's still a bit broken.
// Cleanup.
CALLED();
BPoint result;
int32 textLength = fText->Length();
int32 lineNum = LineAt(inOffset);
STELinePtr line = (*fLines)[lineNum];
float height = 0;
2003-08-08 09:55:12 +00:00
result.x = 0.0;
2003-08-08 09:55:12 +00:00
result.y = line->origin + fTextRect.top;
// Handle the case where there is only one line
// (no text inserted)
// TODO: See if we can do this better
if (fStyles->NumRuns() == 0) {
const rgb_color *color = NULL;
const BFont *font = NULL;
fStyles->GetNullStyle(&font, &color);
2003-08-08 09:55:12 +00:00
font_height fontHeight;
font->GetHeight(&fontHeight);
height = fontHeight.ascent + fontHeight.descent;
2003-08-08 09:55:12 +00:00
} else {
height = (line + 1)->origin - line->origin;
2003-08-08 09:55:12 +00:00
// 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);
2003-08-08 09:55:12 +00:00
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(B_TAB, offset, &numChars);
float width = StyledWidth(offset, numChars);
result.x += width;
if (foundTab) {
result.x += ActualTabWidth(result.x);
numChars++;
}
offset += numChars;
length -= numChars;
numChars = length;
} while (foundTab && length > 0);
}
}
if (fAlignment != B_ALIGN_LEFT) {
float modifier = fTextRect.right - LineWidth(lineNum);
if (fAlignment == B_ALIGN_CENTER)
modifier /= 2;
result.x += modifier;
}
2003-08-08 09:55:12 +00:00
// 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;
2003-10-13 14:38:43 +00:00
return result;
}
/*! \brief Returns the offset for the given location.
\param point A BPoint which specify the wanted location.
\return The offset for the given point.
*/
2003-08-08 09:55:12 +00:00
int32
BTextView::OffsetAt(BPoint point) const
{
CALLED();
2003-08-08 09:55:12 +00:00
// 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];
2003-08-08 09:55:12 +00:00
// 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());
}
2003-08-08 09:55:12 +00:00
// convert to text rect coordinates
if (fAlignment != B_ALIGN_LEFT) {
float lineWidth = fTextRect.right - LineWidth(lineNum);
if (fAlignment == B_ALIGN_CENTER)
lineWidth /= 2;
point.x -= lineWidth;
}
2003-08-08 09:55:12 +00:00
point.x -= fTextRect.left;
point.x = max_c(point.x, 0.0);
// TODO: Not exactly clean and performant: fix this.
#if 1
int32 offset = line->offset;
int32 limit = (line + 1)->offset;
int32 saveOffset;
float x = 0;
do {
saveOffset = offset;
int32 nextInitial = NextInitialByte(offset);
float width = StyledWidth(saveOffset, nextInitial - saveOffset);
if (x + width > point.x) {
if (fabs(x + width - point.x) < fabs(x - point.x))
offset = nextInitial;
break;
}
x += width;
offset = nextInitial;
} while (offset < limit);
#else
2003-08-08 09:55:12 +00:00
// 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;
2003-08-08 09:55:12 +00:00
do {
saveOffset = offset;
if (foundTab) {
// is the point in the right-half of the tab?
if (point.x >= (sigmaWidth - (tabWidth / 2)) &&
point.x < sigmaWidth)
2003-08-08 09:55:12 +00:00
break;
}
// any more tabs?
foundTab = fText->FindChar(B_TAB, offset, &numChars);
2003-08-08 09:55:12 +00:00
delta = numChars / 2;
delta = max_c(delta, 1);
2003-08-08 09:55:12 +00:00
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 {
2003-08-08 09:55:12 +00:00
// still too far to the left, measure some more
offset += delta;
delta /= 2;
delta = max_c(delta, 1);
2003-08-08 09:55:12 +00:00
}
} else {
2003-08-08 09:55:12 +00:00
// oops, we overshot the point, go back some
sigmaWidth -= deltaWidth;
if (delta == 1) {
done = true;
break;
}
delta /= 2;
2004-08-17 13:52:18 +00:00
delta = min_c(delta, 1);
2003-08-08 09:55:12 +00:00
}
} while (offset < (numChars + saveOffset));
}
if (done || offset >= limit)
2003-08-08 09:55:12 +00:00
break;
if (foundTab) {
2004-06-24 13:46:58 +00:00
tabWidth = ActualTabWidth(sigmaWidth);
2003-08-08 09:55:12 +00:00
// is the point in the left-half of the tab?
if (point.x < (sigmaWidth + (tabWidth / 2)))
break;
sigmaWidth += tabWidth;
numChars++;
}
2003-08-08 09:55:12 +00:00
offset = saveOffset + numChars;
length -= numChars;
numChars = length;
} while (foundTab && length > 0);
#endif
2003-08-08 09:55:12 +00:00
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;
2003-08-08 09:55:12 +00:00
// special case: return the offset preceding any spaces that
// aren't at the end of the buffer
if (offset != fText->Length() && (*fText)[offset - 1] == B_SPACE)
return --offset;
2003-08-08 09:55:12 +00:00
}
2003-10-13 14:38:43 +00:00
return offset;
}
/*! \brief Returns the offset of the given line.
\param line A line number.
\return The offset of the passed line.
*/
2003-08-08 09:55:12 +00:00
int32
BTextView::OffsetAt(int32 line) const
{
2003-08-08 09:55:12 +00:00
if (line > fLines->NumLines())
return fText->Length();
2003-08-08 09:55:12 +00:00
return (*fLines)[line]->offset;
}
2003-08-08 09:55:12 +00:00
void
BTextView::FindWord(int32 inOffset, int32 *outFromOffset,
int32 *outToOffset)
{
2003-10-13 14:38:43 +00:00
int32 offset;
2003-08-08 09:55:12 +00:00
// check to the left
for (offset = inOffset; offset > 0; offset--) {
if (CharClassification(offset - 1) == B_SEPARATOR_CHARACTER)
2003-08-08 09:55:12 +00:00
break;
}
if (outFromOffset)
*outFromOffset = offset;
2003-08-08 09:55:12 +00:00
// check to the right
2003-10-13 14:38:43 +00:00
int32 textLen = TextLength();
2003-08-08 09:55:12 +00:00
for (offset = inOffset; offset < textLen; offset++) {
if (CharClassification(offset) == B_SEPARATOR_CHARACTER)
2003-08-08 09:55:12 +00:00
break;
}
if (outToOffset)
*outToOffset = offset;
}
2003-08-08 09:55:12 +00:00
2003-08-08 09:55:12 +00:00
bool
BTextView::CanEndLine(int32 offset)
{
CALLED();
return (CharClassification(offset) == B_SEPARATOR_CHARACTER);
}
/*! \brief Returns the width of the line at the given index.
\param lineNum A line index.
*/
2003-08-08 09:55:12 +00:00
float
BTextView::LineWidth(int32 lineNum) const
{
CALLED();
if (lineNum < 0 || lineNum >= fLines->NumLines())
return 0;
else {
STELinePtr line = (*fLines)[lineNum];
return StyledWidth(line->offset, (line + 1)->offset - line->offset);
}
}
/*! \brief Returns the height of the line at the given index.
\param lineNum A line index.
*/
2003-08-08 09:55:12 +00:00
float
BTextView::LineHeight(int32 lineNum) const
{
CALLED();
return TextHeight(lineNum, lineNum);
}
/*! \brief Returns the height of the text comprised between the two given lines.
\param startLine The index of the starting line.
\param endLine The index of the ending line.
*/
2003-08-08 09:55:12 +00:00
float
BTextView::TextHeight(int32 startLine, int32 endLine) const
{
CALLED();
2003-08-08 09:55:12 +00:00
int32 numLines = fLines->NumLines();
2004-08-17 13:52:18 +00:00
if (startLine < 0)
startLine = 0;
if (endLine > numLines - 1)
endLine = numLines - 1;
2003-08-08 09:55:12 +00:00
2004-08-17 13:52:18 +00:00
// TODO: This looks broken as well. What do we do if there's only one line ?
float height = (*fLines)[endLine + 1]->origin - (*fLines)[startLine]->origin;
2003-08-08 09:55:12 +00:00
if (endLine == numLines - 1 && (*fText)[fText->Length() - 1] == '\n')
2003-08-08 09:55:12 +00:00
height += (*fLines)[endLine + 1]->origin - (*fLines)[endLine]->origin;
height = ceil(height);
return height;
}
2003-08-08 09:55:12 +00:00
void
BTextView::GetTextRegion(int32 startOffset, int32 endOffset,
BRegion *outRegion) const
{
CALLED();
if (!outRegion)
return;
2003-08-08 09:55:12 +00:00
outRegion->MakeEmpty();
2003-08-08 09:55:12 +00:00
// 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);
startLineHeight = ceil(startLineHeight);
endLineHeight = ceil(endLineHeight);
BRect selRect;
2003-08-08 09:55:12 +00:00
if (startPt.y == endPt.y) {
// this is a one-line region
selRect.left = max_c(startPt.x, fTextRect.left);
2003-08-08 09:55:12 +00:00
selRect.top = startPt.y;
selRect.right = endPt.x - 1.0;
selRect.bottom = endPt.y + endLineHeight - 1.0;
outRegion->Include(selRect);
} else {
2003-08-08 09:55:12 +00:00
// more than one line in the specified offset range
selRect.left = max_c(startPt.x, fTextRect.left);
2003-08-08 09:55:12 +00:00
selRect.top = startPt.y;
selRect.right = fTextRect.right;
selRect.bottom = startPt.y + startLineHeight - 1.0;
outRegion->Include(selRect);
if (startPt.y + startLineHeight < endPt.y) {
2003-08-08 09:55:12 +00:00
// more than two lines in the range
selRect.left = fTextRect.left;
selRect.top = startPt.y + startLineHeight;
2003-08-08 09:55:12 +00:00
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);
}
}
2003-08-08 09:55:12 +00:00
void
BTextView::ScrollToOffset(int32 inOffset)
{
CALLED();
BRect bounds = Bounds();
2003-08-08 09:55:12 +00:00
//STELinePtr line = (*fLines)[LineAt(inOffset)];
float lineHeight = 0.0;
BPoint point = PointAt(inOffset, &lineHeight);
2003-08-08 09:55:12 +00:00
if (ScrollBar(B_HORIZONTAL) != NULL) {
if (point.x < bounds.left || point.x >= bounds.right)
2003-08-08 09:55:12 +00:00
ScrollBar(B_HORIZONTAL)->SetValue(point.x - (bounds.IntegerWidth() / 2));
}
if (ScrollBar(B_VERTICAL) != NULL) {
if (point.y < bounds.top || (point.y + lineHeight) >= bounds.bottom)
2003-08-08 09:55:12 +00:00
ScrollBar(B_VERTICAL)->SetValue(point.y - (bounds.IntegerHeight() / 2));
}
}
2003-08-08 09:55:12 +00:00
void
BTextView::ScrollToSelection()
{
CALLED();
ScrollToOffset(fSelStart);
}
/*! \brief Highlight the text comprised between the given offset.
\param startOffset The offset of the text to highlight.
\param endOffset The offset where the text to highlight ends.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::Highlight(int32 startOffset, int32 endOffset)
{
CALLED();
2003-08-08 09:55:12 +00:00
// get real
if (startOffset >= endOffset)
return;
BRegion selRegion;
GetTextRegion(startOffset, endOffset, &selRegion);
SetDrawingMode(B_OP_INVERT);
FillRegion(&selRegion, B_SOLID_HIGH);
SetDrawingMode(B_OP_COPY);
}
2003-08-08 09:55:12 +00:00
void
BTextView::SetTextRect(BRect rect)
{
CALLED();
2003-08-08 09:55:12 +00:00
if (rect == fTextRect)
return;
fTextRect = rect;
if (Window() != NULL)
Refresh(0, fText->Length(), true, false);
}
2003-08-08 09:55:12 +00:00
BRect
BTextView::TextRect() const
{
CALLED();
2003-08-08 09:55:12 +00:00
return fTextRect;
}
2003-08-08 09:55:12 +00:00
void
BTextView::SetStylable(bool stylable)
{
CALLED();
fStylable = stylable;
}
/*! \brief Tells if the object is stylable.
\return true if the object is stylable, false otherwise.
If the object is stylable, it can show multiple fonts at the same time.
*/
2003-08-08 09:55:12 +00:00
bool
BTextView::IsStylable() const
{
CALLED();
return fStylable;
}
2003-08-08 09:55:12 +00:00
void
BTextView::SetTabWidth(float width)
{
CALLED();
2003-08-08 09:55:12 +00:00
if (width == fTabWidth)
return;
fTabWidth = width;
2003-08-08 09:55:12 +00:00
if (Window() != NULL)
Refresh(0, fText->Length(), true, false);
}
/*! \brief Returns the BTextView's tab width.
\return The BTextView's tab width.
*/
2003-08-08 09:55:12 +00:00
float
BTextView::TabWidth() const
{
CALLED();
return fTabWidth;
}
/*! \brief Makes the object selectable, or not selectable.
\param selectable If true, the object will be selectable from now on.
if false, it won't be selectable anymore.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::MakeSelectable(bool selectable)
{
CALLED();
2003-08-08 09:55:12 +00:00
if (selectable == fSelectable)
return;
fSelectable = selectable;
2003-08-08 09:55:12 +00:00
if (Window() != NULL) {
if (fActive) {
// show/hide the caret, hilite/unhilite the selection
if (fSelStart != fSelEnd)
Highlight(fSelStart, fSelEnd);
else
InvertCaret();
}
}
}
/*! \brief Tells if the object is selectable
\return \c true if the object is selectable,
\c false if not.
*/
2003-08-08 09:55:12 +00:00
bool
BTextView::IsSelectable() const
{
CALLED();
return fSelectable;
}
/*! \brief Set (or remove) the editable property for the object.
\param editable If true, will make the object editable,
if false, will make it not editable.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::MakeEditable(bool editable)
{
CALLED();
2003-08-08 09:55:12 +00:00
if (editable == fEditable)
return;
fEditable = editable;
2003-08-08 09:55:12 +00:00
if (Window() != NULL && fActive) {
if (!fEditable) {
if (fCaretVisible)
2003-08-08 09:55:12 +00:00
InvertCaret();
CancelInputMethod();
}
2003-08-08 09:55:12 +00:00
}
}
/*! \brief Tells if the object is editable.
\return \c true if the object is editable,
\c false if not.
*/
2003-08-08 09:55:12 +00:00
bool
BTextView::IsEditable() const
{
CALLED();
return fEditable;
}
/*! \brief Set (or unset) word wrapping mode.
\param wrap Specifies if you want word wrapping active or not.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::SetWordWrap(bool wrap)
{
CALLED();
2003-08-08 09:55:12 +00:00
if (wrap == fWrap)
return;
if (Window() != NULL) {
if (fActive) {
// hide the caret, unhilite the selection
if (fSelStart != fSelEnd)
Highlight(fSelStart, fSelEnd);
else {
if (fCaretVisible)
InvertCaret();
}
}
fWrap = wrap;
Refresh(0, fText->Length(), true, true);
2003-08-08 09:55:12 +00:00
if (fActive) {
// show the caret, hilite the selection
if (fSelStart != fSelEnd && fSelectable)
2003-08-08 09:55:12 +00:00
Highlight(fSelStart, fSelEnd);
else {
if (!fCaretVisible)
InvertCaret();
}
}
}
}
/*! \brief Tells if word wrapping is activated.
\return true if word wrapping is active, false otherwise.
*/
2003-08-08 09:55:12 +00:00
bool
BTextView::DoesWordWrap() const
{
CALLED();
return fWrap;
}
2003-08-08 09:55:12 +00:00
void
BTextView::SetMaxBytes(int32 max)
{
CALLED();
int32 textLength = fText->Length();
fMaxBytes = max;
if (fMaxBytes < textLength)
Delete(fMaxBytes, textLength);
}
2003-08-08 09:55:12 +00:00
int32
BTextView::MaxBytes() const
{
CALLED();
return fMaxBytes;
}
/*! \brief Adds the given char to the disallowed chars list.
\param aChar The charachter to add to the list.
After this function returns, the given charachter won't be accepted
by the textview anymore.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::DisallowChar(uint32 aChar)
{
CALLED();
2003-08-08 09:55:12 +00:00
if (fDisallowedChars == NULL)
fDisallowedChars = new BList;
if (!fDisallowedChars->HasItem(reinterpret_cast<void *>(aChar)))
fDisallowedChars->AddItem(reinterpret_cast<void *>(aChar));
}
/*! \brief Removes the given charachter from the disallowed list.
\param aChar The charachter to remove from the list.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::AllowChar(uint32 aChar)
{
CALLED();
2003-08-08 09:55:12 +00:00
if (fDisallowedChars != NULL)
fDisallowedChars->RemoveItem(reinterpret_cast<void *>(aChar));
}
2003-08-08 09:55:12 +00:00
void
BTextView::SetAlignment(alignment flag)
{
CALLED();
// Do a reality check
if (fAlignment != flag &&
(flag == B_ALIGN_LEFT ||
flag == B_ALIGN_RIGHT ||
flag == B_ALIGN_CENTER)) {
fAlignment = flag;
// After setting new alignment, update the view/window
BWindow *window = Window();
if (window) {
Invalidate();
window->UpdateIfNeeded();
}
}
}
alignment
BTextView::Alignment() const
{
CALLED();
return fAlignment;
}
2003-08-08 09:55:12 +00:00
void
BTextView::SetAutoindent(bool state)
{
CALLED();
fAutoindent = state;
}
2003-08-08 09:55:12 +00:00
bool
BTextView::DoesAutoindent() const
{
CALLED();
return fAutoindent;
}
/*! \brief Set the color space for the offscreen BBitmap.
\param colors The new colorspace for the offscreen BBitmap.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::SetColorSpace(color_space colors)
{
CALLED();
if (colors != fColorSpace && fOffscreen) {
fColorSpace = colors;
DeleteOffscreen();
NewOffscreen();
}
}
/*! \brief Returns the colorspace of the offscreen BBitmap, if any.
\return The colorspace of the BTextView's offscreen BBitmap.
*/
2003-08-08 09:55:12 +00:00
color_space
BTextView::ColorSpace() const
{
CALLED();
return fColorSpace;
}
2003-08-08 09:55:12 +00:00
void
BTextView::MakeResizable(bool resize, BView *resizeView)
{
CALLED();
if (resize) {
fResizable = true;
fContainerView = resizeView;
// Wrapping mode and resizable mode can't live together
if (fWrap) {
fWrap = false;
if (fActive && Window() != NULL) {
if (fSelStart != fSelEnd && fSelectable)
Highlight(fSelStart, fSelEnd);
else if (fCaretVisible) {
InvertCaret();
Refresh(0, fText->Length(), true, false);
}
}
}
} else {
fResizable = false;
fContainerView = NULL;
if (fOffscreen)
DeleteOffscreen();
NewOffscreen();
}
}
2003-08-08 09:55:12 +00:00
bool
BTextView::IsResizable() const
{
CALLED();
return fResizable;
}
2003-08-08 09:55:12 +00:00
void
BTextView::SetDoesUndo(bool undo)
{
CALLED();
if (undo && fUndo == NULL)
fUndo = new _BUndoBuffer_(this, B_UNDO_UNAVAILABLE);
else if (!undo && fUndo != NULL) {
delete fUndo;
fUndo = NULL;
}
}
2003-08-08 09:55:12 +00:00
bool
BTextView::DoesUndo() const
{
CALLED();
return fUndo != NULL;
}
2003-08-08 09:55:12 +00:00
void
BTextView::HideTyping(bool enabled)
{
CALLED();
//TODO: Implement ?
//fText->SetPasswordMode(enabled);
}
2003-08-08 09:55:12 +00:00
bool
BTextView::IsTypingHidden() const
{
CALLED();
2003-10-13 14:38:43 +00:00
return fText->PasswordMode();
}
2003-08-08 09:55:12 +00:00
void
BTextView::ResizeToPreferred()
{
CALLED();
float widht, height;
GetPreferredSize(&widht, &height);
2003-10-13 14:38:43 +00:00
BView::ResizeTo(widht, height);
}
2003-08-08 09:55:12 +00:00
void
BTextView::GetPreferredSize(float *width, float *height)
{
CALLED();
BView::GetPreferredSize(width, height);
}
2003-08-08 09:55:12 +00:00
void
BTextView::AllAttached()
{
CALLED();
BView::AllAttached();
}
2003-08-08 09:55:12 +00:00
void
BTextView::AllDetached()
{
CALLED();
BView::AllDetached();
}
2003-08-08 09:55:12 +00:00
void *
BTextView::FlattenRunArray(const text_run_array *inArray, int32 *outSize)
{
CALLED();
2003-08-08 09:55:12 +00:00
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);
if (array == NULL) {
*outSize = 0;
return NULL;
}
2003-08-08 09:55:12 +00:00
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;
2003-10-13 14:38:43 +00:00
for (int32 i = 0; i < inArray->count; i++) {
2003-08-08 09:55:12 +00:00
array->styles[i].offset = inArray->runs[i].offset;
inArray->runs[i].font.GetFamilyAndStyle(&array->styles[i].family,
2003-08-08 09:55:12 +00:00
&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();
2003-08-08 09:55:12 +00:00
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;
}
2003-08-08 09:55:12 +00:00
text_run_array *
BTextView::UnflattenRunArray(const void *data, int32 *outSize)
{
CALLED();
flattened_text_run_array *array = (flattened_text_run_array *)data;
2003-08-08 09:55:12 +00:00
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) {
2003-08-08 09:55:12 +00:00
if (outSize)
*outSize = 0;
2003-08-08 09:55:12 +00:00
return NULL;
}
int32 size = sizeof(text_run_array) + (array->count - 1) * sizeof(text_run);
text_run_array *run_array = (text_run_array *)malloc(size);
if (run_array == NULL) {
*outSize = 0;
return NULL;
}
2003-08-08 09:55:12 +00:00
run_array->count = array->count;
for (int32 i = 0; i < array->count; i++) {
2003-08-08 09:55:12 +00:00
run_array->runs[i].font = new BFont;
run_array->runs[i].font.SetFamilyAndStyle(array->styles[i].family,
2003-08-08 09:55:12 +00:00
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);
2003-08-08 09:55:12 +00:00
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;
}
2003-08-08 09:55:12 +00:00
if (outSize)
*outSize = size;
return run_array;
}
2003-08-08 09:55:12 +00:00
void
BTextView::InsertText(const char *inText, int32 inLength, int32 inOffset,
const text_run_array *inRuns)
{
CALLED();
2003-08-08 09:55:12 +00:00
// 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)
SetRunArray(inOffset, inOffset + inLength, inRuns);
else {
// apply nullStyle to inserted text
fStyles->SyncNullStyle(inOffset);
fStyles->SetStyleRange(inOffset, inOffset + inLength,
fText->Length(), B_FONT_ALL, NULL, NULL);
2003-08-08 09:55:12 +00:00
}
}
2003-08-08 09:55:12 +00:00
void
BTextView::DeleteText(int32 fromOffset, int32 toOffset)
{
CALLED();
2003-08-08 09:55:12 +00:00
// sanity checking
if (fromOffset >= toOffset || fromOffset < 0 || toOffset < 0)
2003-08-08 09:55:12 +00:00
return;
// set nullStyle to style at beginning of range
fStyles->InvalidateNullStyle();
fStyles->SyncNullStyle(fromOffset);
// remove from the text buffer
fText->RemoveRange(fromOffset, toOffset);
2003-08-08 09:55:12 +00:00
// remove any lines that have been obliterated
fLines->RemoveLineRange(fromOffset, toOffset);
2003-08-08 09:55:12 +00:00
// remove any style runs that have been obliterated
fStyles->RemoveStyleRange(fromOffset, toOffset);
}
/*! \brief Undoes the last changes.
\param clipboard A clipboard to use for the undo operation.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::Undo(BClipboard *clipboard)
{
CALLED();
if (fUndo)
fUndo->Undo(clipboard);
}
2003-08-08 09:55:12 +00:00
undo_state
BTextView::UndoState(bool *isRedo) const
{
CALLED();
return fUndo == NULL ? B_UNDO_UNAVAILABLE : fUndo->State(isRedo);
}
2003-08-08 09:55:12 +00:00
void
BTextView::GetDragParameters(BMessage *drag, BBitmap **bitmap,
BPoint *point, BHandler **handler)
{
CALLED();
if (drag == NULL)
return;
// Add originator and action
drag->AddPointer("be:originator", this);
2003-10-13 14:38:43 +00:00
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);
free(styles);
if (bitmap != NULL)
*bitmap = NULL;
if (handler != NULL)
*handler = NULL;
}
2003-08-08 09:55:12 +00:00
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() {}
/*! \brief Inits the BTextView object.
\param textRect The BTextView's text rect.
\param initialFont The font which the BTextView will use.
\param initialColor The initial color of the text.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::InitObject(BRect textRect, const BFont *initialFont,
const rgb_color *initialColor)
{
CALLED();
fTextRect = textRect;
2003-08-08 09:55:12 +00:00
BFont font;
2003-10-13 14:38:43 +00:00
if (initialFont == NULL)
2003-08-08 09:55:12 +00:00
GetFont(&font);
2003-10-13 14:38:43 +00:00
else
font = *initialFont;
NormalizeFont(&font);
2003-08-08 09:55:12 +00:00
rgb_color black = {0, 0, 0, 255};
if (initialColor == NULL)
initialColor = &black;
fText = new _BTextGapBuffer_;
fLines = new _BLineBuffer_;
2003-10-13 14:38:43 +00:00
fStyles = new _BStyleBuffer_(&font, initialColor);
// We put these here instead of in the constructor initializer list
// to have less code duplication, and a single place where to do changes
// if needed.,
fTextRect = textRect;
fSelStart = fSelEnd = 0;
fCaretVisible = false;
fCaretTime = 0;
fClickOffset = 0;
fClickCount = 0;
fClickTime = 0;
fDragOffset = -1;
fCursor = 0;
fActive = false;
fStylable = false;
fSelectable = true;
fEditable = true;
fWrap = true;
fMaxBytes = LONG_MAX;
fDisallowedChars = NULL;
fAlignment = B_ALIGN_LEFT;
fAutoindent = false;
fOffscreen = NULL;
fColorSpace = B_CMAP8;
fResizable = false;
fContainerView = NULL;
fUndo = NULL;
fInline = NULL;
fDragRunner = NULL;
fClickRunner = NULL;
fTrackingMouse = NULL;
fTextChange = NULL;
}
/*! \brief Called when Backspace key is pressed.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::HandleBackspace()
{
CALLED();
2003-10-13 14:38:43 +00:00
if (fUndo) {
_BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>(fUndo);
if (!undoBuffer) {
delete fUndo;
fUndo = undoBuffer = new _BTypingUndoBuffer_(this);
}
undoBuffer->BackwardErase();
}
if (fSelStart == fSelEnd) {
if (fSelStart == 0)
return;
else
fSelStart = PreviousInitialByte(fSelStart);
} else
Highlight(fSelStart, fSelEnd);
2003-08-08 09:55:12 +00:00
DeleteText(fSelStart, fSelEnd);
fClickOffset = fSelEnd = fSelStart;
2003-08-08 09:55:12 +00:00
Refresh(fSelStart, fSelEnd, true, false);
}
/*! \brief Called when any arrow key is pressed.
\param inArrowKey The code for the pressed key.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::HandleArrowKey(uint32 inArrowKey)
{
CALLED();
2003-08-08 09:55:12 +00:00
// return if there's nowhere to go
if (fText->Length() == 0)
return;
int32 selStart = fSelStart;
int32 selEnd = fSelEnd;
int32 scrollToOffset = 0;
bool shiftDown = modifiers() & B_SHIFT_KEY;
2003-08-08 09:55:12 +00:00
switch (inArrowKey) {
case B_LEFT_ARROW:
if (fClickOffset > 0)
fClickOffset = PreviousInitialByte(fClickOffset);
else if (shiftDown)
return;
2003-08-08 09:55:12 +00:00
if (shiftDown) {
if (fClickOffset >= fSelStart)
selEnd = fClickOffset;
else
selStart = fClickOffset;
} else
selStart = selEnd = fClickOffset;
2003-08-08 09:55:12 +00:00
scrollToOffset = selStart;
break;
2003-08-08 09:55:12 +00:00
case B_RIGHT_ARROW:
if (fClickOffset < fText->Length())
fClickOffset = NextInitialByte(fClickOffset);
else if (shiftDown)
return;
2003-08-08 09:55:12 +00:00
if (shiftDown) {
if (fClickOffset <= fSelEnd)
selStart = fClickOffset;
2003-08-08 09:55:12 +00:00
else
selEnd = fClickOffset;
} else
selStart = selEnd = fClickOffset;
2003-08-08 09:55:12 +00:00
scrollToOffset = selEnd;
break;
2003-08-08 09:55:12 +00:00
case B_UP_ARROW:
{
float height;
BPoint point = PointAt(fClickOffset, &height);
point.y -= height;
fClickOffset = OffsetAt(point);
if (shiftDown) {
if (fClickOffset > fSelStart)
selEnd = fClickOffset;
else
selStart = fClickOffset;
} else
selStart = selEnd = fClickOffset;
2003-08-08 09:55:12 +00:00
scrollToOffset = selStart;
break;
}
2003-08-08 09:55:12 +00:00
case B_DOWN_ARROW:
{
float height;
BPoint point = PointAt(fClickOffset, &height);
2003-08-08 09:55:12 +00:00
point.y += height;
fClickOffset = OffsetAt(point);
if (shiftDown) {
if (fClickOffset < fSelEnd)
selStart = fClickOffset;
else
selEnd = fClickOffset;
} else
selStart = selEnd = fClickOffset;
2003-08-08 09:55:12 +00:00
scrollToOffset = selEnd;
break;
}
}
2003-08-08 09:55:12 +00:00
// invalidate the null style
fStyles->InvalidateNullStyle();
if (selEnd != fSelEnd || selStart != fSelStart)
Select(selStart, selEnd);
2003-08-08 09:55:12 +00:00
// scroll if needed
ScrollToOffset(scrollToOffset);
}
/*! \brief Called when the Delete key is pressed.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::HandleDelete()
{
CALLED();
2003-10-13 14:38:43 +00:00
if (fUndo) {
_BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>(fUndo);
if (!undoBuffer) {
delete fUndo;
fUndo = undoBuffer = new _BTypingUndoBuffer_(this);
}
undoBuffer->ForwardErase();
}
if (fSelStart == fSelEnd) {
if (fSelEnd == fText->Length())
return;
else
fSelEnd = NextInitialByte(fSelEnd);
} else
Highlight(fSelStart, fSelEnd);
2003-08-08 09:55:12 +00:00
DeleteText(fSelStart, fSelEnd);
fClickOffset = fSelEnd = fSelStart;
2003-08-08 09:55:12 +00:00
Refresh(fSelStart, fSelEnd, true, true);
}
/*! \brief Called when a "Page key" is pressed.
\param inPageKey The page key which has been pressed.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::HandlePageKey(uint32 inPageKey)
{
CALLED();
int32 mods = 0;
/*BMessage *currentMessage = Window()->CurrentMessage();
if (currentMessage)
currentMessage->FindInt32("modifiers", &mods);
*/
mods = modifiers();
bool shiftDown = mods & B_SHIFT_KEY;
STELinePtr line = NULL;
int32 start = fSelStart, end = fSelEnd;
2003-08-08 09:55:12 +00:00
switch (inPageKey) {
case B_HOME:
line = (*fLines)[CurrentLine()];
fClickOffset = line->offset;
if (shiftDown) {
if (fClickOffset <= fSelStart) {
start = fClickOffset;
end = fSelEnd;
} else {
start = fSelStart;
end = fClickOffset;
}
} else
start = end = fClickOffset;
break;
2003-08-08 09:55:12 +00:00
case B_END:
// If we are on the last line, just go to the last
// charachter in the buffer, otherwise get the starting
// offset of the next line, and go to the previous charachter
if (CurrentLine() + 1 < fLines->NumLines()) {
line = (*fLines)[CurrentLine() + 1];
fClickOffset = PreviousInitialByte(line->offset);
2004-09-27 23:10:13 +00:00
} else {
// This check if needed to avoid moving the cursor
// when the cursor is on the last line, and that line
// is empty
if (fClickOffset != fText->Length()) {
fClickOffset = fText->Length();
if (ByteAt(fClickOffset - 1) == B_ENTER)
fClickOffset--;
}
2004-09-27 23:10:13 +00:00
}
if (shiftDown) {
if (fClickOffset >= fSelEnd) {
start = fSelStart;
end = fClickOffset;
} else {
start = fClickOffset;
end = fSelEnd;
}
} else
start = end = fClickOffset;
2003-08-08 09:55:12 +00:00
break;
case B_PAGE_UP:
{
BPoint currentPos = PointAt(fClickOffset);
currentPos.y -= Bounds().Height();
fClickOffset = OffsetAt(LineAt(currentPos));
if (shiftDown) {
if (fClickOffset <= fSelStart) {
start = fClickOffset;
end = fSelEnd;
} else {
start = fSelStart;
end = fClickOffset;
}
} else
start = end = fClickOffset;
break;
}
2003-08-08 09:55:12 +00:00
case B_PAGE_DOWN:
{
BPoint currentPos = PointAt(fClickOffset);
currentPos.y += Bounds().Height();
fClickOffset = OffsetAt(LineAt(currentPos) + 1);
if (shiftDown) {
if (fClickOffset >= fSelEnd) {
start = fSelStart;
end = fClickOffset;
} else {
start = fClickOffset;
end = fSelEnd;
}
} else
start = end = fClickOffset;
2003-08-08 09:55:12 +00:00
break;
}
}
ScrollToOffset(fClickOffset);
Select(start, end);
}
/*! \brief Called when an alphanumeric key is pressed.
\param bytes The string or charachter associated with the key.
\param numBytes The amount of bytes containes in "bytes".
*/
2003-08-08 09:55:12 +00:00
void
BTextView::HandleAlphaKey(const char *bytes, int32 numBytes)
{
CALLED();
2003-10-13 14:38:43 +00:00
if (fUndo) {
_BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>(fUndo);
if (!undoBuffer) {
delete fUndo;
fUndo = undoBuffer = new _BTypingUndoBuffer_(this);
}
undoBuffer->InputCharacter(numBytes);
}
bool refresh = fSelStart != fText->Length();
2003-08-08 09:55:12 +00:00
if (fSelStart != fSelEnd) {
Highlight(fSelStart, fSelEnd);
DeleteText(fSelStart, fSelEnd);
2003-08-08 09:55:12 +00:00
refresh = true;
}
if (fAutoindent && numBytes == 1 && *bytes == B_ENTER) {
2003-08-08 09:55:12 +00:00
int32 start, offset;
start = offset = OffsetAt(LineAt(fSelStart));
while (ByteAt(offset) != '\0' &&
(ByteAt(offset) == B_TAB || ByteAt(offset) == B_SPACE))
2003-08-08 09:55:12 +00:00
offset++;
if (start != offset)
InsertText(Text() + start, offset - start, fSelStart, NULL);
2003-08-08 09:55:12 +00:00
InsertText(bytes, numBytes, fSelStart, NULL);
numBytes += offset - start;
} else
2003-08-08 09:55:12 +00:00
InsertText(bytes, numBytes, fSelStart, NULL);
fClickOffset = fSelEnd = fSelStart = fSelStart + numBytes;
if (Window())
Refresh(fSelStart, fSelEnd, refresh, true);
}
/*! \brief Redraw the text comprised between the two given offsets,
recalculating linebreaks if needed.
\param fromOffset The offset from where to refresh.
\param toOffset The offset where to refresh to.
\param erase If true, the function will also erase the textview content
in the parts where text isn't present.
\param scroll If true, function will scroll the view to the end offset.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::Refresh(int32 fromOffset, int32 toOffset, bool erase,
bool scroll)
{
2004-06-24 13:46:58 +00:00
// TODO: Cleanup
CALLED();
ASSERT(Window() != NULL);
if (!Window())
return;
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();
2003-08-08 09:55:12 +00:00
RecalLineBreaks(&fromLine, &toLine);
2003-08-08 09:55:12 +00:00
float newHeight = fTextRect.Height();
// if the line breaks have changed, force an erase
2004-06-24 13:46:58 +00:00
if (fromLine != saveFromLine || toLine != saveToLine
|| newHeight != saveHeight )
2003-08-08 09:55:12 +00:00
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));
}
2003-08-08 09:55:12 +00:00
// draw only those lines that are visible
int32 fromVisible = LineAt(BPoint(0.0f, bounds.top));
int32 toVisible = LineAt(BPoint(0.0f, bounds.bottom));
fromLine = max_c(fromVisible, fromLine);
toLine = min_c(toLine, toVisible);
2003-08-08 09:55:12 +00:00
int32 drawOffset = fromOffset;
if (LineHeight(fromLine) != saveLineHeight ||
newHeight < saveHeight || fromLine < saveFromLine )
2003-08-08 09:55:12 +00:00
drawOffset = (*fLines)[fromLine]->offset;
2004-06-24 13:46:58 +00:00
// TODO: Is it ok here ?
if (fResizable)
AutoResize(false);
2003-08-08 09:55:12 +00:00
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)) {
2003-08-08 09:55:12 +00:00
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);
2003-08-08 09:55:12 +00:00
Flush(); ////
}
2003-08-08 09:55:12 +00:00
void
BTextView::RecalLineBreaks(int32 *startLine, int32 *endLine)
{
CALLED();
2003-08-08 09:55:12 +00:00
// 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;
2003-08-08 09:55:12 +00:00
do {
float ascent, descent;
int32 fromOffset = curLine->offset;
int32 toOffset = FindLineBreak(fromOffset, &ascent,
2003-08-08 09:55:12 +00:00
&descent, &width);
// we want to advance at least by one character
int32 nextOffset = NextInitialByte(fromOffset);
if (toOffset < nextOffset && fromOffset < textLength)
toOffset = nextOffset;
2003-08-08 09:55:12 +00:00
// set the ascent of this line
curLine->ascent = ascent;
lineIndex++;
STELine saveLine = *nextLine;
if ( lineIndex > fLines->NumLines() ||
toOffset < nextLine->offset ) {
2003-08-08 09:55:12 +00:00
// 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 {
2003-08-08 09:55:12 +00:00
// 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 )
2003-08-08 09:55:12 +00:00
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)
2003-08-08 09:55:12 +00:00
*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 = min_c(*startLine, *endLine);
}
2003-08-08 09:55:12 +00:00
int32
BTextView::FindLineBreak(int32 fromOffset, float *outAscent,
float *outDescent, float *ioWidth)
{
CALLED();
2003-08-08 09:55:12 +00:00
*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, fInline, NULL, NULL, outAscent, outDescent);
2003-08-08 09:55:12 +00:00
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;
}
}
2003-08-08 09:55:12 +00:00
2003-10-13 14:38:43 +00:00
return limit;
}
2003-08-08 09:55:12 +00:00
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;
2003-08-08 09:55:12 +00:00
// 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;
2003-10-13 14:38:43 +00:00
int32 numChars;
while ((numChars = fStyles->Iterate(startOffset, length, fInline, NULL, NULL, &ascent, &descent)) != 0) {
*outAscent = max_c(ascent, *outAscent);
*outDescent = max_c(descent, *outDescent);
2003-08-08 09:55:12 +00:00
startOffset += numChars;
length -= numChars;
}
2003-10-13 14:38:43 +00:00
return offset;
}
2003-08-08 09:55:12 +00:00
// 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 {
2003-08-08 09:55:12 +00:00
// include all trailing spaces and tabs,
// but not spaces after tabs
if (theChar != B_SPACE && theChar != B_TAB)
2003-08-08 09:55:12 +00:00
break;
else {
if (theChar == B_SPACE && foundTab)
2003-08-08 09:55:12 +00:00
break;
else {
if (theChar == B_TAB)
2003-08-08 09:55:12 +00:00
foundTab = true;
}
}
}
}
delta = max_c(delta, 1);
2003-08-08 09:55:12 +00:00
deltaWidth = StyledWidth(offset, delta, &ascent, &descent);
strWidth += deltaWidth;
2003-08-08 09:55:12 +00:00
if (!foundTab)
tabWidth = 0.0;
else {
int32 tabCount = 0;
for (int32 i = delta - 1; (*fText)[offset + i] == B_TAB; i--)
2003-08-08 09:55:12 +00:00
tabCount++;
2004-06-24 13:46:58 +00:00
tabWidth = ActualTabWidth(strWidth);
2003-08-08 09:55:12 +00:00
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] != B_SPACE &&
(*fText)[offset + pos] != B_TAB &&
(*fText)[offset + pos] != '\n')
2003-08-08 09:55:12 +00:00
break;
strWidth -= (deltaWidth + tabWidth);
for ( ; ((offset + pos) > offset); pos--) {
uchar theChar = (*fText)[offset + pos];
if (theChar != B_SPACE &&
theChar != B_TAB &&
theChar != '\n')
2003-08-08 09:55:12 +00:00
break;
}
2003-08-08 09:55:12 +00:00
strWidth += StyledWidth(offset, pos + 1, &ascent, &descent);
if (strWidth >= *ioWidth)
break;
2003-08-08 09:55:12 +00:00
if (!foundNewline) {
for ( ; (offset + delta) < limit; delta++) {
if ((*fText)[offset + delta] != B_SPACE &&
(*fText)[offset + delta] != B_TAB)
2003-08-08 09:55:12 +00:00
break;
}
if ( (offset + delta) < limit &&
(*fText)[offset + delta] == '\n' )
2003-08-08 09:55:12 +00:00
delta++;
}
// get the ascent and descent of the spaces/tabs
StyledWidth(offset, delta, &ascent, &descent);
}
*outAscent = max_c(ascent, *outAscent);
*outDescent = max_c(descent, *outDescent);
2003-08-08 09:55:12 +00:00
offset += delta;
delta = 0;
} while (offset < limit && !done);
2003-08-08 09:55:12 +00:00
if (offset - fromOffset < 1) {
2003-08-08 09:55:12 +00:00
// 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++) {
int32 nextInitial = NextInitialByte(offset);
strWidth += StyledWidth(offset, nextInitial - offset, &ascent, &descent);
2003-08-08 09:55:12 +00:00
if (strWidth >= *ioWidth)
break;
*outAscent = max_c(ascent, *outAscent);
*outDescent = max_c(descent, *outDescent);
2003-08-08 09:55:12 +00:00
}
}
offset = min_c(offset, limit);
2003-08-08 09:55:12 +00:00
2003-10-13 14:38:43 +00:00
return offset;
}
/*! \brief Calculate the width of the text within the given limits.
\param fromOffset The offset where to start.
\param length The length of the text to examine.
\param outAscent A pointer to a float which will contain the maximum ascent.
\param outDescent A pointer to a float which will contain the maximum descent.
\return The width for the text within the given limits.
*/
2003-08-08 09:55:12 +00:00
float
BTextView::StyledWidth(int32 fromOffset, int32 length, float *outAscent,
float *outDescent) const
{
CALLED();
2003-08-08 09:55:12 +00:00
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;
2003-10-13 14:38:43 +00:00
int32 numChars;
while ((numChars = fStyles->Iterate(fromOffset, length, fInline, &font, NULL, &ascent, &descent)) != 0) {
maxAscent = max_c(ascent, maxAscent);
maxDescent = max_c(descent, maxDescent);
2003-08-08 09:55:12 +00:00
// Use _BWidthBuffer_ if possible (TODO: reenable this when it's fixed)
if (false && (sWidths != NULL)) {
LockWidthBuffer();
result += sWidths->StringWidth(*fText, fromOffset, numChars, font);
UnlockWidthBuffer();
} else
result += font->StringWidth(fText->Text() + fromOffset, numChars);
2003-08-08 09:55:12 +00:00
fromOffset += numChars;
length -= numChars;
}
if (outAscent != NULL)
*outAscent = maxAscent;
if (outDescent != NULL)
*outDescent = maxDescent;
2003-10-13 14:38:43 +00:00
return result;
}
/*! \brief Calculate the actual tab width for the given location.
\param location The location to calculate the tab width of.
\return The actual tab width for the given location
*/
2003-08-08 09:55:12 +00:00
float
BTextView::ActualTabWidth(float location) const
{
CALLED();
2004-06-24 13:46:58 +00:00
return fTabWidth - fmod(location, fTabWidth);
}
2003-08-08 09:55:12 +00:00
void
BTextView::DoInsertText(const char *inText, int32 inLength, int32 inOffset,
const text_run_array *inRuns,
_BTextChangeResult_ *outResult)
{
CALLED();
}
2003-08-08 09:55:12 +00:00
void
BTextView::DoDeleteText(int32 fromOffset, int32 toOffset,
_BTextChangeResult_ *outResult)
{
CALLED();
}
2003-08-08 09:55:12 +00:00
void
BTextView::DrawLines(int32 startLine, int32 endLine, int32 startOffset,
bool erase)
{
// TODO: Draw on the "fOffscreen" BBitmap, then draw it on the view
2003-08-08 09:55:12 +00:00
// clip the text
BRect clipRect = Bounds() & fTextRect;
clipRect.InsetBy(-1, -1);
BRegion newClip;
newClip.Set(clipRect);
ConstrainClippingRegion(&newClip);
2003-08-08 09:55:12 +00:00
// set the low color to the view color so that
// drawing to a non-white background will work
SetLowColor(ViewColor());
2003-08-08 09:55:12 +00:00
long maxLine = fLines->NumLines() - 1;
startLine = (startLine < 0) ? 0 : startLine;
endLine = (endLine > maxLine) ? maxLine : endLine;
// TODO: See if we can avoid this
if (fAlignment != B_ALIGN_LEFT)
erase = true;
BRect eraseRect = clipRect;
long startEraseLine = startLine;
STELinePtr line = (*fLines)[startLine];
if (erase && startOffset != -1) {
2003-08-08 09:55:12 +00:00
// erase only to the right of startOffset
startEraseLine++;
long startErase = startOffset;
if (startErase > line->offset) {
for ( ; ((*fText)[startErase] != B_SPACE) && ((*fText)[startErase] != B_TAB); startErase--) {
2003-08-08 09:55:12 +00:00
if (startErase <= line->offset)
break;
}
if (startErase > line->offset)
startErase--;
}
2003-08-08 09:55:12 +00:00
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;
}
float startLeft = fTextRect.left;
2003-08-08 09:55:12 +00:00
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--;
if (fAlignment != B_ALIGN_LEFT) {
// B_ALIGN_RIGHT
startLeft = (fTextRect.right - LineWidth(i));
if (fAlignment == B_ALIGN_CENTER)
startLeft /= 2;
startLeft += fTextRect.left;
}
MovePenTo(startLeft, line->origin + line->ascent + fTextRect.top);
2003-08-08 09:55:12 +00:00
if (erase && i >= startEraseLine) {
2003-08-08 09:55:12 +00:00
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) {
bool foundTab = false;
long tabChars = 0;
long numTabs = 0;
long offset = line->offset;
2003-08-08 09:55:12 +00:00
const BFont *font = NULL;
const rgb_color *color = NULL;
2003-10-13 14:38:43 +00:00
int32 numChars;
// iterate through each style on this line
while ((numChars = fStyles->Iterate(offset, length, fInline, &font, &color)) != 0) {
2003-08-08 09:55:12 +00:00
SetFont(font);
SetHighColor(*color);
tabChars = numChars;
do {
foundTab = fText->FindChar(B_TAB, offset, &tabChars);
2003-08-08 09:55:12 +00:00
if (foundTab) {
for (numTabs = 0; (tabChars + numTabs) < numChars; numTabs++) {
if ((*fText)[offset + tabChars + numTabs] != B_TAB)
2003-08-08 09:55:12 +00:00
break;
}
}
// TODO: Revisit this as it looks ugly, and it's not even efficient,
// as the red highlight is drawn over the blue one.
if (fInline && fInline->IsActive()) {
int32 inlineOffset = fInline->Offset();
int32 inlineLength = fInline->Length();
if (inlineOffset >= offset &&
inlineOffset + inlineLength <= offset + length) {
float height;
BPoint rightBottom = PointAt(inlineOffset + inlineLength, &height);
rightBottom.y += height;
BRect rect(PointAt(inlineOffset), rightBottom);
// Highlight in blue the inputted text
PushState();
SetLowColor(kBlueInputColor);
FillRect(rect, B_SOLID_LOW);
PopState();
// Highlight in red the selected part
if (fInline->SelectionLength() > 0) {
rightBottom = PointAt(inlineOffset + fInline->SelectionOffset() +
fInline->SelectionLength(), &height);
rightBottom.y += height;
rect.SetLeftTop(PointAt(inlineOffset + fInline->SelectionOffset()));
rect.SetRightBottom(rightBottom);
PushState();
SetLowColor(kRedInputColor);
FillRect(rect, B_SOLID_LOW);
PopState();
}
}
}
2003-08-08 09:55:12 +00:00
DrawString(fText->GetString(offset, tabChars), tabChars);
if (foundTab) {
float penPos = PenLocation().x - fTextRect.left;
2004-06-24 13:46:58 +00:00
float tabWidth = ActualTabWidth(penPos);
2003-08-08 09:55:12 +00:00
if (numTabs > 1)
tabWidth += ((numTabs - 1) * fTabWidth);
MovePenBy(tabWidth, 0.0);
tabChars += numTabs;
}
offset += tabChars;
length -= tabChars;
numChars -= tabChars;
tabChars = numChars;
numTabs = 0;
} while (foundTab && tabChars > 0);
2003-08-08 09:55:12 +00:00
}
}
2003-08-08 09:55:12 +00:00
line++;
}
2003-08-08 09:55:12 +00:00
ConstrainClippingRegion(NULL);
}
2003-08-08 09:55:12 +00:00
void
BTextView::DrawCaret(int32 offset)
{
CALLED();
2003-08-08 09:55:12 +00:00
//long lineNum = LineAt(offset);
//STELinePtr line = (*fLines)[lineNum];
float lineHeight;
BPoint caretPoint = PointAt(offset, &lineHeight);
caretPoint.x = min_c(caretPoint.x, fTextRect.right);
2003-08-08 09:55:12 +00:00
BRect caretRect;
caretRect.left = caretRect.right = caretPoint.x;
caretRect.top = caretPoint.y;
caretRect.bottom = caretPoint.y + lineHeight;
InvertRect(caretRect);
Flush(); ////
}
2004-06-24 13:46:58 +00:00
/*! \brief Inverts the blinking caret status.
Hides the caret if it is being shown, and if it's hidden, shows it.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::InvertCaret()
{
CALLED();
2003-08-08 09:55:12 +00:00
DrawCaret(fSelStart);
fCaretVisible = !fCaretVisible;
fCaretTime = system_time();
}
2004-06-24 13:46:58 +00:00
/*! \brief Place the blinking caret at the given offset.
\param offset The offset (zero based within the object's text) where to place
the blinking caret. If it's -1, hide the caret.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::DragCaret(int32 offset)
{
CALLED();
2003-08-08 09:55:12 +00:00
// 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) {
2003-08-08 09:55:12 +00:00
fDragOffset = -1;
return;
}
}
DrawCaret(offset);
}
fDragOffset = offset;
}
2003-08-08 09:55:12 +00:00
void
BTextView::StopMouseTracking()
{
CALLED();
}
2003-08-08 09:55:12 +00:00
bool
BTextView::PerformMouseUp(BPoint where)
{
CALLED();
return false;
}
bool BTextView::PerformMouseMoved(BPoint where, uint32 code)
{
CALLED();
return false;
}
/*! \brief Tracks the mouse position, doing special actions like changing the view cursor.
\param where The point where the mouse has moved.
\param message The dragging message, if there is any.
\param force Passed as second parameter of SetViewCursor()
*/
2003-08-08 09:55:12 +00:00
void
BTextView::TrackMouse(BPoint where, const BMessage *message, bool force)
{
CALLED();
BRegion textRegion;
GetTextRegion(fSelStart, fSelEnd, &textRegion);
if (AcceptsDrop(message))
TrackDrag(where);
else if (!textRegion.Contains(where))
SetViewCursor(B_CURSOR_I_BEAM, force);
// If we are within the selection, switch to the "hand" cursor,
else
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, force);
}
/*! \brief Tracks the mouse position when the user is dragging some data.
\param where The point where the mouse has moved.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::TrackDrag(BPoint where)
{
CALLED();
if (Bounds().Contains(where)) {
int32 offset = OffsetAt(where);
DragCaret(offset);
}
}
/*! \brief Function called to initiate a drag operation.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::InitiateDrag()
{
CALLED();
BMessage *drag = new BMessage(B_MIME_DATA);
2003-10-13 14:38:43 +00:00
BBitmap *dragBitmap = NULL;
BPoint bitmapPoint;
BHandler *dragHandler = NULL;
2003-08-08 09:55:12 +00:00
2003-10-13 14:38:43 +00:00
GetDragParameters(drag, &dragBitmap, &bitmapPoint, &dragHandler);
2003-08-08 09:55:12 +00:00
2003-10-13 14:38:43 +00:00
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
2003-08-08 09:55:12 +00:00
2003-10-13 14:38:43 +00:00
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);
}
}
/*! \brief Called when some data is dropped on the view.
\param inMessage The message which has been dropped.
\param where The location where the message has been dropped.
\param offset ?
\return \c true if the message was handled, \c false if not.
*/
2003-08-08 09:55:12 +00:00
bool
BTextView::MessageDropped(BMessage *inMessage, BPoint where, BPoint offset)
{
CALLED();
void *from = NULL;
bool internalDrop = false;
if (inMessage->FindPointer("be:originator", &from) == B_OK
&& from == this && fSelEnd != fSelStart)
internalDrop = true;
DragCaret(-1);
TrackMouse(where, NULL);
2003-08-08 09:55:12 +00:00
// are we sure we like this message?
if (!AcceptsDrop(inMessage))
return false;
long dropOffset = OffsetAt(where);
2003-10-13 14:38:43 +00:00
if (dropOffset > TextLength())
dropOffset = TextLength();
2003-08-08 09:55:12 +00:00
// if this view initiated the drag, move instead of copy
2003-10-13 14:38:43 +00:00
if (internalDrop) {
2003-08-08 09:55:12 +00:00
// dropping onto itself?
2003-10-13 14:38:43 +00:00
if (dropOffset >= fSelStart && dropOffset <= fSelEnd)
return true;
2003-08-08 09:55:12 +00:00
}
ssize_t dataLen = 0;
2003-10-13 14:38:43 +00:00
const char *text = NULL;
if (inMessage->FindData("text/plain", B_MIME_TYPE, (const void **)&text, &dataLen) == B_OK) {
2003-08-08 09:55:12 +00:00
2003-10-13 14:38:43 +00:00
text_run_array *runArray = NULL;
ssize_t runLen = 0;
if (fStylable)
2003-10-13 14:38:43 +00:00
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) {
if (dropOffset > fSelEnd)
dropOffset -= dataLen;
2003-10-13 14:38:43 +00:00
Delete();
}
2003-10-13 14:38:43 +00:00
Insert(dropOffset, text, dataLen, runArray);
}
2003-08-08 09:55:12 +00:00
return true;
}
2004-06-24 13:46:58 +00:00
/*! \brief Updates the scrollbars associated with the object (if any).
*/
2003-08-08 09:55:12 +00:00
void
BTextView::UpdateScrollbars()
{
CALLED();
2004-06-24 13:46:58 +00:00
BRect bounds(Bounds());
2003-08-08 09:55:12 +00:00
BScrollBar *hsb = ScrollBar(B_HORIZONTAL);
BScrollBar *vsb = ScrollBar(B_VERTICAL);
// do we have a horizontal scroll bar?
if (hsb != NULL) {
2004-06-24 13:46:58 +00:00
long viewWidth = bounds.IntegerWidth();
2003-08-08 09:55:12 +00:00
long dataWidth = fTextRect.IntegerWidth();
dataWidth += (long)ceil(fTextRect.left) + 1;
long maxRange = dataWidth - viewWidth;
maxRange = max_c(maxRange, 0);
2003-08-08 09:55:12 +00:00
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) {
2004-06-24 13:46:58 +00:00
long viewHeight = bounds.IntegerHeight();
2003-08-08 09:55:12 +00:00
long dataHeight = fTextRect.IntegerHeight();
dataHeight += (long)ceil(fTextRect.top) + 1;
long maxRange = dataHeight - viewHeight;
maxRange = max_c(maxRange, 0);
2003-08-08 09:55:12 +00:00
vsb->SetRange(0, maxRange);
vsb->SetProportion((float)viewHeight / (float)dataHeight);
vsb->SetSteps(12, viewHeight);
}
}
2004-06-24 13:46:58 +00:00
/*! \brief Autoresizes the view to fit the contained text.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::AutoResize(bool doredraw)
{
CALLED();
2004-06-24 13:46:58 +00:00
// TODO: Implement
}
2004-06-24 13:46:58 +00:00
/*! \brief Creates a new offscreen BBitmap with an associated BView.
param padding Padding (?)
Creates an offscreen BBitmap which will be used to draw.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::NewOffscreen(float padding)
{
CALLED();
if (fOffscreen != NULL)
2003-10-13 14:38:43 +00:00
DeleteOffscreen();
2004-06-24 13:46:58 +00:00
// TODO: Is this correct ?
BRect bitmapRect(0, 0, fTextRect.Width() + padding, fTextRect.Height());
fOffscreen = new BBitmap(bitmapRect, fColorSpace, true, false);
if (fOffscreen != NULL && fOffscreen->Lock()) {
2004-06-24 13:46:58 +00:00
BView *bufferView = new BView(bitmapRect, "drawing view", 0, 0);
fOffscreen->AddChild(bufferView);
fOffscreen->Unlock();
}
}
2004-06-24 13:46:58 +00:00
/*! \brief Deletes the textview's offscreen bitmap, if any.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::DeleteOffscreen()
{
CALLED();
if (fOffscreen != NULL && fOffscreen->Lock()) {
2003-10-13 14:38:43 +00:00
delete fOffscreen;
fOffscreen = NULL;
}
}
/*! \brief Creates a new offscreen bitmap, highlight the selection, and set the
cursor to B_CURSOR_I_BEAM.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::Activate()
{
CALLED();
2003-08-08 09:55:12 +00:00
fActive = true;
2004-06-24 13:46:58 +00:00
// Create a new offscreen BBitmap
NewOffscreen();
2003-08-08 09:55:12 +00:00
if (fSelStart != fSelEnd) {
if (fSelectable)
Highlight(fSelStart, fSelEnd);
} else {
2003-08-08 09:55:12 +00:00
if (fEditable)
InvertCaret();
}
BPoint where;
ulong buttons;
2003-08-08 09:55:12 +00:00
GetMouse(&where, &buttons);
if (Bounds().Contains(where))
2003-10-13 14:38:43 +00:00
SetViewCursor(B_CURSOR_I_BEAM);
}
/*! \brief Unhilights the selection, set the cursor to B_CURSOR_SYSTEM_DEFAULT.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::Deactivate()
{
CALLED();
2003-08-08 09:55:12 +00:00
fActive = false;
CancelInputMethod();
2003-08-08 09:55:12 +00:00
if (fSelStart != fSelEnd) {
if (fSelectable)
Highlight(fSelStart, fSelEnd);
} else {
2003-08-08 09:55:12 +00:00
if (fCaretVisible)
InvertCaret();
}
BPoint where;
ulong buttons;
2003-08-08 09:55:12 +00:00
GetMouse(&where, &buttons);
if (Bounds().Contains(where))
2003-10-13 14:38:43 +00:00
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
}
2004-06-24 13:46:58 +00:00
/*! \brief Changes the passed font to be displayable by the object.
\param font A pointer to the font to normalize.
Set font rotation to 0, removes any font flag, set font spacing
to \c B_BITMAP_SPACING and font encoding to \c B_UNICODE_UTF8
*/
2003-08-08 09:55:12 +00:00
void
BTextView::NormalizeFont(BFont *font)
{
CALLED();
2004-08-17 13:52:18 +00:00
if (font) {
font->SetRotation(0.0f);
font->SetFlags(0);
font->SetSpacing(B_BITMAP_SPACING);
font->SetEncoding(B_UNICODE_UTF8);
}
}
2004-06-24 13:46:58 +00:00
/*! \brief Returns a value which tells if the given charachter is a separator
charachter or not.
\param offset The offset where the wanted charachter can be found.
\return A value which represents the charachter's classification.
*/
2003-08-08 09:55:12 +00:00
uint32
BTextView::CharClassification(int32 offset) const
{
CALLED();
// TODO:Should check against a list of characters containing also
// japanese word breakers.
// And what about other languages ? Isn't there a better way to check
// for separator charachters ?
switch (fText->RealCharAt(offset)) {
case B_SPACE:
case '_':
case '.':
2003-10-13 14:38:43 +00:00
case '\0':
case B_TAB:
case B_ENTER:
2003-10-13 14:38:43 +00:00
case '&':
case '*':
case '+':
case '-':
case '/':
case '<':
case '=':
case '>':
case '\\':
case '^':
case '|':
return B_SEPARATOR_CHARACTER;
default:
return B_OTHER_CHARACTER;
}
}
2004-06-24 13:46:58 +00:00
/*! \brief Returns the offset of the next UTF8 charachter within the BTextView's text.
\param offset The offset where to start looking.
\return The offset of the next UTF8 charachter.
*/
2003-08-08 09:55:12 +00:00
int32
BTextView::NextInitialByte(int32 offset) const
{
CALLED();
2003-08-08 09:55:12 +00:00
const char *text = Text();
if (text == NULL)
return 0;
for (++offset; (*(text + offset) & 0xc0) == 0x80; ++offset)
;
return offset;
}
2004-06-24 13:46:58 +00:00
/*! \brief Returns the offset of the previous UTF8 charachter within the BTextView's text.
\param offset The offset where to start looking.
\return The offset of the previous UTF8 charachter.
*/
2003-08-08 09:55:12 +00:00
int32
BTextView::PreviousInitialByte(int32 offset) const
{
CALLED();
2003-08-08 09:55:12 +00:00
const char *text = Text();
int32 count = 6;
2003-08-08 09:55:12 +00:00
for (--offset; (text + offset) > text && count; --offset, --count) {
2003-08-08 09:55:12 +00:00
if ((*(text + offset) & 0xc0 ) != 0x80)
break;
}
return count ? offset : 0;
}
2003-08-08 09:55:12 +00:00
bool
BTextView::GetProperty(BMessage *specifier, int32 form,
const char *property, BMessage *reply)
{
CALLED();
if (strcmp(property, "Selection") == 0) {
2003-08-08 09:55:12 +00:00
reply->what = B_REPLY;
reply->AddInt32("result", fSelStart);
reply->AddInt32("result", fSelEnd);
reply->AddInt32("error", B_OK);
2003-08-08 09:55:12 +00:00
return true;
} else if (strcmp(property, "Text") == 0) {
2003-08-08 09:55:12 +00:00
int32 index, range;
2003-08-08 09:55:12 +00:00
specifier->FindInt32("index", &index);
specifier->FindInt32("range", &range);
char *buffer = new char[range + 1];
2003-08-08 09:55:12 +00:00
GetText(index, range, buffer);
reply->what = B_REPLY;
reply->AddString("result", buffer);
delete buffer;
reply->AddInt32("error", B_OK);
2003-08-08 09:55:12 +00:00
return true;
} else if (strcmp(property, "text_run_array") == 0)
2003-08-08 09:55:12 +00:00
return false;
else
return false;
}
2003-08-08 09:55:12 +00:00
bool
BTextView::SetProperty(BMessage *specifier, int32 form,
const char *property, BMessage *reply)
{
CALLED();
if (strcmp(property, "Selection") == 0) {
2003-08-08 09:55:12 +00:00
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) {
2003-08-08 09:55:12 +00:00
int32 index, range;
2003-08-08 09:55:12 +00:00
specifier->FindInt32("index", &index);
specifier->FindInt32("range", &range);
const char *buffer = NULL;
2003-08-08 09:55:12 +00:00
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)
2003-08-08 09:55:12 +00:00
return false;
2003-08-08 09:55:12 +00:00
else
return false;
}
2003-08-08 09:55:12 +00:00
bool
BTextView::CountProperties(BMessage *specifier, int32 form,
const char *property, BMessage *reply)
{
CALLED();
if (strcmp(property, "Text") == 0) {
2003-08-08 09:55:12 +00:00
reply->what = B_REPLY;
reply->AddInt32("result", TextLength());
reply->AddInt32("error", B_OK);
return true;
} else
2003-08-08 09:55:12 +00:00
return false;
}
/*! \brief Called when the object receives a B_INPUT_METHOD_CHANGE message.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::HandleInputMethodChanged(BMessage *message)
{
CALLED();
if (!fInline)
return;
const char *string = NULL;
2004-08-17 13:52:18 +00:00
if (message->FindString("be:string", &string) < B_OK || string == NULL)
return;
be_app->ObscureCursor();
// If we find the "be:confirmed" boolean (and the boolean is true),
// it means it's over for now, so the current _BInlineInput_ object
// should become inactive
bool confirmed = false;
if (message->FindBool("be:confirmed", &confirmed) == B_OK && confirmed) {
fInline->SetActive(false);
Refresh(0, fInline->Offset(), true, false);
return;
}
// Delete the previously inserted text (if any)
if (fInline->IsActive()) {
int32 oldOffset = fInline->Offset();
DeleteText(oldOffset, oldOffset + fInline->Length());
fClickOffset = fSelStart = fSelEnd = oldOffset;
}
int32 stringLen = strlen(string);
fInline->SetOffset(fSelStart);
fInline->SetLength(stringLen);
fInline->ResetClauses();
// Get the clauses, and pass them to the _BInlineInput_ object
// TODO: Find out if what we did it's ok, currently we don't consider clauses
// at all, while the bebook says we should; though the visual effect we obtained
// seems correct. Weird.
int32 clauseCount = 0;
int32 clauseStart;
int32 clauseEnd;
while (message->FindInt32("be:clause_start", clauseCount, &clauseStart) == B_OK &&
message->FindInt32("be:clause_end", clauseCount, &clauseEnd) == B_OK) {
fInline->AddClause(clauseStart, clauseEnd);
clauseCount++;
}
int32 selectionStart = 0;
int32 selectionEnd = 0;
message->FindInt32("be:selection", 0, &selectionStart);
message->FindInt32("be:selection", 1, &selectionEnd);
fInline->SetSelectionOffset(selectionStart);
fInline->SetSelectionLength(selectionEnd - selectionStart);
// Insert the new text
InsertText(string, stringLen, fSelStart, NULL);
fSelStart += stringLen;
fClickOffset = fSelEnd = fSelStart;
if (!fInline->IsActive())
fInline->SetActive(true);
Refresh(0, fSelEnd, true, false);
}
/*! \brief Called when the object receives a B_INPUT_METHOD_LOCATION_REQUEST message.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::HandleInputMethodLocationRequest()
{
CALLED();
if (!fInline)
return;
int32 offset = fInline->Offset();
int32 limit = offset + fInline->Length();
BMessage message(B_INPUT_METHOD_EVENT);
message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST);
// Add the location of the UTF8 charachters
float height = 0;
BPoint where;
while (offset < limit) {
where = PointAt(offset, &height);
ConvertToScreen(&where);
message.AddPoint("be:location_reply", where);
message.AddFloat("be:height_reply", height);
offset = NextInitialByte(offset);
}
fInline->Method()->SendMessage(&message);
}
/*! \brief Tells the input server method addon to stop the current transaction.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::CancelInputMethod()
{
CALLED();
if (!fInline)
return;
BMessage message(B_INPUT_METHOD_EVENT);
message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED);
fInline->Method()->SendMessage(&message);
delete fInline;
fInline = NULL;
if (Window())
Refresh(0, fText->Length(), true, false);
}
2004-06-24 13:46:58 +00:00
/*! \brief Locks the static _BWidthBuffer_ object to be able to access it safely.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::LockWidthBuffer()
{
CALLED();
if (atomic_add(&sWidthAtom, -1) <= 0) {
while (acquire_sem(sWidthSem) == B_INTERRUPTED)
;
}
}
2004-06-24 13:46:58 +00:00
/*! \brief Unlocks the static _BWidthBuffer_ object.
*/
2003-08-08 09:55:12 +00:00
void
BTextView::UnlockWidthBuffer()
{
CALLED();
if (atomic_add(&sWidthAtom, 1) < 0)
2003-08-08 09:55:12 +00:00
release_sem(sWidthSem);
}