* Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "TextEditor.h"
#include <algorithm>
#include <stdio.h>
TextEditor::TextEditor()
:
fDocument(),
fLayout(),
fSelection(),
fCaretAnchorX(0.0f),
fStyleAtCaret(),
fEditingEnabled(true)
{
}
TextEditor::TextEditor(const TextEditor& other)
:
fDocument(other.fDocument),
fLayout(other.fLayout),
fSelection(other.fSelection),
fCaretAnchorX(other.fCaretAnchorX),
fStyleAtCaret(other.fStyleAtCaret),
fEditingEnabled(other.fEditingEnabled)
{
}
TextEditor::~TextEditor()
{
}
TextEditor&
TextEditor::operator=(const TextEditor& other)
{
if (this == &other)
return *this;
fDocument = other.fDocument;
fLayout = other.fLayout;
fSelection = other.fSelection;
fCaretAnchorX = other.fCaretAnchorX;
fStyleAtCaret = other.fStyleAtCaret;
fEditingEnabled = other.fEditingEnabled;
return *this;
}
bool
TextEditor::operator==(const TextEditor& other) const
{
if (this == &other)
return true;
return fDocument == other.fDocument
&& fLayout == other.fLayout
&& fSelection == other.fSelection
&& fCaretAnchorX == other.fCaretAnchorX
&& fStyleAtCaret == other.fStyleAtCaret
&& fEditingEnabled == other.fEditingEnabled;
}
bool
TextEditor::operator!=(const TextEditor& other) const
{
return !(*this == other);
}
void
TextEditor::SetDocument(const TextDocumentRef& ref)
{
fDocument = ref;
SetSelection(TextSelection());
}
void
TextEditor::SetLayout(const TextDocumentLayoutRef& ref)
{
fLayout = ref;
SetSelection(TextSelection());
}
void
TextEditor::SetEditingEnabled(bool enabled)
{
fEditingEnabled = enabled;
}
void
TextEditor::SetCaret(BPoint location, bool extendSelection)
{
if (!fDocument.IsSet() || !fLayout.IsSet())
return;
bool rightOfChar = false;
int32 caretOffset = fLayout->TextOffsetAt(location.x, location.y,
rightOfChar);
if (rightOfChar)
caretOffset++;
_SetCaretOffset(caretOffset, true, extendSelection, true);
}
void
TextEditor::SelectAll()
{
if (!fDocument.IsSet())
return;
SetSelection(TextSelection(0, fDocument->Length()));
}
void
TextEditor::SetSelection(TextSelection selection)
{
_SetSelection(selection.Caret(), selection.Anchor(), true, true);
}
void
TextEditor::SetCharacterStyle(::CharacterStyle style)
{
if (fStyleAtCaret == style)
return;
fStyleAtCaret = style;
if (HasSelection()) {
}
}
void
TextEditor::KeyDown(KeyEvent event)
{
if (!fDocument.IsSet())
return;
bool select = (event.modifiers & B_SHIFT_KEY) != 0;
switch (event.key) {
case B_UP_ARROW:
LineUp(select);
break;
case B_DOWN_ARROW:
LineDown(select);
break;
case B_LEFT_ARROW:
if (HasSelection() && !select) {
_SetCaretOffset(
std::min(fSelection.Caret(), fSelection.Anchor()),
true, false, true);
} else
_SetCaretOffset(fSelection.Caret() - 1, true, select, true);
break;
case B_RIGHT_ARROW:
if (HasSelection() && !select) {
_SetCaretOffset(
std::max(fSelection.Caret(), fSelection.Anchor()),
true, false, true);
} else
_SetCaretOffset(fSelection.Caret() + 1, true, select, true);
break;
case B_HOME:
LineStart(select);
break;
case B_END:
LineEnd(select);
break;
case B_ENTER:
Insert(fSelection.Caret(), "\n");
break;
case B_TAB:
Insert(fSelection.Caret(), " ");
break;
case B_ESCAPE:
break;
case B_BACKSPACE:
if (HasSelection()) {
Remove(SelectionStart(), SelectionLength());
} else {
if (fSelection.Caret() > 0)
Remove(fSelection.Caret() - 1, 1);
}
break;
case B_DELETE:
if (HasSelection()) {
Remove(SelectionStart(), SelectionLength());
} else {
if (fSelection.Caret() < fDocument->Length())
Remove(fSelection.Caret(), 1);
}
break;
case B_INSERT:
break;
case B_PAGE_UP:
case B_PAGE_DOWN:
case B_SUBSTITUTE:
case B_FUNCTION_KEY:
case B_KATAKANA_HIRAGANA:
case B_HANKAKU_ZENKAKU:
break;
default:
if (event.bytes != NULL && event.length > 0) {
BString text(event.bytes, event.length);
Replace(SelectionStart(), SelectionLength(), text);
}
break;
}
}
status_t
TextEditor::Insert(int32 offset, const BString& string)
{
if (!fEditingEnabled || !fDocument.IsSet())
return B_ERROR;
status_t ret = fDocument->Insert(offset, string, fStyleAtCaret);
if (ret == B_OK) {
_SetCaretOffset(offset + string.CountChars(), true, false, true);
}
return ret;
}
status_t
TextEditor::Remove(int32 offset, int32 length)
{
if (!fEditingEnabled || !fDocument.IsSet())
return B_ERROR;
status_t ret = fDocument->Remove(offset, length);
if (ret == B_OK) {
_SetCaretOffset(offset, true, false, true);
}
return ret;
}
status_t
TextEditor::Replace(int32 offset, int32 length, const BString& string)
{
if (!fEditingEnabled || !fDocument.IsSet())
return B_ERROR;
status_t ret = fDocument->Replace(offset, length, string);
if (ret == B_OK) {
_SetCaretOffset(offset + string.CountChars(), true, false, true);
}
return ret;
}
void
TextEditor::LineUp(bool select)
{
if (!fLayout.IsSet())
return;
int32 lineIndex = fLayout->LineIndexForOffset(fSelection.Caret());
_MoveToLine(lineIndex - 1, select);
}
void
TextEditor::LineDown(bool select)
{
if (!fLayout.IsSet())
return;
int32 lineIndex = fLayout->LineIndexForOffset(fSelection.Caret());
_MoveToLine(lineIndex + 1, select);
}
void
TextEditor::LineStart(bool select)
{
if (!fLayout.IsSet())
return;
int32 lineIndex = fLayout->LineIndexForOffset(fSelection.Caret());
_SetCaretOffset(fLayout->FirstOffsetOnLine(lineIndex), true, select,
true);
}
void
TextEditor::LineEnd(bool select)
{
if (!fLayout.IsSet())
return;
int32 lineIndex = fLayout->LineIndexForOffset(fSelection.Caret());
_SetCaretOffset(fLayout->LastOffsetOnLine(lineIndex), true, select,
true);
}
bool
TextEditor::HasSelection() const
{
return SelectionLength() > 0;
}
int32
TextEditor::SelectionStart() const
{
return std::min(fSelection.Caret(), fSelection.Anchor());
}
int32
TextEditor::SelectionEnd() const
{
return std::max(fSelection.Caret(), fSelection.Anchor());
}
int32
TextEditor::SelectionLength() const
{
return SelectionEnd() - SelectionStart();
}
void
TextEditor::_MoveToLine(int32 lineIndex, bool select)
{
if (lineIndex < 0) {
_SetCaretOffset(0, false, select, true);
return;
}
if (lineIndex >= fLayout->CountLines()) {
_SetCaretOffset(fDocument->Length(), false, select, true);
return;
}
float x1;
float y1;
float x2;
float y2;
fLayout->GetLineBounds(lineIndex , x1, y1, x2, y2);
bool rightOfCenter;
int32 textOffset = fLayout->TextOffsetAt(fCaretAnchorX, (y1 + y2) / 2,
rightOfCenter);
if (rightOfCenter)
textOffset++;
_SetCaretOffset(textOffset, false, select, true);
}
void
TextEditor::_SetCaretOffset(int32 offset, bool updateAnchor,
bool lockSelectionAnchor, bool updateSelectionStyle)
{
if (!fDocument.IsSet())
return;
if (offset < 0)
offset = 0;
int32 textLength = fDocument->Length();
if (offset > textLength)
offset = textLength;
int32 caret = offset;
int32 anchor = lockSelectionAnchor ? fSelection.Anchor() : offset;
_SetSelection(caret, anchor, updateAnchor, updateSelectionStyle);
}
void
TextEditor::_SetSelection(int32 caret, int32 anchor, bool updateAnchor,
bool updateSelectionStyle)
{
if (!fLayout.IsSet())
return;
if (caret == fSelection.Caret() && anchor == fSelection.Anchor())
return;
fSelection.SetCaret(caret);
fSelection.SetAnchor(anchor);
if (updateAnchor) {
float x1;
float y1;
float x2;
float y2;
fLayout->GetTextBounds(caret, x1, y1, x2, y2);
fCaretAnchorX = x1;
}
if (updateSelectionStyle)
_UpdateStyleAtCaret();
}
void
TextEditor::_UpdateStyleAtCaret()
{
if (!fDocument.IsSet())
return;
int32 offset = fSelection.Caret() - 1;
if (offset < 0)
offset = 0;
SetCharacterStyle(fDocument->CharacterStyleAt(offset));
}