1210284Sjmallett/*
2232812Sjmallett * Copyright 2014, Stephan A��mus <superstippi@gmx.de>.
3215990Sjmallett * All rights reserved. Distributed under the terms of the MIT License.
4210284Sjmallett */
5210284Sjmallett
6215990Sjmallett#include "TextEditor.h"
7215990Sjmallett
8215990Sjmallett#include <algorithm>
9210284Sjmallett#include <stdio.h>
10215990Sjmallett
11215990Sjmallett
12210284SjmallettTextEditor::TextEditor()
13215990Sjmallett	:
14215990Sjmallett	fDocument(),
15215990Sjmallett	fLayout(),
16215990Sjmallett	fSelection(),
17215990Sjmallett	fCaretAnchorX(0.0f),
18232812Sjmallett	fStyleAtCaret(),
19215990Sjmallett	fEditingEnabled(true)
20215990Sjmallett{
21215990Sjmallett}
22215990Sjmallett
23215990Sjmallett
24215990SjmallettTextEditor::TextEditor(const TextEditor& other)
25215990Sjmallett	:
26215990Sjmallett	fDocument(other.fDocument),
27215990Sjmallett	fLayout(other.fLayout),
28215990Sjmallett	fSelection(other.fSelection),
29232812Sjmallett	fCaretAnchorX(other.fCaretAnchorX),
30215990Sjmallett	fStyleAtCaret(other.fStyleAtCaret),
31215990Sjmallett	fEditingEnabled(other.fEditingEnabled)
32215990Sjmallett{
33215990Sjmallett}
34215990Sjmallett
35215990Sjmallett
36215990SjmallettTextEditor::~TextEditor()
37215990Sjmallett{
38210284Sjmallett}
39210284Sjmallett
40215990Sjmallett
41210284SjmallettTextEditor&
42210284SjmallettTextEditor::operator=(const TextEditor& other)
43210284Sjmallett{
44210284Sjmallett	if (this == &other)
45210284Sjmallett		return *this;
46210284Sjmallett
47210284Sjmallett	fDocument = other.fDocument;
48210284Sjmallett	fLayout = other.fLayout;
49210284Sjmallett	fSelection = other.fSelection;
50215990Sjmallett	fCaretAnchorX = other.fCaretAnchorX;
51215990Sjmallett	fStyleAtCaret = other.fStyleAtCaret;
52215990Sjmallett	fEditingEnabled = other.fEditingEnabled;
53215990Sjmallett	return *this;
54210284Sjmallett}
55210284Sjmallett
56210284Sjmallett
57210284Sjmallettbool
58210284SjmallettTextEditor::operator==(const TextEditor& other) const
59210284Sjmallett{
60210284Sjmallett	if (this == &other)
61210284Sjmallett		return true;
62210284Sjmallett
63210284Sjmallett	return fDocument == other.fDocument
64210284Sjmallett		&& fLayout == other.fLayout
65210284Sjmallett		&& fSelection == other.fSelection
66210284Sjmallett		&& fCaretAnchorX == other.fCaretAnchorX
67210284Sjmallett		&& fStyleAtCaret == other.fStyleAtCaret
68210284Sjmallett		&& fEditingEnabled == other.fEditingEnabled;
69210284Sjmallett}
70210284Sjmallett
71210284Sjmallett
72210284Sjmallettbool
73210284SjmallettTextEditor::operator!=(const TextEditor& other) const
74210284Sjmallett{
75210284Sjmallett	return !(*this == other);
76210284Sjmallett}
77210284Sjmallett
78215990Sjmallett
79243470Sjmallett// #pragma mark -
80243470Sjmallett
81243470Sjmallett
82210284Sjmallettvoid
83210284SjmallettTextEditor::SetDocument(const TextDocumentRef& ref)
84215990Sjmallett{
85210284Sjmallett	fDocument = ref;
86210284Sjmallett	SetSelection(TextSelection());
87215990Sjmallett}
88215990Sjmallett
89215990Sjmallett
90215990Sjmallettvoid
91215990SjmallettTextEditor::SetLayout(const TextDocumentLayoutRef& ref)
92210284Sjmallett{
93210284Sjmallett	fLayout = ref;
94210284Sjmallett	SetSelection(TextSelection());
95210284Sjmallett}
96210284Sjmallett
97210284Sjmallett
98210284Sjmallettvoid
99215990SjmallettTextEditor::SetEditingEnabled(bool enabled)
100210284Sjmallett{
101210284Sjmallett	fEditingEnabled = enabled;
102210284Sjmallett}
103210284Sjmallett
104210284Sjmallett
105210284Sjmallettvoid
106210284SjmallettTextEditor::SetCaret(BPoint location, bool extendSelection)
107210284Sjmallett{
108210284Sjmallett	if (!fDocument.IsSet() || !fLayout.IsSet())
109210284Sjmallett		return;
110210284Sjmallett
111210284Sjmallett	bool rightOfChar = false;
112210284Sjmallett	int32 caretOffset = fLayout->TextOffsetAt(location.x, location.y,
113210284Sjmallett		rightOfChar);
114210284Sjmallett
115210284Sjmallett	if (rightOfChar)
116210284Sjmallett		caretOffset++;
117215990Sjmallett
118215990Sjmallett	_SetCaretOffset(caretOffset, true, extendSelection, true);
119210284Sjmallett}
120210284Sjmallett
121210284Sjmallett
122210284Sjmallettvoid
123210284SjmallettTextEditor::SelectAll()
124210284Sjmallett{
125210284Sjmallett	if (!fDocument.IsSet())
126215990Sjmallett		return;
127210284Sjmallett
128210284Sjmallett	SetSelection(TextSelection(0, fDocument->Length()));
129210284Sjmallett}
130210284Sjmallett
131210284Sjmallett
132210284Sjmallettvoid
133210284SjmallettTextEditor::SetSelection(TextSelection selection)
134210284Sjmallett{
135232812Sjmallett	_SetSelection(selection.Caret(), selection.Anchor(), true, true);
136232812Sjmallett}
137232812Sjmallett
138210284Sjmallett
139210284Sjmallettvoid
140210284SjmallettTextEditor::SetCharacterStyle(::CharacterStyle style)
141210284Sjmallett{
142210284Sjmallett	if (fStyleAtCaret == style)
143210284Sjmallett		return;
144210284Sjmallett
145210284Sjmallett	fStyleAtCaret = style;
146210284Sjmallett
147210284Sjmallett	if (HasSelection()) {
148210284Sjmallett		// TODO: Apply style to selection range
149210284Sjmallett	}
150210284Sjmallett}
151210284Sjmallett
152210284Sjmallett
153210284Sjmallettvoid
154210284SjmallettTextEditor::KeyDown(KeyEvent event)
155210284Sjmallett{
156210284Sjmallett	if (!fDocument.IsSet())
157210284Sjmallett		return;
158210284Sjmallett
159210284Sjmallett	bool select = (event.modifiers & B_SHIFT_KEY) != 0;
160210284Sjmallett
161210284Sjmallett	switch (event.key) {
162210284Sjmallett		case B_UP_ARROW:
163210284Sjmallett			LineUp(select);
164210284Sjmallett			break;
165210284Sjmallett
166210284Sjmallett		case B_DOWN_ARROW:
167210284Sjmallett			LineDown(select);
168210284Sjmallett			break;
169210284Sjmallett
170210284Sjmallett		case B_LEFT_ARROW:
171210284Sjmallett			if (HasSelection() && !select) {
172210284Sjmallett				_SetCaretOffset(
173210284Sjmallett					std::min(fSelection.Caret(), fSelection.Anchor()),
174210284Sjmallett					true, false, true);
175210284Sjmallett			} else
176210284Sjmallett				_SetCaretOffset(fSelection.Caret() - 1, true, select, true);
177210284Sjmallett			break;
178210284Sjmallett
179210284Sjmallett		case B_RIGHT_ARROW:
180210284Sjmallett			if (HasSelection() && !select) {
181210284Sjmallett				_SetCaretOffset(
182210284Sjmallett					std::max(fSelection.Caret(), fSelection.Anchor()),
183210284Sjmallett					true, false, true);
184210284Sjmallett			} else
185210284Sjmallett				_SetCaretOffset(fSelection.Caret() + 1, true, select, true);
186210284Sjmallett			break;
187210284Sjmallett
188210284Sjmallett		case B_HOME:
189210284Sjmallett			LineStart(select);
190210284Sjmallett			break;
191210284Sjmallett
192210284Sjmallett		case B_END:
193232812Sjmallett			LineEnd(select);
194210284Sjmallett			break;
195210284Sjmallett
196210284Sjmallett		case B_ENTER:
197210284Sjmallett			Insert(fSelection.Caret(), "\n");
198210284Sjmallett			break;
199210284Sjmallett
200210284Sjmallett		case B_TAB:
201210284Sjmallett			// TODO: Tab support in TextLayout
202210284Sjmallett			Insert(fSelection.Caret(), " ");
203210284Sjmallett			break;
204210284Sjmallett
205210284Sjmallett		case B_ESCAPE:
206210284Sjmallett			break;
207210284Sjmallett
208210284Sjmallett		case B_BACKSPACE:
209210284Sjmallett			if (HasSelection()) {
210210284Sjmallett				Remove(SelectionStart(), SelectionLength());
211210284Sjmallett			} else {
212210284Sjmallett				if (fSelection.Caret() > 0)
213210284Sjmallett					Remove(fSelection.Caret() - 1, 1);
214210284Sjmallett			}
215210284Sjmallett			break;
216
217		case B_DELETE:
218			if (HasSelection()) {
219				Remove(SelectionStart(), SelectionLength());
220			} else {
221				if (fSelection.Caret() < fDocument->Length())
222					Remove(fSelection.Caret(), 1);
223			}
224			break;
225
226		case B_INSERT:
227			// TODO: Toggle insert mode (or maybe just don't support it)
228			break;
229
230		case B_PAGE_UP:
231		case B_PAGE_DOWN:
232		case B_SUBSTITUTE:
233		case B_FUNCTION_KEY:
234		case B_KATAKANA_HIRAGANA:
235		case B_HANKAKU_ZENKAKU:
236			break;
237
238		default:
239			if (event.bytes != NULL && event.length > 0) {
240				// Handle null-termintating the string
241				BString text(event.bytes, event.length);
242
243				Replace(SelectionStart(), SelectionLength(), text);
244			}
245			break;
246	}
247}
248
249
250status_t
251TextEditor::Insert(int32 offset, const BString& string)
252{
253	if (!fEditingEnabled || !fDocument.IsSet())
254		return B_ERROR;
255
256	status_t ret = fDocument->Insert(offset, string, fStyleAtCaret);
257
258	if (ret == B_OK) {
259		_SetCaretOffset(offset + string.CountChars(), true, false, true);
260	}
261
262	return ret;
263}
264
265
266status_t
267TextEditor::Remove(int32 offset, int32 length)
268{
269	if (!fEditingEnabled || !fDocument.IsSet())
270		return B_ERROR;
271
272	status_t ret = fDocument->Remove(offset, length);
273
274	if (ret == B_OK) {
275		_SetCaretOffset(offset, true, false, true);
276	}
277
278	return ret;
279}
280
281
282status_t
283TextEditor::Replace(int32 offset, int32 length, const BString& string)
284{
285	if (!fEditingEnabled || !fDocument.IsSet())
286		return B_ERROR;
287
288	status_t ret = fDocument->Replace(offset, length, string);
289
290	if (ret == B_OK) {
291		_SetCaretOffset(offset + string.CountChars(), true, false, true);
292	}
293
294	return ret;
295}
296
297
298// #pragma mark -
299
300
301void
302TextEditor::LineUp(bool select)
303{
304	if (!fLayout.IsSet())
305		return;
306
307	int32 lineIndex = fLayout->LineIndexForOffset(fSelection.Caret());
308	_MoveToLine(lineIndex - 1, select);
309}
310
311
312void
313TextEditor::LineDown(bool select)
314{
315	if (!fLayout.IsSet())
316		return;
317
318	int32 lineIndex = fLayout->LineIndexForOffset(fSelection.Caret());
319	_MoveToLine(lineIndex + 1, select);
320}
321
322
323void
324TextEditor::LineStart(bool select)
325{
326	if (!fLayout.IsSet())
327		return;
328
329	int32 lineIndex = fLayout->LineIndexForOffset(fSelection.Caret());
330	_SetCaretOffset(fLayout->FirstOffsetOnLine(lineIndex), true, select,
331		true);
332}
333
334
335void
336TextEditor::LineEnd(bool select)
337{
338	if (!fLayout.IsSet())
339		return;
340
341	int32 lineIndex = fLayout->LineIndexForOffset(fSelection.Caret());
342	_SetCaretOffset(fLayout->LastOffsetOnLine(lineIndex), true, select,
343		true);
344}
345
346
347// #pragma mark -
348
349
350bool
351TextEditor::HasSelection() const
352{
353	return SelectionLength() > 0;
354}
355
356
357int32
358TextEditor::SelectionStart() const
359{
360	return std::min(fSelection.Caret(), fSelection.Anchor());
361}
362
363
364int32
365TextEditor::SelectionEnd() const
366{
367	return std::max(fSelection.Caret(), fSelection.Anchor());
368}
369
370
371int32
372TextEditor::SelectionLength() const
373{
374	return SelectionEnd() - SelectionStart();
375}
376
377
378// #pragma mark - private
379
380
381// _MoveToLine
382void
383TextEditor::_MoveToLine(int32 lineIndex, bool select)
384{
385	if (lineIndex < 0) {
386		// Move to beginning of line instead. Most editors do. Some only when
387		// selecting. Note that we are not updating the horizontal anchor here,
388		// even though the horizontal caret position changes. Most editors
389		// return to the previous horizonal offset when moving back down from
390		// the beginning of the line.
391		_SetCaretOffset(0, false, select, true);
392		return;
393	}
394	if (lineIndex >= fLayout->CountLines()) {
395		// Move to end of line instead, see above for why we do not update the
396		// horizontal anchor.
397		_SetCaretOffset(fDocument->Length(), false, select, true);
398		return;
399	}
400
401	float x1;
402	float y1;
403	float x2;
404	float y2;
405	fLayout->GetLineBounds(lineIndex , x1, y1, x2, y2);
406
407	bool rightOfCenter;
408	int32 textOffset = fLayout->TextOffsetAt(fCaretAnchorX, (y1 + y2) / 2,
409		rightOfCenter);
410
411	if (rightOfCenter)
412		textOffset++;
413
414	_SetCaretOffset(textOffset, false, select, true);
415}
416
417void
418TextEditor::_SetCaretOffset(int32 offset, bool updateAnchor,
419	bool lockSelectionAnchor, bool updateSelectionStyle)
420{
421	if (!fDocument.IsSet())
422		return;
423
424	if (offset < 0)
425		offset = 0;
426	int32 textLength = fDocument->Length();
427	if (offset > textLength)
428		offset = textLength;
429
430	int32 caret = offset;
431	int32 anchor = lockSelectionAnchor ? fSelection.Anchor() : offset;
432	_SetSelection(caret, anchor, updateAnchor, updateSelectionStyle);
433}
434
435
436void
437TextEditor::_SetSelection(int32 caret, int32 anchor, bool updateAnchor,
438	bool updateSelectionStyle)
439{
440	if (!fLayout.IsSet())
441		return;
442
443	if (caret == fSelection.Caret() && anchor == fSelection.Anchor())
444		return;
445
446	fSelection.SetCaret(caret);
447	fSelection.SetAnchor(anchor);
448
449	if (updateAnchor) {
450		float x1;
451		float y1;
452		float x2;
453		float y2;
454
455		fLayout->GetTextBounds(caret, x1, y1, x2, y2);
456		fCaretAnchorX = x1;
457	}
458
459	if (updateSelectionStyle)
460		_UpdateStyleAtCaret();
461}
462
463
464void
465TextEditor::_UpdateStyleAtCaret()
466{
467	if (!fDocument.IsSet())
468		return;
469
470	int32 offset = fSelection.Caret() - 1;
471	if (offset < 0)
472		offset = 0;
473	SetCharacterStyle(fDocument->CharacterStyleAt(offset));
474}
475
476
477