vfs: check userland buffers before reading them.
[haiku.git] / src / apps / haikudepot / textview / TextEditor.cpp
blobbfd46ba8506efdad1820cbe74c0faf4e404f4712
1 /*
2 * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
6 #include "TextEditor.h"
8 #include <algorithm>
9 #include <stdio.h>
12 TextEditor::TextEditor()
14 fDocument(),
15 fLayout(),
16 fSelection(),
17 fCaretAnchorX(0.0f),
18 fStyleAtCaret(),
19 fEditingEnabled(true)
24 TextEditor::TextEditor(const TextEditor& other)
26 fDocument(other.fDocument),
27 fLayout(other.fLayout),
28 fSelection(other.fSelection),
29 fCaretAnchorX(other.fCaretAnchorX),
30 fStyleAtCaret(other.fStyleAtCaret),
31 fEditingEnabled(other.fEditingEnabled)
36 TextEditor::~TextEditor()
41 TextEditor&
42 TextEditor::operator=(const TextEditor& other)
44 if (this == &other)
45 return *this;
47 fDocument = other.fDocument;
48 fLayout = other.fLayout;
49 fSelection = other.fSelection;
50 fCaretAnchorX = other.fCaretAnchorX;
51 fStyleAtCaret = other.fStyleAtCaret;
52 fEditingEnabled = other.fEditingEnabled;
53 return *this;
57 bool
58 TextEditor::operator==(const TextEditor& other) const
60 if (this == &other)
61 return true;
63 return fDocument == other.fDocument
64 && fLayout == other.fLayout
65 && fSelection == other.fSelection
66 && fCaretAnchorX == other.fCaretAnchorX
67 && fStyleAtCaret == other.fStyleAtCaret
68 && fEditingEnabled == other.fEditingEnabled;
72 bool
73 TextEditor::operator!=(const TextEditor& other) const
75 return !(*this == other);
79 // #pragma mark -
82 void
83 TextEditor::SetDocument(const TextDocumentRef& ref)
85 fDocument = ref;
86 SetSelection(TextSelection());
90 void
91 TextEditor::SetLayout(const TextDocumentLayoutRef& ref)
93 fLayout = ref;
94 SetSelection(TextSelection());
98 void
99 TextEditor::SetEditingEnabled(bool enabled)
101 fEditingEnabled = enabled;
105 void
106 TextEditor::SetCaret(BPoint location, bool extendSelection)
108 if (fDocument.Get() == NULL || fLayout.Get() == NULL)
109 return;
111 bool rightOfChar = false;
112 int32 caretOffset = fLayout->TextOffsetAt(location.x, location.y,
113 rightOfChar);
115 if (rightOfChar)
116 caretOffset++;
118 _SetCaretOffset(caretOffset, true, extendSelection, true);
122 void
123 TextEditor::SelectAll()
125 if (fDocument.Get() == NULL)
126 return;
128 SetSelection(TextSelection(0, fDocument->Length()));
132 void
133 TextEditor::SetSelection(TextSelection selection)
135 _SetSelection(selection.Caret(), selection.Anchor(), true, true);
139 void
140 TextEditor::SetCharacterStyle(::CharacterStyle style)
142 if (fStyleAtCaret == style)
143 return;
145 fStyleAtCaret = style;
147 if (HasSelection()) {
148 // TODO: Apply style to selection range
153 void
154 TextEditor::KeyDown(KeyEvent event)
156 if (fDocument.Get() == NULL)
157 return;
159 bool select = (event.modifiers & B_SHIFT_KEY) != 0;
161 switch (event.key) {
162 case B_UP_ARROW:
163 LineUp(select);
164 break;
166 case B_DOWN_ARROW:
167 LineDown(select);
168 break;
170 case B_LEFT_ARROW:
171 if (HasSelection() && !select) {
172 _SetCaretOffset(
173 std::min(fSelection.Caret(), fSelection.Anchor()),
174 true, false, true);
175 } else
176 _SetCaretOffset(fSelection.Caret() - 1, true, select, true);
177 break;
179 case B_RIGHT_ARROW:
180 if (HasSelection() && !select) {
181 _SetCaretOffset(
182 std::max(fSelection.Caret(), fSelection.Anchor()),
183 true, false, true);
184 } else
185 _SetCaretOffset(fSelection.Caret() + 1, true, select, true);
186 break;
188 case B_HOME:
189 LineStart(select);
190 break;
192 case B_END:
193 LineEnd(select);
194 break;
196 case B_ENTER:
197 Insert(fSelection.Caret(), "\n");
198 break;
200 case B_TAB:
201 // TODO: Tab support in TextLayout
202 Insert(fSelection.Caret(), " ");
203 break;
205 case B_ESCAPE:
206 break;
208 case B_BACKSPACE:
209 if (HasSelection()) {
210 Remove(SelectionStart(), SelectionLength());
211 } else {
212 if (fSelection.Caret() > 0)
213 Remove(fSelection.Caret() - 1, 1);
215 break;
217 case B_DELETE:
218 if (HasSelection()) {
219 Remove(SelectionStart(), SelectionLength());
220 } else {
221 if (fSelection.Caret() < fDocument->Length())
222 Remove(fSelection.Caret(), 1);
224 break;
226 case B_INSERT:
227 // TODO: Toggle insert mode (or maybe just don't support it)
228 break;
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;
238 default:
239 if (event.bytes != NULL && event.length > 0) {
240 // Handle null-termintating the string
241 BString text(event.bytes, event.length);
243 Replace(SelectionStart(), SelectionLength(), text);
245 break;
250 status_t
251 TextEditor::Insert(int32 offset, const BString& string)
253 if (!fEditingEnabled || fDocument.Get() == NULL)
254 return B_ERROR;
256 status_t ret = fDocument->Insert(offset, string, fStyleAtCaret);
258 if (ret == B_OK) {
259 _SetCaretOffset(offset + string.CountChars(), true, false, true);
261 fDocument->PrintToStream();
264 return ret;
268 status_t
269 TextEditor::Remove(int32 offset, int32 length)
271 if (!fEditingEnabled || fDocument.Get() == NULL)
272 return B_ERROR;
274 status_t ret = fDocument->Remove(offset, length);
276 if (ret == B_OK) {
277 _SetCaretOffset(offset, true, false, true);
279 fDocument->PrintToStream();
282 return ret;
286 status_t
287 TextEditor::Replace(int32 offset, int32 length, const BString& string)
289 if (!fEditingEnabled || fDocument.Get() == NULL)
290 return B_ERROR;
292 status_t ret = fDocument->Replace(offset, length, string);
294 if (ret == B_OK) {
295 _SetCaretOffset(offset + string.CountChars(), true, false, true);
297 fDocument->PrintToStream();
300 return ret;
304 // #pragma mark -
307 void
308 TextEditor::LineUp(bool select)
310 if (fLayout.Get() == NULL)
311 return;
313 int32 lineIndex = fLayout->LineIndexForOffset(fSelection.Caret());
314 _MoveToLine(lineIndex - 1, select);
318 void
319 TextEditor::LineDown(bool select)
321 if (fLayout.Get() == NULL)
322 return;
324 int32 lineIndex = fLayout->LineIndexForOffset(fSelection.Caret());
325 _MoveToLine(lineIndex + 1, select);
329 void
330 TextEditor::LineStart(bool select)
332 if (fLayout.Get() == NULL)
333 return;
335 int32 lineIndex = fLayout->LineIndexForOffset(fSelection.Caret());
336 _SetCaretOffset(fLayout->FirstOffsetOnLine(lineIndex), true, select,
337 true);
341 void
342 TextEditor::LineEnd(bool select)
344 if (fLayout.Get() == NULL)
345 return;
347 int32 lineIndex = fLayout->LineIndexForOffset(fSelection.Caret());
348 _SetCaretOffset(fLayout->LastOffsetOnLine(lineIndex), true, select,
349 true);
353 // #pragma mark -
356 bool
357 TextEditor::HasSelection() const
359 return SelectionLength() > 0;
363 int32
364 TextEditor::SelectionStart() const
366 return std::min(fSelection.Caret(), fSelection.Anchor());
370 int32
371 TextEditor::SelectionEnd() const
373 return std::max(fSelection.Caret(), fSelection.Anchor());
377 int32
378 TextEditor::SelectionLength() const
380 return SelectionEnd() - SelectionStart();
384 // #pragma mark - private
387 // _MoveToLine
388 void
389 TextEditor::_MoveToLine(int32 lineIndex, bool select)
391 if (lineIndex < 0) {
392 // Move to beginning of line instead. Most editors do. Some only when
393 // selecting. Note that we are not updating the horizontal anchor here,
394 // even though the horizontal caret position changes. Most editors
395 // return to the previous horizonal offset when moving back down from
396 // the beginning of the line.
397 _SetCaretOffset(0, false, select, true);
398 return;
400 if (lineIndex >= fLayout->CountLines()) {
401 // Move to end of line instead, see above for why we do not update the
402 // horizontal anchor.
403 _SetCaretOffset(fDocument->Length(), false, select, true);
404 return;
407 float x1;
408 float y1;
409 float x2;
410 float y2;
411 fLayout->GetLineBounds(lineIndex , x1, y1, x2, y2);
413 bool rightOfCenter;
414 int32 textOffset = fLayout->TextOffsetAt(fCaretAnchorX, (y1 + y2) / 2,
415 rightOfCenter);
417 if (rightOfCenter)
418 textOffset++;
420 _SetCaretOffset(textOffset, false, select, true);
423 void
424 TextEditor::_SetCaretOffset(int32 offset, bool updateAnchor,
425 bool lockSelectionAnchor, bool updateSelectionStyle)
427 if (fDocument.Get() == NULL)
428 return;
430 if (offset < 0)
431 offset = 0;
432 int32 textLength = fDocument->Length();
433 if (offset > textLength)
434 offset = textLength;
436 int32 caret = offset;
437 int32 anchor = lockSelectionAnchor ? fSelection.Anchor() : offset;
438 _SetSelection(caret, anchor, updateAnchor, updateSelectionStyle);
442 void
443 TextEditor::_SetSelection(int32 caret, int32 anchor, bool updateAnchor,
444 bool updateSelectionStyle)
446 if (fLayout.Get() == NULL)
447 return;
449 if (caret == fSelection.Caret() && anchor == fSelection.Anchor())
450 return;
452 fSelection.SetCaret(caret);
453 fSelection.SetAnchor(anchor);
455 if (updateAnchor) {
456 float x1;
457 float y1;
458 float x2;
459 float y2;
461 fLayout->GetTextBounds(caret, x1, y1, x2, y2);
462 fCaretAnchorX = x1;
465 if (updateSelectionStyle)
466 _UpdateStyleAtCaret();
470 void
471 TextEditor::_UpdateStyleAtCaret()
473 if (fDocument.Get() == NULL)
474 return;
476 int32 offset = fSelection.Caret() - 1;
477 if (offset < 0)
478 offset = 0;
479 SetCharacterStyle(fDocument->CharacterStyleAt(offset));