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