1 /* 2 * Copyright 2001-2020 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus, superstippi@gmx.de 7 * Stefano Ceccherini, stefano.ceccherini@gmail.com 8 * Marc Flerackers, mflerackers@androme.be 9 * Hiroshi Lockheimer (BTextView is based on his STEEngine) 10 * John Scipione, jscipione@gmail.com 11 * Oliver Tappe, zooey@hirschkaefer.de 12 */ 13 14 15 // TODOs: 16 // - Consider using BObjectList instead of BList 17 // for disallowed characters (it would remove a lot of reinterpret_casts) 18 // - Check for correctness and possible optimizations the calls to _Refresh(), 19 // to refresh only changed parts of text (currently we often redraw the whole 20 // text) 21 22 // Known Bugs: 23 // - Double buffering doesn't work well (disabled by default) 24 25 26 #include <TextView.h> 27 28 #include <algorithm> 29 #include <new> 30 31 #include <stdio.h> 32 #include <stdlib.h> 33 34 #include <Alignment.h> 35 #include <Application.h> 36 #include <Beep.h> 37 #include <Bitmap.h> 38 #include <Clipboard.h> 39 #include <ControlLook.h> 40 #include <Debug.h> 41 #include <Entry.h> 42 #include <Input.h> 43 #include <LayoutBuilder.h> 44 #include <LayoutUtils.h> 45 #include <MessageRunner.h> 46 #include <Path.h> 47 #include <PopUpMenu.h> 48 #include <PropertyInfo.h> 49 #include <Region.h> 50 #include <ScrollBar.h> 51 #include <SystemCatalog.h> 52 #include <Window.h> 53 54 #include <binary_compatibility/Interface.h> 55 56 #include "InlineInput.h" 57 #include "LineBuffer.h" 58 #include "StyleBuffer.h" 59 #include "TextGapBuffer.h" 60 #include "UndoBuffer.h" 61 #include "WidthBuffer.h" 62 63 64 using namespace std; 65 using BPrivate::gSystemCatalog; 66 67 68 #undef B_TRANSLATION_CONTEXT 69 #define B_TRANSLATION_CONTEXT "TextView" 70 71 72 #define TRANSLATE(str) \ 73 gSystemCatalog.GetString(B_TRANSLATE_MARK(str), "TextView") 74 75 #undef TRACE 76 #undef CALLED 77 //#define TRACE_TEXT_VIEW 78 #ifdef TRACE_TEXT_VIEW 79 # include <FunctionTracer.h> 80 static int32 sFunctionDepth = -1; 81 # define CALLED(x...) FunctionTracer _ft("BTextView", __FUNCTION__, \ 82 sFunctionDepth) 83 # define TRACE(x...) { BString _to; \ 84 _to.Append(' ', (sFunctionDepth + 1) * 2); \ 85 printf("%s", _to.String()); printf(x); } 86 #else 87 # define CALLED(x...) 88 # define TRACE(x...) 89 #endif 90 91 92 #define USE_WIDTHBUFFER 1 93 #define USE_DOUBLEBUFFERING 0 94 95 96 struct flattened_text_run { 97 int32 offset; 98 font_family family; 99 font_style style; 100 float size; 101 float shear; // typically 90.0 102 uint16 face; // typically 0 103 uint8 red; 104 uint8 green; 105 uint8 blue; 106 uint8 alpha; // 255 == opaque 107 uint16 _reserved_; // 0 108 }; 109 110 struct flattened_text_run_array { 111 uint32 magic; 112 uint32 version; 113 int32 count; 114 flattened_text_run styles[1]; 115 }; 116 117 static const uint32 kFlattenedTextRunArrayMagic = 'Ali!'; 118 static const uint32 kFlattenedTextRunArrayVersion = 0; 119 120 121 enum { 122 CHAR_CLASS_DEFAULT, 123 CHAR_CLASS_WHITESPACE, 124 CHAR_CLASS_GRAPHICAL, 125 CHAR_CLASS_QUOTE, 126 CHAR_CLASS_PUNCTUATION, 127 CHAR_CLASS_PARENS_OPEN, 128 CHAR_CLASS_PARENS_CLOSE, 129 CHAR_CLASS_END_OF_TEXT 130 }; 131 132 133 class BTextView::TextTrackState { 134 public: 135 TextTrackState(BMessenger messenger); 136 ~TextTrackState(); 137 138 void SimulateMouseMovement(BTextView* view); 139 140 public: 141 int32 clickOffset; 142 bool shiftDown; 143 BRect selectionRect; 144 BPoint where; 145 146 int32 anchor; 147 int32 selStart; 148 int32 selEnd; 149 150 private: 151 BMessageRunner* fRunner; 152 }; 153 154 155 struct BTextView::LayoutData { 156 LayoutData() 157 : leftInset(0), 158 topInset(0), 159 rightInset(0), 160 bottomInset(0), 161 valid(false), 162 overridden(false) 163 { 164 } 165 166 float leftInset; 167 float topInset; 168 float rightInset; 169 float bottomInset; 170 171 BSize min; 172 BSize preferred; 173 bool valid : 1; 174 bool overridden : 1; 175 }; 176 177 178 static const rgb_color kBlueInputColor = { 152, 203, 255, 255 }; 179 static const rgb_color kRedInputColor = { 255, 152, 152, 255 }; 180 181 static const float kHorizontalScrollBarStep = 10.0; 182 static const float kVerticalScrollBarStep = 12.0; 183 184 static const int32 kMsgNavigateArrow = '_NvA'; 185 static const int32 kMsgNavigatePage = '_NvP'; 186 static const int32 kMsgRemoveWord = '_RmW'; 187 188 189 static property_info sPropertyList[] = { 190 { 191 "selection", 192 { B_GET_PROPERTY, 0 }, 193 { B_DIRECT_SPECIFIER, 0 }, 194 "Returns the current selection.", 0, 195 { B_INT32_TYPE, 0 } 196 }, 197 { 198 "selection", 199 { B_SET_PROPERTY, 0 }, 200 { B_DIRECT_SPECIFIER, 0 }, 201 "Sets the current selection.", 0, 202 { B_INT32_TYPE, 0 } 203 }, 204 { 205 "Text", 206 { B_COUNT_PROPERTIES, 0 }, 207 { B_DIRECT_SPECIFIER, 0 }, 208 "Returns the length of the text in bytes.", 0, 209 { B_INT32_TYPE, 0 } 210 }, 211 { 212 "Text", 213 { B_GET_PROPERTY, 0 }, 214 { B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 }, 215 "Returns the text in the specified range in the BTextView.", 0, 216 { B_STRING_TYPE, 0 } 217 }, 218 { 219 "Text", 220 { B_SET_PROPERTY, 0 }, 221 { B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 }, 222 "Removes or inserts text into the specified range in the BTextView.", 0, 223 { B_STRING_TYPE, 0 } 224 }, 225 { 226 "text_run_array", 227 { B_GET_PROPERTY, 0 }, 228 { B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 }, 229 "Returns the style information for the text in the specified range in " 230 "the BTextView.", 0, 231 { B_RAW_TYPE, 0 }, 232 }, 233 { 234 "text_run_array", 235 { B_SET_PROPERTY, 0 }, 236 { B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 }, 237 "Sets the style information for the text in the specified range in the " 238 "BTextView.", 0, 239 { B_RAW_TYPE, 0 }, 240 }, 241 242 { 0 } 243 }; 244 245 246 BTextView::BTextView(BRect frame, const char* name, BRect textRect, 247 uint32 resizeMask, uint32 flags) 248 : 249 BView(frame, name, resizeMask, 250 flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE), 251 fText(NULL), 252 fLines(NULL), 253 fStyles(NULL), 254 fDisallowedChars(NULL), 255 fUndo(NULL), 256 fDragRunner(NULL), 257 fClickRunner(NULL), 258 fLayoutData(NULL) 259 { 260 _InitObject(textRect, NULL, NULL); 261 SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR); 262 } 263 264 265 BTextView::BTextView(BRect frame, const char* name, BRect textRect, 266 const BFont* initialFont, const rgb_color* initialColor, 267 uint32 resizeMask, uint32 flags) 268 : 269 BView(frame, name, resizeMask, 270 flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE), 271 fText(NULL), 272 fLines(NULL), 273 fStyles(NULL), 274 fDisallowedChars(NULL), 275 fUndo(NULL), 276 fDragRunner(NULL), 277 fClickRunner(NULL), 278 fLayoutData(NULL) 279 { 280 _InitObject(textRect, initialFont, initialColor); 281 SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR); 282 } 283 284 285 BTextView::BTextView(const char* name, uint32 flags) 286 : 287 BView(name, 288 flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE), 289 fText(NULL), 290 fLines(NULL), 291 fStyles(NULL), 292 fDisallowedChars(NULL), 293 fUndo(NULL), 294 fDragRunner(NULL), 295 fClickRunner(NULL), 296 fLayoutData(NULL) 297 { 298 _InitObject(Bounds(), NULL, NULL); 299 SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR); 300 } 301 302 303 BTextView::BTextView(const char* name, const BFont* initialFont, 304 const rgb_color* initialColor, uint32 flags) 305 : 306 BView(name, 307 flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE), 308 fText(NULL), 309 fLines(NULL), 310 fStyles(NULL), 311 fDisallowedChars(NULL), 312 fUndo(NULL), 313 fDragRunner(NULL), 314 fClickRunner(NULL), 315 fLayoutData(NULL) 316 { 317 _InitObject(Bounds(), initialFont, initialColor); 318 SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR); 319 } 320 321 322 BTextView::BTextView(BMessage* archive) 323 : 324 BView(archive), 325 fText(NULL), 326 fLines(NULL), 327 fStyles(NULL), 328 fDisallowedChars(NULL), 329 fUndo(NULL), 330 fDragRunner(NULL), 331 fClickRunner(NULL), 332 fLayoutData(NULL) 333 { 334 CALLED(); 335 BRect rect; 336 337 if (archive->FindRect("_trect", &rect) != B_OK) 338 rect.Set(0, 0, 0, 0); 339 340 _InitObject(rect, NULL, NULL); 341 342 bool toggle; 343 344 if (archive->FindBool("_password", &toggle) == B_OK) 345 HideTyping(toggle); 346 347 const char* text = NULL; 348 if (archive->FindString("_text", &text) == B_OK) 349 SetText(text); 350 351 int32 flag, flag2; 352 if (archive->FindInt32("_align", &flag) == B_OK) 353 SetAlignment((alignment)flag); 354 355 float value; 356 357 if (archive->FindFloat("_tab", &value) == B_OK) 358 SetTabWidth(value); 359 360 if (archive->FindInt32("_col_sp", &flag) == B_OK) 361 SetColorSpace((color_space)flag); 362 363 if (archive->FindInt32("_max", &flag) == B_OK) 364 SetMaxBytes(flag); 365 366 if (archive->FindInt32("_sel", &flag) == B_OK && 367 archive->FindInt32("_sel", &flag2) == B_OK) 368 Select(flag, flag2); 369 370 if (archive->FindBool("_stylable", &toggle) == B_OK) 371 SetStylable(toggle); 372 373 if (archive->FindBool("_auto_in", &toggle) == B_OK) 374 SetAutoindent(toggle); 375 376 if (archive->FindBool("_wrap", &toggle) == B_OK) 377 SetWordWrap(toggle); 378 379 if (archive->FindBool("_nsel", &toggle) == B_OK) 380 MakeSelectable(!toggle); 381 382 if (archive->FindBool("_nedit", &toggle) == B_OK) 383 MakeEditable(!toggle); 384 385 ssize_t disallowedCount = 0; 386 const int32* disallowedChars = NULL; 387 if (archive->FindData("_dis_ch", B_RAW_TYPE, 388 (const void**)&disallowedChars, &disallowedCount) == B_OK) { 389 390 fDisallowedChars = new BList; 391 disallowedCount /= sizeof(int32); 392 for (int32 x = 0; x < disallowedCount; x++) { 393 fDisallowedChars->AddItem( 394 reinterpret_cast<void*>(disallowedChars[x])); 395 } 396 } 397 398 ssize_t runSize = 0; 399 const void* flattenedRun = NULL; 400 401 if (archive->FindData("_runs", B_RAW_TYPE, &flattenedRun, &runSize) 402 == B_OK) { 403 text_run_array* runArray = UnflattenRunArray(flattenedRun, 404 (int32*)&runSize); 405 if (runArray) { 406 SetRunArray(0, fText->Length(), runArray); 407 FreeRunArray(runArray); 408 } 409 } 410 } 411 412 413 BTextView::~BTextView() 414 { 415 _CancelInputMethod(); 416 _StopMouseTracking(); 417 _DeleteOffscreen(); 418 419 delete fText; 420 delete fLines; 421 delete fStyles; 422 delete fDisallowedChars; 423 delete fUndo; 424 delete fDragRunner; 425 delete fClickRunner; 426 delete fLayoutData; 427 } 428 429 430 BArchivable* 431 BTextView::Instantiate(BMessage* archive) 432 { 433 CALLED(); 434 if (validate_instantiation(archive, "BTextView")) 435 return new BTextView(archive); 436 return NULL; 437 } 438 439 440 status_t 441 BTextView::Archive(BMessage* data, bool deep) const 442 { 443 CALLED(); 444 status_t err = BView::Archive(data, deep); 445 if (err == B_OK) 446 err = data->AddString("_text", Text()); 447 if (err == B_OK) 448 err = data->AddInt32("_align", fAlignment); 449 if (err == B_OK) 450 err = data->AddFloat("_tab", fTabWidth); 451 if (err == B_OK) 452 err = data->AddInt32("_col_sp", fColorSpace); 453 if (err == B_OK) 454 err = data->AddRect("_trect", fTextRect); 455 if (err == B_OK) 456 err = data->AddInt32("_max", fMaxBytes); 457 if (err == B_OK) 458 err = data->AddInt32("_sel", fSelStart); 459 if (err == B_OK) 460 err = data->AddInt32("_sel", fSelEnd); 461 if (err == B_OK) 462 err = data->AddBool("_stylable", fStylable); 463 if (err == B_OK) 464 err = data->AddBool("_auto_in", fAutoindent); 465 if (err == B_OK) 466 err = data->AddBool("_wrap", fWrap); 467 if (err == B_OK) 468 err = data->AddBool("_nsel", !fSelectable); 469 if (err == B_OK) 470 err = data->AddBool("_nedit", !fEditable); 471 if (err == B_OK) 472 err = data->AddBool("_password", IsTypingHidden()); 473 474 if (err == B_OK && fDisallowedChars != NULL && fDisallowedChars->CountItems() > 0) { 475 err = data->AddData("_dis_ch", B_RAW_TYPE, fDisallowedChars->Items(), 476 fDisallowedChars->CountItems() * sizeof(int32)); 477 } 478 479 if (err == B_OK) { 480 int32 runSize = 0; 481 text_run_array* runArray = RunArray(0, fText->Length()); 482 483 void* flattened = FlattenRunArray(runArray, &runSize); 484 if (flattened != NULL) { 485 data->AddData("_runs", B_RAW_TYPE, flattened, runSize); 486 free(flattened); 487 } else 488 err = B_NO_MEMORY; 489 490 FreeRunArray(runArray); 491 } 492 493 return err; 494 } 495 496 497 void 498 BTextView::AttachedToWindow() 499 { 500 BView::AttachedToWindow(); 501 502 SetDrawingMode(B_OP_COPY); 503 504 Window()->SetPulseRate(500000); 505 506 fCaretVisible = false; 507 fCaretTime = 0; 508 fClickCount = 0; 509 fClickTime = 0; 510 fDragOffset = -1; 511 fActive = false; 512 513 _ValidateTextRect(); 514 515 _AutoResize(true); 516 517 _UpdateScrollbars(); 518 519 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 520 } 521 522 523 void 524 BTextView::DetachedFromWindow() 525 { 526 BView::DetachedFromWindow(); 527 } 528 529 530 void 531 BTextView::Draw(BRect updateRect) 532 { 533 // what lines need to be drawn? 534 int32 startLine = _LineAt(BPoint(0.0, updateRect.top)); 535 int32 endLine = _LineAt(BPoint(0.0, updateRect.bottom)); 536 537 _DrawLines(startLine, endLine, -1, true); 538 } 539 540 541 void 542 BTextView::MouseDown(BPoint where) 543 { 544 // should we even bother? 545 if (!fEditable && !fSelectable) 546 return; 547 548 _CancelInputMethod(); 549 550 if (!IsFocus()) 551 MakeFocus(); 552 553 _HideCaret(); 554 555 _StopMouseTracking(); 556 557 int32 modifiers = 0; 558 uint32 buttons = 0; 559 BMessage* currentMessage = Window()->CurrentMessage(); 560 if (currentMessage != NULL) { 561 currentMessage->FindInt32("modifiers", &modifiers); 562 currentMessage->FindInt32("buttons", (int32*)&buttons); 563 } 564 565 if (buttons == B_SECONDARY_MOUSE_BUTTON) { 566 _ShowContextMenu(where); 567 return; 568 } 569 570 BMessenger messenger(this); 571 fTrackingMouse = new (nothrow) TextTrackState(messenger); 572 if (fTrackingMouse == NULL) 573 return; 574 575 fTrackingMouse->clickOffset = OffsetAt(where); 576 fTrackingMouse->shiftDown = modifiers & B_SHIFT_KEY; 577 fTrackingMouse->where = where; 578 579 bigtime_t clickTime = system_time(); 580 bigtime_t clickSpeed = 0; 581 get_click_speed(&clickSpeed); 582 bool multipleClick 583 = clickTime - fClickTime < clickSpeed 584 && fLastClickOffset == fTrackingMouse->clickOffset; 585 586 fWhere = where; 587 588 SetMouseEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS, 589 B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY); 590 591 if (fSelStart != fSelEnd && !fTrackingMouse->shiftDown && !multipleClick) { 592 BRegion region; 593 GetTextRegion(fSelStart, fSelEnd, ®ion); 594 if (region.Contains(where)) { 595 // Setup things for dragging 596 fTrackingMouse->selectionRect = region.Frame(); 597 fClickCount = 1; 598 fClickTime = clickTime; 599 fLastClickOffset = OffsetAt(where); 600 return; 601 } 602 } 603 604 if (multipleClick) { 605 if (fClickCount > 3) { 606 fClickCount = 0; 607 fClickTime = 0; 608 } else { 609 fClickCount++; 610 fClickTime = clickTime; 611 } 612 } else if (!fTrackingMouse->shiftDown) { 613 // If no multiple click yet and shift is not pressed, this is an 614 // independent first click somewhere into the textview - we initialize 615 // the corresponding members for handling potential multiple clicks: 616 fLastClickOffset = fCaretOffset = fTrackingMouse->clickOffset; 617 fClickCount = 1; 618 fClickTime = clickTime; 619 620 // Deselect any previously selected text 621 Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset); 622 } 623 624 if (fClickTime == clickTime) { 625 BMessage message(_PING_); 626 message.AddInt64("clickTime", clickTime); 627 delete fClickRunner; 628 629 BMessenger messenger(this); 630 fClickRunner = new (nothrow) BMessageRunner(messenger, &message, 631 clickSpeed, 1); 632 } 633 634 if (!fSelectable) { 635 _StopMouseTracking(); 636 return; 637 } 638 639 int32 offset = fSelStart; 640 if (fTrackingMouse->clickOffset > fSelStart) 641 offset = fSelEnd; 642 643 fTrackingMouse->anchor = offset; 644 645 MouseMoved(where, B_INSIDE_VIEW, NULL); 646 } 647 648 649 void 650 BTextView::MouseUp(BPoint where) 651 { 652 BView::MouseUp(where); 653 _PerformMouseUp(where); 654 655 delete fDragRunner; 656 fDragRunner = NULL; 657 } 658 659 660 void 661 BTextView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage) 662 { 663 // check if it's a "click'n'move" 664 if (_PerformMouseMoved(where, code)) 665 return; 666 667 switch (code) { 668 case B_ENTERED_VIEW: 669 case B_INSIDE_VIEW: 670 _TrackMouse(where, dragMessage, true); 671 break; 672 673 case B_EXITED_VIEW: 674 _DragCaret(-1); 675 if (Window()->IsActive() && dragMessage == NULL) 676 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 677 break; 678 679 default: 680 BView::MouseMoved(where, code, dragMessage); 681 } 682 } 683 684 685 void 686 BTextView::WindowActivated(bool active) 687 { 688 BView::WindowActivated(active); 689 690 if (active && IsFocus()) { 691 if (!fActive) 692 _Activate(); 693 } else { 694 if (fActive) 695 _Deactivate(); 696 } 697 698 BPoint where; 699 uint32 buttons; 700 GetMouse(&where, &buttons, false); 701 702 if (Bounds().Contains(where)) 703 _TrackMouse(where, NULL); 704 } 705 706 707 void 708 BTextView::KeyDown(const char* bytes, int32 numBytes) 709 { 710 const char keyPressed = bytes[0]; 711 712 if (!fEditable) { 713 // only arrow and page keys are allowed 714 // (no need to hide the cursor) 715 switch (keyPressed) { 716 case B_LEFT_ARROW: 717 case B_RIGHT_ARROW: 718 case B_UP_ARROW: 719 case B_DOWN_ARROW: 720 _HandleArrowKey(keyPressed); 721 break; 722 723 case B_HOME: 724 case B_END: 725 case B_PAGE_UP: 726 case B_PAGE_DOWN: 727 _HandlePageKey(keyPressed); 728 break; 729 730 default: 731 BView::KeyDown(bytes, numBytes); 732 break; 733 } 734 735 return; 736 } 737 738 // hide the cursor and caret 739 if (IsFocus()) 740 be_app->ObscureCursor(); 741 _HideCaret(); 742 743 switch (keyPressed) { 744 case B_BACKSPACE: 745 _HandleBackspace(); 746 break; 747 748 case B_LEFT_ARROW: 749 case B_RIGHT_ARROW: 750 case B_UP_ARROW: 751 case B_DOWN_ARROW: 752 _HandleArrowKey(keyPressed); 753 break; 754 755 case B_DELETE: 756 _HandleDelete(); 757 break; 758 759 case B_HOME: 760 case B_END: 761 case B_PAGE_UP: 762 case B_PAGE_DOWN: 763 _HandlePageKey(keyPressed); 764 break; 765 766 case B_ESCAPE: 767 case B_INSERT: 768 case B_FUNCTION_KEY: 769 // ignore, pass it up to superclass 770 BView::KeyDown(bytes, numBytes); 771 break; 772 773 default: 774 // bail out if the character is not allowed 775 if (fDisallowedChars 776 && fDisallowedChars->HasItem( 777 reinterpret_cast<void*>((uint32)keyPressed))) { 778 beep(); 779 return; 780 } 781 782 _HandleAlphaKey(bytes, numBytes); 783 break; 784 } 785 786 // draw the caret 787 if (fSelStart == fSelEnd) 788 _ShowCaret(); 789 } 790 791 792 void 793 BTextView::Pulse() 794 { 795 if (fActive && (fEditable || fSelectable) && fSelStart == fSelEnd) { 796 if (system_time() > (fCaretTime + 500000.0)) 797 _InvertCaret(); 798 } 799 } 800 801 802 void 803 BTextView::FrameResized(float newWidth, float newHeight) 804 { 805 BView::FrameResized(newWidth, newHeight); 806 807 // frame resized in _AutoResize() instead 808 if (fResizable) 809 return; 810 811 if (fWrap) { 812 // recalculate line breaks 813 // will update scroll bars if text rect changes 814 _ResetTextRect(); 815 } else { 816 // don't recalculate line breaks, 817 // move text rect into position and redraw. 818 819 float dataWidth = _TextWidth(); 820 newWidth = std::max(dataWidth, newWidth); 821 822 // align rect 823 BRect rect(fLayoutData->leftInset, fLayoutData->topInset, 824 newWidth - fLayoutData->rightInset, 825 newHeight - fLayoutData->bottomInset); 826 827 rect = BLayoutUtils::AlignOnRect(rect, 828 BSize(fTextRect.Width(), fTextRect.Height()), 829 BAlignment(fAlignment, B_ALIGN_TOP)); 830 fTextRect.OffsetTo(rect.left, rect.top); 831 832 // must invalidate whole thing because of highlighting 833 Invalidate(); 834 _UpdateScrollbars(); 835 } 836 } 837 838 839 void 840 BTextView::MakeFocus(bool focus) 841 { 842 BView::MakeFocus(focus); 843 844 if (focus && Window() != NULL && Window()->IsActive()) { 845 if (!fActive) 846 _Activate(); 847 } else { 848 if (fActive) 849 _Deactivate(); 850 } 851 } 852 853 854 void 855 BTextView::MessageReceived(BMessage* message) 856 { 857 // ToDo: block input if not editable (Andrew) 858 859 // was this message dropped? 860 if (message->WasDropped()) { 861 BPoint dropOffset; 862 BPoint dropPoint = message->DropPoint(&dropOffset); 863 ConvertFromScreen(&dropPoint); 864 ConvertFromScreen(&dropOffset); 865 if (!_MessageDropped(message, dropPoint, dropOffset)) 866 BView::MessageReceived(message); 867 868 return; 869 } 870 871 switch (message->what) { 872 case B_CUT: 873 if (!IsTypingHidden()) 874 Cut(be_clipboard); 875 else 876 beep(); 877 break; 878 879 case B_COPY: 880 if (!IsTypingHidden()) 881 Copy(be_clipboard); 882 else 883 beep(); 884 break; 885 886 case B_PASTE: 887 Paste(be_clipboard); 888 break; 889 890 case B_UNDO: 891 Undo(be_clipboard); 892 break; 893 894 case B_SELECT_ALL: 895 SelectAll(); 896 break; 897 898 case B_INPUT_METHOD_EVENT: 899 { 900 int32 opcode; 901 if (message->FindInt32("be:opcode", &opcode) == B_OK) { 902 switch (opcode) { 903 case B_INPUT_METHOD_STARTED: 904 { 905 BMessenger messenger; 906 if (message->FindMessenger("be:reply_to", &messenger) 907 == B_OK) { 908 ASSERT(fInline == NULL); 909 fInline = new InlineInput(messenger); 910 } 911 break; 912 } 913 914 case B_INPUT_METHOD_STOPPED: 915 delete fInline; 916 fInline = NULL; 917 break; 918 919 case B_INPUT_METHOD_CHANGED: 920 if (fInline != NULL) 921 _HandleInputMethodChanged(message); 922 break; 923 924 case B_INPUT_METHOD_LOCATION_REQUEST: 925 if (fInline != NULL) 926 _HandleInputMethodLocationRequest(); 927 break; 928 929 default: 930 break; 931 } 932 } 933 break; 934 } 935 936 case B_SET_PROPERTY: 937 case B_GET_PROPERTY: 938 case B_COUNT_PROPERTIES: 939 { 940 BPropertyInfo propInfo(sPropertyList); 941 BMessage specifier; 942 const char* property; 943 944 if (message->GetCurrentSpecifier(NULL, &specifier) < B_OK 945 || specifier.FindString("property", &property) < B_OK) { 946 BView::MessageReceived(message); 947 return; 948 } 949 950 if (propInfo.FindMatch(message, 0, &specifier, specifier.what, 951 property) < B_OK) { 952 BView::MessageReceived(message); 953 break; 954 } 955 956 BMessage reply; 957 bool handled = false; 958 switch(message->what) { 959 case B_GET_PROPERTY: 960 handled = _GetProperty(message, &specifier, property, 961 &reply); 962 break; 963 964 case B_SET_PROPERTY: 965 handled = _SetProperty(message, &specifier, property, 966 &reply); 967 break; 968 969 case B_COUNT_PROPERTIES: 970 handled = _CountProperties(message, &specifier, 971 property, &reply); 972 break; 973 974 default: 975 break; 976 } 977 if (handled) 978 message->SendReply(&reply); 979 else 980 BView::MessageReceived(message); 981 break; 982 } 983 984 case _PING_: 985 { 986 if (message->HasInt64("clickTime")) { 987 bigtime_t clickTime; 988 message->FindInt64("clickTime", &clickTime); 989 if (clickTime == fClickTime) { 990 if (fSelStart != fSelEnd && fSelectable) { 991 BRegion region; 992 GetTextRegion(fSelStart, fSelEnd, ®ion); 993 if (region.Contains(fWhere)) 994 _TrackMouse(fWhere, NULL); 995 } 996 delete fClickRunner; 997 fClickRunner = NULL; 998 } 999 } else if (fTrackingMouse) { 1000 fTrackingMouse->SimulateMouseMovement(this); 1001 _PerformAutoScrolling(); 1002 } 1003 break; 1004 } 1005 1006 case _DISPOSE_DRAG_: 1007 if (fEditable) 1008 _TrackDrag(fWhere); 1009 break; 1010 1011 case kMsgNavigateArrow: 1012 { 1013 int32 key = message->GetInt32("key", 0); 1014 int32 modifiers = message->GetInt32("modifiers", 0); 1015 _HandleArrowKey(key, modifiers); 1016 break; 1017 } 1018 1019 case kMsgNavigatePage: 1020 { 1021 int32 key = message->GetInt32("key", 0); 1022 int32 modifiers = message->GetInt32("modifiers", 0); 1023 _HandlePageKey(key, modifiers); 1024 break; 1025 } 1026 1027 case kMsgRemoveWord: 1028 { 1029 int32 key = message->GetInt32("key", 0); 1030 int32 modifiers = message->GetInt32("modifiers", 0); 1031 if (key == B_DELETE) 1032 _HandleDelete(modifiers); 1033 else if (key == B_BACKSPACE) 1034 _HandleBackspace(modifiers); 1035 break; 1036 } 1037 1038 default: 1039 BView::MessageReceived(message); 1040 break; 1041 } 1042 } 1043 1044 1045 BHandler* 1046 BTextView::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier, 1047 int32 what, const char* property) 1048 { 1049 BPropertyInfo propInfo(sPropertyList); 1050 BHandler* target = this; 1051 1052 if (propInfo.FindMatch(message, index, specifier, what, property) < B_OK) { 1053 target = BView::ResolveSpecifier(message, index, specifier, what, 1054 property); 1055 } 1056 1057 return target; 1058 } 1059 1060 1061 status_t 1062 BTextView::GetSupportedSuites(BMessage* data) 1063 { 1064 if (data == NULL) 1065 return B_BAD_VALUE; 1066 1067 status_t err = data->AddString("suites", "suite/vnd.Be-text-view"); 1068 if (err != B_OK) 1069 return err; 1070 1071 BPropertyInfo prop_info(sPropertyList); 1072 err = data->AddFlat("messages", &prop_info); 1073 1074 if (err != B_OK) 1075 return err; 1076 return BView::GetSupportedSuites(data); 1077 } 1078 1079 1080 status_t 1081 BTextView::Perform(perform_code code, void* _data) 1082 { 1083 switch (code) { 1084 case PERFORM_CODE_MIN_SIZE: 1085 ((perform_data_min_size*)_data)->return_value 1086 = BTextView::MinSize(); 1087 return B_OK; 1088 case PERFORM_CODE_MAX_SIZE: 1089 ((perform_data_max_size*)_data)->return_value 1090 = BTextView::MaxSize(); 1091 return B_OK; 1092 case PERFORM_CODE_PREFERRED_SIZE: 1093 ((perform_data_preferred_size*)_data)->return_value 1094 = BTextView::PreferredSize(); 1095 return B_OK; 1096 case PERFORM_CODE_LAYOUT_ALIGNMENT: 1097 ((perform_data_layout_alignment*)_data)->return_value 1098 = BTextView::LayoutAlignment(); 1099 return B_OK; 1100 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 1101 ((perform_data_has_height_for_width*)_data)->return_value 1102 = BTextView::HasHeightForWidth(); 1103 return B_OK; 1104 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 1105 { 1106 perform_data_get_height_for_width* data 1107 = (perform_data_get_height_for_width*)_data; 1108 BTextView::GetHeightForWidth(data->width, &data->min, &data->max, 1109 &data->preferred); 1110 return B_OK; 1111 } 1112 case PERFORM_CODE_SET_LAYOUT: 1113 { 1114 perform_data_set_layout* data = (perform_data_set_layout*)_data; 1115 BTextView::SetLayout(data->layout); 1116 return B_OK; 1117 } 1118 case PERFORM_CODE_LAYOUT_INVALIDATED: 1119 { 1120 perform_data_layout_invalidated* data 1121 = (perform_data_layout_invalidated*)_data; 1122 BTextView::LayoutInvalidated(data->descendants); 1123 return B_OK; 1124 } 1125 case PERFORM_CODE_DO_LAYOUT: 1126 { 1127 BTextView::DoLayout(); 1128 return B_OK; 1129 } 1130 } 1131 1132 return BView::Perform(code, _data); 1133 } 1134 1135 1136 void 1137 BTextView::SetText(const char* text, const text_run_array* runs) 1138 { 1139 SetText(text, text ? strlen(text) : 0, runs); 1140 } 1141 1142 1143 void 1144 BTextView::SetText(const char* text, int32 length, const text_run_array* runs) 1145 { 1146 _CancelInputMethod(); 1147 1148 // hide the caret/unhighlight the selection 1149 if (fActive) { 1150 if (fSelStart != fSelEnd) { 1151 if (fSelectable) 1152 Highlight(fSelStart, fSelEnd); 1153 } else 1154 _HideCaret(); 1155 } 1156 1157 // remove data from buffer 1158 if (fText->Length() > 0) 1159 DeleteText(0, fText->Length()); 1160 1161 if (text != NULL && length > 0) 1162 InsertText(text, length, 0, runs); 1163 1164 // bounds are invalid, set them based on text 1165 if (!Bounds().IsValid()) { 1166 ResizeTo(LineWidth(0) - 1, LineHeight(0)); 1167 fTextRect = Bounds(); 1168 _ValidateTextRect(); 1169 _UpdateInsets(fTextRect); 1170 } 1171 1172 // recalculate line breaks and draw the text 1173 _Refresh(0, length); 1174 fCaretOffset = fSelStart = fSelEnd = 0; 1175 1176 // draw the caret 1177 _ShowCaret(); 1178 } 1179 1180 1181 void 1182 BTextView::SetText(BFile* file, int32 offset, int32 length, 1183 const text_run_array* runs) 1184 { 1185 CALLED(); 1186 1187 _CancelInputMethod(); 1188 1189 if (file == NULL) 1190 return; 1191 1192 if (fText->Length() > 0) 1193 DeleteText(0, fText->Length()); 1194 1195 if (!fText->InsertText(file, offset, length, 0)) 1196 return; 1197 1198 // update the start offsets of each line below offset 1199 fLines->BumpOffset(length, _LineAt(offset) + 1); 1200 1201 // update the style runs 1202 fStyles->BumpOffset(length, fStyles->OffsetToRun(offset - 1) + 1); 1203 1204 if (fStylable && runs != NULL) 1205 SetRunArray(offset, offset + length, runs); 1206 else { 1207 // apply null-style to inserted text 1208 _ApplyStyleRange(offset, offset + length); 1209 } 1210 1211 // recalculate line breaks and draw the text 1212 _Refresh(0, length); 1213 fCaretOffset = fSelStart = fSelEnd = 0; 1214 ScrollToOffset(fSelStart); 1215 1216 // draw the caret 1217 _ShowCaret(); 1218 } 1219 1220 1221 void 1222 BTextView::Insert(const char* text, const text_run_array* runs) 1223 { 1224 if (text != NULL) 1225 _DoInsertText(text, strlen(text), fSelStart, runs); 1226 } 1227 1228 1229 void 1230 BTextView::Insert(const char* text, int32 length, const text_run_array* runs) 1231 { 1232 if (text != NULL && length > 0) 1233 _DoInsertText(text, strnlen(text, length), fSelStart, runs); 1234 } 1235 1236 1237 void 1238 BTextView::Insert(int32 offset, const char* text, int32 length, 1239 const text_run_array* runs) 1240 { 1241 // pin offset at reasonable values 1242 if (offset < 0) 1243 offset = 0; 1244 else if (offset > fText->Length()) 1245 offset = fText->Length(); 1246 1247 if (text != NULL && length > 0) 1248 _DoInsertText(text, strnlen(text, length), offset, runs); 1249 } 1250 1251 1252 void 1253 BTextView::Delete() 1254 { 1255 Delete(fSelStart, fSelEnd); 1256 } 1257 1258 1259 void 1260 BTextView::Delete(int32 startOffset, int32 endOffset) 1261 { 1262 CALLED(); 1263 1264 // pin offsets at reasonable values 1265 if (startOffset < 0) 1266 startOffset = 0; 1267 else if (startOffset > fText->Length()) 1268 startOffset = fText->Length(); 1269 1270 if (endOffset < 0) 1271 endOffset = 0; 1272 else if (endOffset > fText->Length()) 1273 endOffset = fText->Length(); 1274 1275 // anything to delete? 1276 if (startOffset == endOffset) 1277 return; 1278 1279 // hide the caret/unhighlight the selection 1280 if (fActive) { 1281 if (fSelStart != fSelEnd) { 1282 if (fSelectable) 1283 Highlight(fSelStart, fSelEnd); 1284 } else 1285 _HideCaret(); 1286 } 1287 // remove data from buffer 1288 DeleteText(startOffset, endOffset); 1289 1290 // check if the caret needs to be moved 1291 if (fCaretOffset >= endOffset) 1292 fCaretOffset -= (endOffset - startOffset); 1293 else if (fCaretOffset >= startOffset && fCaretOffset < endOffset) 1294 fCaretOffset = startOffset; 1295 1296 fSelEnd = fSelStart = fCaretOffset; 1297 1298 // recalculate line breaks and draw what's left 1299 _Refresh(startOffset, endOffset, fCaretOffset); 1300 1301 // draw the caret 1302 _ShowCaret(); 1303 } 1304 1305 1306 const char* 1307 BTextView::Text() const 1308 { 1309 return fText->RealText(); 1310 } 1311 1312 1313 int32 1314 BTextView::TextLength() const 1315 { 1316 return fText->Length(); 1317 } 1318 1319 1320 void 1321 BTextView::GetText(int32 offset, int32 length, char* buffer) const 1322 { 1323 if (buffer != NULL) 1324 fText->GetString(offset, length, buffer); 1325 } 1326 1327 1328 uchar 1329 BTextView::ByteAt(int32 offset) const 1330 { 1331 if (offset < 0 || offset >= fText->Length()) 1332 return '\0'; 1333 1334 return fText->RealCharAt(offset); 1335 } 1336 1337 1338 int32 1339 BTextView::CountLines() const 1340 { 1341 return fLines->NumLines(); 1342 } 1343 1344 1345 int32 1346 BTextView::CurrentLine() const 1347 { 1348 return LineAt(fSelStart); 1349 } 1350 1351 1352 void 1353 BTextView::GoToLine(int32 index) 1354 { 1355 _CancelInputMethod(); 1356 _HideCaret(); 1357 fSelStart = fSelEnd = fCaretOffset = OffsetAt(index); 1358 _ShowCaret(); 1359 } 1360 1361 1362 void 1363 BTextView::Cut(BClipboard* clipboard) 1364 { 1365 _CancelInputMethod(); 1366 if (!fEditable) 1367 return; 1368 if (fUndo) { 1369 delete fUndo; 1370 fUndo = new CutUndoBuffer(this); 1371 } 1372 Copy(clipboard); 1373 Delete(); 1374 } 1375 1376 1377 void 1378 BTextView::Copy(BClipboard* clipboard) 1379 { 1380 _CancelInputMethod(); 1381 1382 if (clipboard->Lock()) { 1383 clipboard->Clear(); 1384 1385 BMessage* clip = clipboard->Data(); 1386 if (clip != NULL) { 1387 int32 numBytes = fSelEnd - fSelStart; 1388 const char* text = fText->GetString(fSelStart, &numBytes); 1389 clip->AddData("text/plain", B_MIME_TYPE, text, numBytes); 1390 1391 int32 size; 1392 if (fStylable) { 1393 text_run_array* runArray = RunArray(fSelStart, fSelEnd, &size); 1394 clip->AddData("application/x-vnd.Be-text_run_array", 1395 B_MIME_TYPE, runArray, size); 1396 FreeRunArray(runArray); 1397 } 1398 clipboard->Commit(); 1399 } 1400 clipboard->Unlock(); 1401 } 1402 } 1403 1404 1405 void 1406 BTextView::Paste(BClipboard* clipboard) 1407 { 1408 CALLED(); 1409 _CancelInputMethod(); 1410 1411 if (!fEditable || !clipboard->Lock()) 1412 return; 1413 1414 BMessage* clip = clipboard->Data(); 1415 if (clip != NULL) { 1416 const char* text = NULL; 1417 ssize_t length = 0; 1418 1419 if (clip->FindData("text/plain", B_MIME_TYPE, 1420 (const void**)&text, &length) == B_OK) { 1421 text_run_array* runArray = NULL; 1422 ssize_t runLength = 0; 1423 1424 if (fStylable) { 1425 clip->FindData("application/x-vnd.Be-text_run_array", 1426 B_MIME_TYPE, (const void**)&runArray, &runLength); 1427 } 1428 1429 _FilterDisallowedChars((char*)text, length, runArray); 1430 1431 if (length < 1) { 1432 beep(); 1433 clipboard->Unlock(); 1434 return; 1435 } 1436 1437 if (fUndo) { 1438 delete fUndo; 1439 fUndo = new PasteUndoBuffer(this, text, length, runArray, 1440 runLength); 1441 } 1442 1443 if (fSelStart != fSelEnd) 1444 Delete(); 1445 1446 Insert(text, length, runArray); 1447 ScrollToOffset(fSelEnd); 1448 } 1449 } 1450 1451 clipboard->Unlock(); 1452 } 1453 1454 1455 void 1456 BTextView::Clear() 1457 { 1458 // We always check for fUndo != NULL (not only here), 1459 // because when fUndo is NULL, undo is deactivated. 1460 if (fUndo) { 1461 delete fUndo; 1462 fUndo = new ClearUndoBuffer(this); 1463 } 1464 1465 Delete(); 1466 } 1467 1468 1469 bool 1470 BTextView::AcceptsPaste(BClipboard* clipboard) 1471 { 1472 bool result = false; 1473 1474 if (fEditable && clipboard && clipboard->Lock()) { 1475 BMessage* data = clipboard->Data(); 1476 result = data && data->HasData("text/plain", B_MIME_TYPE); 1477 clipboard->Unlock(); 1478 } 1479 1480 return result; 1481 } 1482 1483 1484 bool 1485 BTextView::AcceptsDrop(const BMessage* message) 1486 { 1487 return fEditable && message 1488 && message->HasData("text/plain", B_MIME_TYPE); 1489 } 1490 1491 1492 void 1493 BTextView::Select(int32 startOffset, int32 endOffset) 1494 { 1495 CALLED(); 1496 if (!fSelectable) 1497 return; 1498 1499 _CancelInputMethod(); 1500 1501 // pin offsets at reasonable values 1502 if (startOffset < 0) 1503 startOffset = 0; 1504 else if (startOffset > fText->Length()) 1505 startOffset = fText->Length(); 1506 if (endOffset < 0) 1507 endOffset = 0; 1508 else if (endOffset > fText->Length()) 1509 endOffset = fText->Length(); 1510 1511 // a negative selection? 1512 if (startOffset > endOffset) 1513 return; 1514 1515 // is the new selection any different from the current selection? 1516 if (startOffset == fSelStart && endOffset == fSelEnd) 1517 return; 1518 1519 fStyles->InvalidateNullStyle(); 1520 1521 _HideCaret(); 1522 1523 if (startOffset == endOffset) { 1524 if (fSelStart != fSelEnd) { 1525 // unhilite the selection 1526 if (fActive) 1527 Highlight(fSelStart, fSelEnd); 1528 } 1529 fSelStart = fSelEnd = fCaretOffset = startOffset; 1530 _ShowCaret(); 1531 } else { 1532 if (fActive) { 1533 // draw only those ranges that are different 1534 long start, end; 1535 if (startOffset != fSelStart) { 1536 // start of selection has changed 1537 if (startOffset > fSelStart) { 1538 start = fSelStart; 1539 end = startOffset; 1540 } else { 1541 start = startOffset; 1542 end = fSelStart; 1543 } 1544 Highlight(start, end); 1545 } 1546 1547 if (endOffset != fSelEnd) { 1548 // end of selection has changed 1549 if (endOffset > fSelEnd) { 1550 start = fSelEnd; 1551 end = endOffset; 1552 } else { 1553 start = endOffset; 1554 end = fSelEnd; 1555 } 1556 Highlight(start, end); 1557 } 1558 } 1559 fSelStart = startOffset; 1560 fSelEnd = endOffset; 1561 } 1562 } 1563 1564 1565 void 1566 BTextView::SelectAll() 1567 { 1568 Select(0, fText->Length()); 1569 } 1570 1571 1572 void 1573 BTextView::GetSelection(int32* _start, int32* _end) const 1574 { 1575 int32 start = 0; 1576 int32 end = 0; 1577 1578 if (fSelectable) { 1579 start = fSelStart; 1580 end = fSelEnd; 1581 } 1582 1583 if (_start) 1584 *_start = start; 1585 1586 if (_end) 1587 *_end = end; 1588 } 1589 1590 1591 void 1592 BTextView::SetFontAndColor(const BFont* font, uint32 mode, 1593 const rgb_color* color) 1594 { 1595 SetFontAndColor(fSelStart, fSelEnd, font, mode, color); 1596 } 1597 1598 1599 void 1600 BTextView::SetFontAndColor(int32 startOffset, int32 endOffset, 1601 const BFont* font, uint32 mode, const rgb_color* color) 1602 { 1603 CALLED(); 1604 1605 _HideCaret(); 1606 1607 const int32 textLength = fText->Length(); 1608 1609 if (!fStylable) { 1610 // When the text view is not stylable, we always set the whole text's 1611 // style and ignore the offsets 1612 startOffset = 0; 1613 endOffset = textLength; 1614 } else { 1615 // pin offsets at reasonable values 1616 if (startOffset < 0) 1617 startOffset = 0; 1618 else if (startOffset > textLength) 1619 startOffset = textLength; 1620 1621 if (endOffset < 0) 1622 endOffset = 0; 1623 else if (endOffset > textLength) 1624 endOffset = textLength; 1625 } 1626 1627 // apply the style to the style buffer 1628 fStyles->InvalidateNullStyle(); 1629 _ApplyStyleRange(startOffset, endOffset, mode, font, color); 1630 1631 if ((mode & (B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE)) != 0) { 1632 // ToDo: maybe only invalidate the layout (depending on 1633 // B_SUPPORTS_LAYOUT) and have it _Refresh() automatically? 1634 InvalidateLayout(); 1635 // recalc the line breaks and redraw with new style 1636 _Refresh(startOffset, endOffset); 1637 } else { 1638 // the line breaks wont change, simply redraw 1639 _RequestDrawLines(_LineAt(startOffset), _LineAt(endOffset)); 1640 } 1641 1642 _ShowCaret(); 1643 } 1644 1645 1646 void 1647 BTextView::GetFontAndColor(int32 offset, BFont* _font, 1648 rgb_color* _color) const 1649 { 1650 fStyles->GetStyle(offset, _font, _color); 1651 } 1652 1653 1654 void 1655 BTextView::GetFontAndColor(BFont* _font, uint32* _mode, 1656 rgb_color* _color, bool* _sameColor) const 1657 { 1658 fStyles->ContinuousGetStyle(_font, _mode, _color, _sameColor, 1659 fSelStart, fSelEnd); 1660 } 1661 1662 1663 void 1664 BTextView::SetRunArray(int32 startOffset, int32 endOffset, 1665 const text_run_array* runs) 1666 { 1667 CALLED(); 1668 1669 _CancelInputMethod(); 1670 1671 text_run_array oneRun; 1672 1673 if (!fStylable) { 1674 // when the text view is not stylable, we always set the whole text's 1675 // style with the first run and ignore the offsets 1676 if (runs->count == 0) 1677 return; 1678 1679 startOffset = 0; 1680 endOffset = fText->Length(); 1681 oneRun.count = 1; 1682 oneRun.runs[0] = runs->runs[0]; 1683 oneRun.runs[0].offset = 0; 1684 runs = &oneRun; 1685 } else { 1686 // pin offsets at reasonable values 1687 if (startOffset < 0) 1688 startOffset = 0; 1689 else if (startOffset > fText->Length()) 1690 startOffset = fText->Length(); 1691 1692 if (endOffset < 0) 1693 endOffset = 0; 1694 else if (endOffset > fText->Length()) 1695 endOffset = fText->Length(); 1696 } 1697 1698 _SetRunArray(startOffset, endOffset, runs); 1699 1700 _Refresh(startOffset, endOffset); 1701 } 1702 1703 1704 text_run_array* 1705 BTextView::RunArray(int32 startOffset, int32 endOffset, int32* _size) const 1706 { 1707 // pin offsets at reasonable values 1708 if (startOffset < 0) 1709 startOffset = 0; 1710 else if (startOffset > fText->Length()) 1711 startOffset = fText->Length(); 1712 1713 if (endOffset < 0) 1714 endOffset = 0; 1715 else if (endOffset > fText->Length()) 1716 endOffset = fText->Length(); 1717 1718 STEStyleRange* styleRange 1719 = fStyles->GetStyleRange(startOffset, endOffset - 1); 1720 if (styleRange == NULL) 1721 return NULL; 1722 1723 text_run_array* runArray = AllocRunArray(styleRange->count, _size); 1724 if (runArray != NULL) { 1725 for (int32 i = 0; i < runArray->count; i++) { 1726 runArray->runs[i].offset = styleRange->runs[i].offset; 1727 runArray->runs[i].font = styleRange->runs[i].style.font; 1728 runArray->runs[i].color = styleRange->runs[i].style.color; 1729 } 1730 } 1731 1732 free(styleRange); 1733 1734 return runArray; 1735 } 1736 1737 1738 int32 1739 BTextView::LineAt(int32 offset) const 1740 { 1741 // pin offset at reasonable values 1742 if (offset < 0) 1743 offset = 0; 1744 else if (offset > fText->Length()) 1745 offset = fText->Length(); 1746 1747 int32 lineNum = _LineAt(offset); 1748 if (_IsOnEmptyLastLine(offset)) 1749 lineNum++; 1750 return lineNum; 1751 } 1752 1753 1754 int32 1755 BTextView::LineAt(BPoint point) const 1756 { 1757 int32 lineNum = _LineAt(point); 1758 if ((*fLines)[lineNum + 1]->origin <= point.y - fTextRect.top) 1759 lineNum++; 1760 1761 return lineNum; 1762 } 1763 1764 1765 BPoint 1766 BTextView::PointAt(int32 offset, float* _height) const 1767 { 1768 // pin offset at reasonable values 1769 if (offset < 0) 1770 offset = 0; 1771 else if (offset > fText->Length()) 1772 offset = fText->Length(); 1773 1774 // ToDo: Cleanup. 1775 int32 lineNum = _LineAt(offset); 1776 STELine* line = (*fLines)[lineNum]; 1777 float height = 0; 1778 1779 BPoint result; 1780 result.x = 0.0; 1781 result.y = line->origin + fTextRect.top; 1782 1783 bool onEmptyLastLine = _IsOnEmptyLastLine(offset); 1784 1785 if (fStyles->NumRuns() == 0) { 1786 // Handle the case where there is only one line (no text inserted) 1787 fStyles->SyncNullStyle(0); 1788 height = _NullStyleHeight(); 1789 } else { 1790 height = (line + 1)->origin - line->origin; 1791 1792 if (onEmptyLastLine) { 1793 // special case: go down one line if offset is at the newline 1794 // at the end of the buffer ... 1795 result.y += height; 1796 // ... and return the height of that (empty) line 1797 fStyles->SyncNullStyle(offset); 1798 height = _NullStyleHeight(); 1799 } else { 1800 int32 length = offset - line->offset; 1801 result.x += _TabExpandedStyledWidth(line->offset, length); 1802 } 1803 } 1804 1805 if (fAlignment != B_ALIGN_LEFT) { 1806 float lineWidth = onEmptyLastLine ? 0.0 : LineWidth(lineNum); 1807 float alignmentOffset = fTextRect.Width() + 1 - lineWidth; 1808 if (fAlignment == B_ALIGN_CENTER) 1809 alignmentOffset = floorf(alignmentOffset / 2); 1810 result.x += alignmentOffset; 1811 } 1812 1813 // convert from text rect coordinates 1814 result.x += fTextRect.left; 1815 1816 // round up 1817 result.x = lroundf(result.x); 1818 result.y = lroundf(result.y); 1819 if (_height != NULL) 1820 *_height = height; 1821 1822 return result; 1823 } 1824 1825 1826 int32 1827 BTextView::OffsetAt(BPoint point) const 1828 { 1829 const int32 textLength = fText->Length(); 1830 1831 // should we even bother? 1832 if (point.y >= fTextRect.bottom) 1833 return textLength; 1834 else if (point.y < fTextRect.top) 1835 return 0; 1836 1837 int32 lineNum = _LineAt(point); 1838 STELine* line = (*fLines)[lineNum]; 1839 1840 #define COMPILE_PROBABLY_BAD_CODE 1 1841 1842 #if COMPILE_PROBABLY_BAD_CODE 1843 // special case: if point is within the text rect and PixelToLine() 1844 // tells us that it's on the last line, but if point is actually 1845 // lower than the bottom of the last line, return the last offset 1846 // (can happen for newlines) 1847 if (lineNum == (fLines->NumLines() - 1)) { 1848 if (point.y >= ((line + 1)->origin + fTextRect.top)) 1849 return textLength; 1850 } 1851 #endif 1852 1853 // convert to text rect coordinates 1854 if (fAlignment != B_ALIGN_LEFT) { 1855 float alignmentOffset = fTextRect.Width() + 1 - LineWidth(lineNum); 1856 if (fAlignment == B_ALIGN_CENTER) 1857 alignmentOffset = floorf(alignmentOffset / 2); 1858 point.x -= alignmentOffset; 1859 } 1860 1861 point.x -= fTextRect.left; 1862 point.x = std::max(point.x, 0.0f); 1863 1864 // ToDo: The following code isn't very efficient, because it always starts 1865 // from the left end, so when the point is near the right end it's very 1866 // slow. 1867 int32 offset = line->offset; 1868 const int32 limit = (line + 1)->offset; 1869 float location = 0; 1870 do { 1871 const int32 nextInitial = _NextInitialByte(offset); 1872 const int32 saveOffset = offset; 1873 float width = 0; 1874 if (ByteAt(offset) == B_TAB) 1875 width = _ActualTabWidth(location); 1876 else 1877 width = _StyledWidth(saveOffset, nextInitial - saveOffset); 1878 if (location + width > point.x) { 1879 if (fabs(location + width - point.x) < fabs(location - point.x)) 1880 offset = nextInitial; 1881 break; 1882 } 1883 1884 location += width; 1885 offset = nextInitial; 1886 } while (offset < limit); 1887 1888 if (offset == (line + 1)->offset) { 1889 // special case: newlines aren't visible 1890 // return the offset of the character preceding the newline 1891 if (ByteAt(offset - 1) == B_ENTER) 1892 return --offset; 1893 1894 // special case: return the offset preceding any spaces that 1895 // aren't at the end of the buffer 1896 if (offset != textLength && ByteAt(offset - 1) == B_SPACE) 1897 return --offset; 1898 } 1899 1900 return offset; 1901 } 1902 1903 1904 int32 1905 BTextView::OffsetAt(int32 line) const 1906 { 1907 if (line < 0) 1908 return 0; 1909 1910 if (line > fLines->NumLines()) 1911 return fText->Length(); 1912 1913 return (*fLines)[line]->offset; 1914 } 1915 1916 1917 void 1918 BTextView::FindWord(int32 offset, int32* _fromOffset, int32* _toOffset) 1919 { 1920 if (offset < 0) { 1921 if (_fromOffset) 1922 *_fromOffset = 0; 1923 1924 if (_toOffset) 1925 *_toOffset = 0; 1926 1927 return; 1928 } 1929 1930 if (offset > fText->Length()) { 1931 if (_fromOffset) 1932 *_fromOffset = fText->Length(); 1933 1934 if (_toOffset) 1935 *_toOffset = fText->Length(); 1936 1937 return; 1938 } 1939 1940 if (_fromOffset) 1941 *_fromOffset = _PreviousWordBoundary(offset); 1942 1943 if (_toOffset) 1944 *_toOffset = _NextWordBoundary(offset); 1945 } 1946 1947 1948 bool 1949 BTextView::CanEndLine(int32 offset) 1950 { 1951 if (offset < 0 || offset > fText->Length()) 1952 return false; 1953 1954 // TODO: This should be improved using the LocaleKit. 1955 uint32 classification = _CharClassification(offset); 1956 1957 // wrapping is always allowed at end of text and at newlines 1958 if (classification == CHAR_CLASS_END_OF_TEXT || ByteAt(offset) == B_ENTER) 1959 return true; 1960 1961 uint32 nextClassification = _CharClassification(offset + 1); 1962 if (nextClassification == CHAR_CLASS_END_OF_TEXT) 1963 return true; 1964 1965 // never separate a punctuation char from its preceeding word 1966 if (classification == CHAR_CLASS_DEFAULT 1967 && nextClassification == CHAR_CLASS_PUNCTUATION) { 1968 return false; 1969 } 1970 1971 if ((classification == CHAR_CLASS_WHITESPACE 1972 && nextClassification != CHAR_CLASS_WHITESPACE) 1973 || (classification != CHAR_CLASS_WHITESPACE 1974 && nextClassification == CHAR_CLASS_WHITESPACE)) { 1975 return true; 1976 } 1977 1978 // allow wrapping after whitespace, unless more whitespace (except for 1979 // newline) follows 1980 if (classification == CHAR_CLASS_WHITESPACE 1981 && (nextClassification != CHAR_CLASS_WHITESPACE 1982 || ByteAt(offset + 1) == B_ENTER)) { 1983 return true; 1984 } 1985 1986 // allow wrapping after punctuation chars, unless more punctuation, closing 1987 // parens or quotes follow 1988 if (classification == CHAR_CLASS_PUNCTUATION 1989 && nextClassification != CHAR_CLASS_PUNCTUATION 1990 && nextClassification != CHAR_CLASS_PARENS_CLOSE 1991 && nextClassification != CHAR_CLASS_QUOTE) { 1992 return true; 1993 } 1994 1995 // allow wrapping after quotes, graphical chars and closing parens only if 1996 // whitespace follows (not perfect, but seems to do the right thing most 1997 // of the time) 1998 if ((classification == CHAR_CLASS_QUOTE 1999 || classification == CHAR_CLASS_GRAPHICAL 2000 || classification == CHAR_CLASS_PARENS_CLOSE) 2001 && nextClassification == CHAR_CLASS_WHITESPACE) { 2002 return true; 2003 } 2004 2005 return false; 2006 } 2007 2008 2009 float 2010 BTextView::LineWidth(int32 lineNumber) const 2011 { 2012 if (lineNumber < 0 || lineNumber >= fLines->NumLines()) 2013 return 0; 2014 2015 STELine* line = (*fLines)[lineNumber]; 2016 int32 length = (line + 1)->offset - line->offset; 2017 2018 // skip newline at the end of the line, if any, as it does no contribute 2019 // to the width 2020 if (ByteAt((line + 1)->offset - 1) == B_ENTER) 2021 length--; 2022 2023 return _TabExpandedStyledWidth(line->offset, length); 2024 } 2025 2026 2027 float 2028 BTextView::LineHeight(int32 lineNumber) const 2029 { 2030 float lineHeight = TextHeight(lineNumber, lineNumber); 2031 if (lineHeight == 0.0) { 2032 // We probably don't have text content yet. Take the initial 2033 // style's font height or fall back to the plain font. 2034 const BFont* font; 2035 fStyles->GetNullStyle(&font, NULL); 2036 if (font == NULL) 2037 font = be_plain_font; 2038 2039 font_height fontHeight; 2040 font->GetHeight(&fontHeight); 2041 // This is how the height is calculated in _RecalculateLineBreaks(). 2042 lineHeight = ceilf(fontHeight.ascent + fontHeight.descent) + 1; 2043 } 2044 2045 return lineHeight; 2046 } 2047 2048 2049 float 2050 BTextView::TextHeight(int32 startLine, int32 endLine) const 2051 { 2052 const int32 numLines = fLines->NumLines(); 2053 if (startLine < 0) 2054 startLine = 0; 2055 else if (startLine > numLines - 1) 2056 startLine = numLines - 1; 2057 2058 if (endLine < 0) 2059 endLine = 0; 2060 else if (endLine > numLines - 1) 2061 endLine = numLines - 1; 2062 2063 float height = (*fLines)[endLine + 1]->origin 2064 - (*fLines)[startLine]->origin; 2065 2066 if (startLine != endLine && endLine == numLines - 1 2067 && fText->RealCharAt(fText->Length() - 1) == B_ENTER) { 2068 height += (*fLines)[endLine + 1]->origin - (*fLines)[endLine]->origin; 2069 } 2070 2071 return ceilf(height); 2072 } 2073 2074 2075 void 2076 BTextView::GetTextRegion(int32 startOffset, int32 endOffset, 2077 BRegion* outRegion) const 2078 { 2079 if (!outRegion) 2080 return; 2081 2082 outRegion->MakeEmpty(); 2083 2084 // pin offsets at reasonable values 2085 if (startOffset < 0) 2086 startOffset = 0; 2087 else if (startOffset > fText->Length()) 2088 startOffset = fText->Length(); 2089 if (endOffset < 0) 2090 endOffset = 0; 2091 else if (endOffset > fText->Length()) 2092 endOffset = fText->Length(); 2093 2094 // return an empty region if the range is invalid 2095 if (startOffset >= endOffset) 2096 return; 2097 2098 float startLineHeight = 0.0; 2099 float endLineHeight = 0.0; 2100 BPoint startPt = PointAt(startOffset, &startLineHeight); 2101 BPoint endPt = PointAt(endOffset, &endLineHeight); 2102 2103 startLineHeight = ceilf(startLineHeight); 2104 endLineHeight = ceilf(endLineHeight); 2105 2106 BRect selRect; 2107 const BRect bounds(Bounds()); 2108 2109 if (startPt.y == endPt.y) { 2110 // this is a one-line region 2111 selRect.left = startPt.x; 2112 selRect.top = startPt.y; 2113 selRect.right = endPt.x - 1; 2114 selRect.bottom = endPt.y + endLineHeight - 1; 2115 outRegion->Include(selRect); 2116 } else { 2117 // more than one line in the specified offset range 2118 2119 // include first line from start of selection to end of window 2120 selRect.left = startPt.x; 2121 selRect.top = startPt.y; 2122 selRect.right = std::max(fTextRect.right, 2123 bounds.right - fLayoutData->rightInset); 2124 selRect.bottom = startPt.y + startLineHeight - 1; 2125 outRegion->Include(selRect); 2126 2127 if (startPt.y + startLineHeight < endPt.y) { 2128 // more than two lines in the range 2129 // include middle lines from start to end of window 2130 selRect.left = std::min(fTextRect.left, 2131 bounds.left + fLayoutData->leftInset); 2132 selRect.top = startPt.y + startLineHeight; 2133 selRect.right = std::max(fTextRect.right, 2134 bounds.right - fLayoutData->rightInset); 2135 selRect.bottom = endPt.y - 1; 2136 outRegion->Include(selRect); 2137 } 2138 2139 // include last line start of window to end of selection 2140 selRect.left = std::min(fTextRect.left, 2141 bounds.left + fLayoutData->leftInset); 2142 selRect.top = endPt.y; 2143 selRect.right = endPt.x - 1; 2144 selRect.bottom = endPt.y + endLineHeight - 1; 2145 outRegion->Include(selRect); 2146 } 2147 } 2148 2149 2150 void 2151 BTextView::ScrollToOffset(int32 offset) 2152 { 2153 BRect bounds = Bounds(); 2154 float lineHeight = 0.0; 2155 BPoint point = PointAt(offset, &lineHeight); 2156 BPoint scrollBy(B_ORIGIN); 2157 2158 // horizontal 2159 if (point.x < bounds.left) 2160 scrollBy.x = point.x - bounds.right; 2161 else if (point.x > bounds.right) 2162 scrollBy.x = point.x - bounds.left; 2163 2164 // prevent from scrolling out of view 2165 if (scrollBy.x != 0.0) { 2166 float rightMax = fTextRect.right + fLayoutData->rightInset; 2167 if (bounds.right + scrollBy.x > rightMax) 2168 scrollBy.x = rightMax - bounds.right; 2169 float leftMin = fTextRect.left - fLayoutData->leftInset; 2170 if (bounds.left + scrollBy.x < leftMin) 2171 scrollBy.x = leftMin - bounds.left; 2172 } 2173 2174 // vertical 2175 if (CountLines() > 1) { 2176 // scroll in Y only if multiple lines! 2177 if (point.y < bounds.top - fLayoutData->topInset) 2178 scrollBy.y = point.y - bounds.top - fLayoutData->topInset; 2179 else if (point.y + lineHeight > bounds.bottom 2180 + fLayoutData->bottomInset) { 2181 scrollBy.y = point.y + lineHeight - bounds.bottom 2182 + fLayoutData->bottomInset; 2183 } 2184 } 2185 2186 ScrollBy(scrollBy.x, scrollBy.y); 2187 2188 // Update text rect position and scroll bars 2189 if (CountLines() > 1 && !fWrap) 2190 FrameResized(Bounds().Width(), Bounds().Height()); 2191 } 2192 2193 2194 void 2195 BTextView::ScrollToSelection() 2196 { 2197 ScrollToOffset(fSelStart); 2198 } 2199 2200 2201 void 2202 BTextView::Highlight(int32 startOffset, int32 endOffset) 2203 { 2204 // pin offsets at reasonable values 2205 if (startOffset < 0) 2206 startOffset = 0; 2207 else if (startOffset > fText->Length()) 2208 startOffset = fText->Length(); 2209 if (endOffset < 0) 2210 endOffset = 0; 2211 else if (endOffset > fText->Length()) 2212 endOffset = fText->Length(); 2213 2214 if (startOffset >= endOffset) 2215 return; 2216 2217 BRegion selRegion; 2218 GetTextRegion(startOffset, endOffset, &selRegion); 2219 2220 SetDrawingMode(B_OP_INVERT); 2221 FillRegion(&selRegion, B_SOLID_HIGH); 2222 SetDrawingMode(B_OP_COPY); 2223 } 2224 2225 2226 // #pragma mark - Configuration methods 2227 2228 2229 void 2230 BTextView::SetTextRect(BRect rect) 2231 { 2232 if (rect == fTextRect) 2233 return; 2234 2235 if (!fWrap) { 2236 rect.right = Bounds().right; 2237 rect.bottom = Bounds().bottom; 2238 } 2239 2240 _UpdateInsets(rect); 2241 2242 fTextRect = rect; 2243 2244 _ResetTextRect(); 2245 } 2246 2247 2248 BRect 2249 BTextView::TextRect() const 2250 { 2251 return fTextRect; 2252 } 2253 2254 2255 void 2256 BTextView::_ResetTextRect() 2257 { 2258 BRect oldTextRect(fTextRect); 2259 // reset text rect to bounds minus insets ... 2260 fTextRect = Bounds().OffsetToCopy(B_ORIGIN); 2261 fTextRect.left += fLayoutData->leftInset; 2262 fTextRect.top += fLayoutData->topInset; 2263 fTextRect.right -= fLayoutData->rightInset; 2264 fTextRect.bottom -= fLayoutData->bottomInset; 2265 2266 // and rewrap (potentially adjusting the right and the bottom of the text 2267 // rect) 2268 _Refresh(0, fText->Length()); 2269 2270 // Make sure that the dirty area outside the text is redrawn too. 2271 BRegion invalid(oldTextRect | fTextRect); 2272 invalid.Exclude(fTextRect); 2273 Invalidate(&invalid); 2274 } 2275 2276 2277 void 2278 BTextView::SetInsets(float left, float top, float right, float bottom) 2279 { 2280 if (fLayoutData->leftInset == left 2281 && fLayoutData->topInset == top 2282 && fLayoutData->rightInset == right 2283 && fLayoutData->bottomInset == bottom) 2284 return; 2285 2286 fLayoutData->leftInset = left; 2287 fLayoutData->topInset = top; 2288 fLayoutData->rightInset = right; 2289 fLayoutData->bottomInset = bottom; 2290 2291 fLayoutData->overridden = true; 2292 2293 InvalidateLayout(); 2294 Invalidate(); 2295 } 2296 2297 2298 void 2299 BTextView::GetInsets(float* _left, float* _top, float* _right, 2300 float* _bottom) const 2301 { 2302 if (_left) 2303 *_left = fLayoutData->leftInset; 2304 if (_top) 2305 *_top = fLayoutData->topInset; 2306 if (_right) 2307 *_right = fLayoutData->rightInset; 2308 if (_bottom) 2309 *_bottom = fLayoutData->bottomInset; 2310 } 2311 2312 2313 void 2314 BTextView::SetStylable(bool stylable) 2315 { 2316 fStylable = stylable; 2317 } 2318 2319 2320 bool 2321 BTextView::IsStylable() const 2322 { 2323 return fStylable; 2324 } 2325 2326 2327 void 2328 BTextView::SetTabWidth(float width) 2329 { 2330 if (width == fTabWidth) 2331 return; 2332 2333 fTabWidth = width; 2334 2335 if (Window() != NULL) 2336 _Refresh(0, fText->Length()); 2337 } 2338 2339 2340 float 2341 BTextView::TabWidth() const 2342 { 2343 return fTabWidth; 2344 } 2345 2346 2347 void 2348 BTextView::MakeSelectable(bool selectable) 2349 { 2350 if (selectable == fSelectable) 2351 return; 2352 2353 fSelectable = selectable; 2354 2355 if (fActive && fSelStart != fSelEnd && Window() != NULL) 2356 Highlight(fSelStart, fSelEnd); 2357 } 2358 2359 2360 bool 2361 BTextView::IsSelectable() const 2362 { 2363 return fSelectable; 2364 } 2365 2366 2367 void 2368 BTextView::MakeEditable(bool editable) 2369 { 2370 if (editable == fEditable) 2371 return; 2372 2373 fEditable = editable; 2374 // TextControls change the color of the text when 2375 // they are made editable, so we need to invalidate 2376 // the NULL style here 2377 // TODO: it works well, but it could be caused by a bug somewhere else 2378 if (fEditable) 2379 fStyles->InvalidateNullStyle(); 2380 if (Window() != NULL && fActive) { 2381 if (!fEditable) { 2382 if (!fSelectable) 2383 _HideCaret(); 2384 _CancelInputMethod(); 2385 } 2386 } 2387 } 2388 2389 2390 bool 2391 BTextView::IsEditable() const 2392 { 2393 return fEditable; 2394 } 2395 2396 2397 void 2398 BTextView::SetWordWrap(bool wrap) 2399 { 2400 if (wrap == fWrap) 2401 return; 2402 2403 bool updateOnScreen = fActive && Window() != NULL; 2404 if (updateOnScreen) { 2405 // hide the caret, unhilite the selection 2406 if (fSelStart != fSelEnd) { 2407 if (fSelectable) 2408 Highlight(fSelStart, fSelEnd); 2409 } else 2410 _HideCaret(); 2411 } 2412 2413 BRect savedBounds = Bounds(); 2414 2415 fWrap = wrap; 2416 if (wrap) 2417 _ResetTextRect(); // calls _Refresh 2418 else 2419 _Refresh(0, fText->Length()); 2420 2421 if (fEditable || fSelectable) 2422 ScrollToOffset(fCaretOffset); 2423 2424 // redraw text rect and update scroll bars if bounds have changed 2425 if (Bounds() != savedBounds) 2426 FrameResized(Bounds().Width(), Bounds().Height()); 2427 2428 if (updateOnScreen) { 2429 // show the caret, hilite the selection 2430 if (fSelStart != fSelEnd) { 2431 if (fSelectable) 2432 Highlight(fSelStart, fSelEnd); 2433 } else 2434 _ShowCaret(); 2435 } 2436 } 2437 2438 2439 bool 2440 BTextView::DoesWordWrap() const 2441 { 2442 return fWrap; 2443 } 2444 2445 2446 void 2447 BTextView::SetMaxBytes(int32 max) 2448 { 2449 const int32 textLength = fText->Length(); 2450 fMaxBytes = max; 2451 2452 if (fMaxBytes < textLength) { 2453 int32 offset = fMaxBytes; 2454 // Delete the text after fMaxBytes, but 2455 // respect multibyte characters boundaries. 2456 const int32 previousInitial = _PreviousInitialByte(offset); 2457 if (_NextInitialByte(previousInitial) != offset) 2458 offset = previousInitial; 2459 2460 Delete(offset, textLength); 2461 } 2462 } 2463 2464 2465 int32 2466 BTextView::MaxBytes() const 2467 { 2468 return fMaxBytes; 2469 } 2470 2471 2472 void 2473 BTextView::DisallowChar(uint32 character) 2474 { 2475 if (fDisallowedChars == NULL) 2476 fDisallowedChars = new BList; 2477 if (!fDisallowedChars->HasItem(reinterpret_cast<void*>(character))) 2478 fDisallowedChars->AddItem(reinterpret_cast<void*>(character)); 2479 } 2480 2481 2482 void 2483 BTextView::AllowChar(uint32 character) 2484 { 2485 if (fDisallowedChars != NULL) 2486 fDisallowedChars->RemoveItem(reinterpret_cast<void*>(character)); 2487 } 2488 2489 2490 void 2491 BTextView::SetAlignment(alignment align) 2492 { 2493 // Do a reality check 2494 if (fAlignment != align && 2495 (align == B_ALIGN_LEFT || 2496 align == B_ALIGN_RIGHT || 2497 align == B_ALIGN_CENTER)) { 2498 fAlignment = align; 2499 2500 // After setting new alignment, update the view/window 2501 if (Window() != NULL) { 2502 FrameResized(Bounds().Width(), Bounds().Height()); 2503 // text rect position and scroll bars may change 2504 Invalidate(); 2505 } 2506 } 2507 } 2508 2509 2510 alignment 2511 BTextView::Alignment() const 2512 { 2513 return fAlignment; 2514 } 2515 2516 2517 void 2518 BTextView::SetAutoindent(bool state) 2519 { 2520 fAutoindent = state; 2521 } 2522 2523 2524 bool 2525 BTextView::DoesAutoindent() const 2526 { 2527 return fAutoindent; 2528 } 2529 2530 2531 void 2532 BTextView::SetColorSpace(color_space colors) 2533 { 2534 if (colors != fColorSpace && fOffscreen) { 2535 fColorSpace = colors; 2536 _DeleteOffscreen(); 2537 _NewOffscreen(); 2538 } 2539 } 2540 2541 2542 color_space 2543 BTextView::ColorSpace() const 2544 { 2545 return fColorSpace; 2546 } 2547 2548 2549 void 2550 BTextView::MakeResizable(bool resize, BView* resizeView) 2551 { 2552 if (resize) { 2553 fResizable = true; 2554 fContainerView = resizeView; 2555 2556 // Wrapping mode and resizable mode can't live together 2557 if (fWrap) { 2558 fWrap = false; 2559 2560 if (fActive && Window() != NULL) { 2561 if (fSelStart != fSelEnd) { 2562 if (fSelectable) 2563 Highlight(fSelStart, fSelEnd); 2564 } else 2565 _HideCaret(); 2566 } 2567 } 2568 // We need to reset the right inset, as otherwise the auto-resize would 2569 // get confused about just how wide the textview needs to be. 2570 // This seems to be an artifact of how Tracker creates the textview 2571 // during a rename action. 2572 fLayoutData->rightInset = fLayoutData->leftInset; 2573 } else { 2574 fResizable = false; 2575 fContainerView = NULL; 2576 if (fOffscreen) 2577 _DeleteOffscreen(); 2578 _NewOffscreen(); 2579 } 2580 2581 _Refresh(0, fText->Length()); 2582 } 2583 2584 2585 bool 2586 BTextView::IsResizable() const 2587 { 2588 return fResizable; 2589 } 2590 2591 2592 void 2593 BTextView::SetDoesUndo(bool undo) 2594 { 2595 if (undo && fUndo == NULL) 2596 fUndo = new UndoBuffer(this, B_UNDO_UNAVAILABLE); 2597 else if (!undo && fUndo != NULL) { 2598 delete fUndo; 2599 fUndo = NULL; 2600 } 2601 } 2602 2603 2604 bool 2605 BTextView::DoesUndo() const 2606 { 2607 return fUndo != NULL; 2608 } 2609 2610 2611 void 2612 BTextView::HideTyping(bool enabled) 2613 { 2614 if (enabled) 2615 Delete(0, fText->Length()); 2616 2617 fText->SetPasswordMode(enabled); 2618 } 2619 2620 2621 bool 2622 BTextView::IsTypingHidden() const 2623 { 2624 return fText->PasswordMode(); 2625 } 2626 2627 2628 // #pragma mark - Size methods 2629 2630 2631 void 2632 BTextView::ResizeToPreferred() 2633 { 2634 BView::ResizeToPreferred(); 2635 } 2636 2637 2638 void 2639 BTextView::GetPreferredSize(float* _width, float* _height) 2640 { 2641 CALLED(); 2642 2643 _ValidateLayoutData(); 2644 2645 if (_width) { 2646 float width = Bounds().Width(); 2647 if (width < fLayoutData->min.width 2648 || (Flags() & B_SUPPORTS_LAYOUT) != 0) { 2649 width = fLayoutData->min.width; 2650 } 2651 *_width = width; 2652 } 2653 2654 if (_height) { 2655 float height = Bounds().Height(); 2656 if (height < fLayoutData->min.height 2657 || (Flags() & B_SUPPORTS_LAYOUT) != 0) { 2658 height = fLayoutData->min.height; 2659 } 2660 *_height = height; 2661 } 2662 } 2663 2664 2665 BSize 2666 BTextView::MinSize() 2667 { 2668 CALLED(); 2669 2670 _ValidateLayoutData(); 2671 return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min); 2672 } 2673 2674 2675 BSize 2676 BTextView::MaxSize() 2677 { 2678 CALLED(); 2679 2680 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), 2681 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); 2682 } 2683 2684 2685 BSize 2686 BTextView::PreferredSize() 2687 { 2688 CALLED(); 2689 2690 _ValidateLayoutData(); 2691 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), 2692 fLayoutData->preferred); 2693 } 2694 2695 2696 bool 2697 BTextView::HasHeightForWidth() 2698 { 2699 if (IsEditable()) 2700 return BView::HasHeightForWidth(); 2701 2702 // When not editable, we assume that all text is supposed to be visible. 2703 return true; 2704 } 2705 2706 2707 void 2708 BTextView::GetHeightForWidth(float width, float* min, float* max, 2709 float* preferred) 2710 { 2711 if (IsEditable()) { 2712 BView::GetHeightForWidth(width, min, max, preferred); 2713 return; 2714 } 2715 2716 BRect saveTextRect = fTextRect; 2717 2718 fTextRect.right = fTextRect.left + width; 2719 2720 // If specific insets were set, reduce the width accordingly (this may result in more 2721 // linebreaks being inserted) 2722 if (fLayoutData->overridden) { 2723 fTextRect.left += fLayoutData->leftInset; 2724 fTextRect.right -= fLayoutData->rightInset; 2725 } 2726 2727 int32 fromLine = _LineAt(0); 2728 int32 toLine = _LineAt(fText->Length()); 2729 _RecalculateLineBreaks(&fromLine, &toLine); 2730 2731 // If specific insets were set, add the top and bottom margins to the returned preferred height 2732 if (fLayoutData->overridden) { 2733 fTextRect.top -= fLayoutData->topInset; 2734 fTextRect.bottom += fLayoutData->bottomInset; 2735 } 2736 2737 if (min != NULL) 2738 *min = fTextRect.Height(); 2739 if (max != NULL) 2740 *max = B_SIZE_UNLIMITED; 2741 if (preferred != NULL) 2742 *preferred = fTextRect.Height(); 2743 2744 // Restore the text rect since we were not supposed to change it in this method. 2745 // Unfortunately, we did change a few other things by calling _RecalculateLineBreaks, that are 2746 // not so easily undone. However, we are likely to soon get resized to the new width and height 2747 // computed here, and that will recompute the linebreaks and do a full _Refresh if needed. 2748 fTextRect = saveTextRect; 2749 } 2750 2751 2752 // #pragma mark - Layout methods 2753 2754 2755 void 2756 BTextView::LayoutInvalidated(bool descendants) 2757 { 2758 CALLED(); 2759 2760 fLayoutData->valid = false; 2761 } 2762 2763 2764 void 2765 BTextView::DoLayout() 2766 { 2767 // Bail out, if we shan't do layout. 2768 if (!(Flags() & B_SUPPORTS_LAYOUT)) 2769 return; 2770 2771 CALLED(); 2772 2773 // If the user set a layout, we let the base class version call its 2774 // hook. 2775 if (GetLayout()) { 2776 BView::DoLayout(); 2777 return; 2778 } 2779 2780 _ValidateLayoutData(); 2781 2782 // validate current size 2783 BSize size(Bounds().Size()); 2784 if (size.width < fLayoutData->min.width) 2785 size.width = fLayoutData->min.width; 2786 if (size.height < fLayoutData->min.height) 2787 size.height = fLayoutData->min.height; 2788 2789 _ResetTextRect(); 2790 } 2791 2792 2793 void 2794 BTextView::_ValidateLayoutData() 2795 { 2796 if (fLayoutData->valid) 2797 return; 2798 2799 CALLED(); 2800 2801 float lineHeight = ceilf(LineHeight(0)); 2802 TRACE("line height: %.2f\n", lineHeight); 2803 2804 // compute our minimal size 2805 BSize min(lineHeight * 3, lineHeight); 2806 min.width += fLayoutData->leftInset + fLayoutData->rightInset; 2807 min.height += fLayoutData->topInset + fLayoutData->bottomInset; 2808 2809 fLayoutData->min = min; 2810 2811 // compute our preferred size 2812 fLayoutData->preferred.height = _TextHeight(); 2813 2814 if (fWrap) 2815 fLayoutData->preferred.width = min.width + 5 * lineHeight; 2816 else { 2817 float maxWidth = fLines->MaxWidth(); 2818 if (maxWidth < min.width) 2819 maxWidth = min.width; 2820 2821 fLayoutData->preferred.width 2822 = maxWidth + fLayoutData->leftInset + fLayoutData->rightInset; 2823 } 2824 2825 fLayoutData->valid = true; 2826 ResetLayoutInvalidation(); 2827 2828 TRACE("width: %.2f, height: %.2f\n", min.width, min.height); 2829 } 2830 2831 2832 // #pragma mark - 2833 2834 2835 void 2836 BTextView::AllAttached() 2837 { 2838 BView::AllAttached(); 2839 } 2840 2841 2842 void 2843 BTextView::AllDetached() 2844 { 2845 BView::AllDetached(); 2846 } 2847 2848 2849 /* static */ 2850 text_run_array* 2851 BTextView::AllocRunArray(int32 entryCount, int32* outSize) 2852 { 2853 int32 size = sizeof(text_run_array) + (entryCount - 1) * sizeof(text_run); 2854 2855 text_run_array* runArray = (text_run_array*)calloc(size, 1); 2856 if (runArray == NULL) { 2857 if (outSize != NULL) 2858 *outSize = 0; 2859 return NULL; 2860 } 2861 2862 runArray->count = entryCount; 2863 2864 // Call constructors explicitly as the text_run_array 2865 // was allocated with malloc (and has to, for backwards 2866 // compatibility) 2867 for (int32 i = 0; i < runArray->count; i++) 2868 new (&runArray->runs[i].font) BFont; 2869 2870 if (outSize != NULL) 2871 *outSize = size; 2872 2873 return runArray; 2874 } 2875 2876 2877 /* static */ 2878 text_run_array* 2879 BTextView::CopyRunArray(const text_run_array* orig, int32 countDelta) 2880 { 2881 text_run_array* copy = AllocRunArray(countDelta, NULL); 2882 if (copy != NULL) { 2883 for (int32 i = 0; i < countDelta; i++) { 2884 copy->runs[i].offset = orig->runs[i].offset; 2885 copy->runs[i].font = orig->runs[i].font; 2886 copy->runs[i].color = orig->runs[i].color; 2887 } 2888 } 2889 return copy; 2890 } 2891 2892 2893 /* static */ 2894 void 2895 BTextView::FreeRunArray(text_run_array* array) 2896 { 2897 if (array == NULL) 2898 return; 2899 2900 // Call destructors explicitly 2901 for (int32 i = 0; i < array->count; i++) 2902 array->runs[i].font.~BFont(); 2903 2904 free(array); 2905 } 2906 2907 2908 /* static */ 2909 void* 2910 BTextView::FlattenRunArray(const text_run_array* runArray, int32* _size) 2911 { 2912 CALLED(); 2913 int32 size = sizeof(flattened_text_run_array) + (runArray->count - 1) 2914 * sizeof(flattened_text_run); 2915 2916 flattened_text_run_array* array = (flattened_text_run_array*)malloc(size); 2917 if (array == NULL) { 2918 if (_size) 2919 *_size = 0; 2920 return NULL; 2921 } 2922 2923 array->magic = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayMagic); 2924 array->version = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayVersion); 2925 array->count = B_HOST_TO_BENDIAN_INT32(runArray->count); 2926 2927 for (int32 i = 0; i < runArray->count; i++) { 2928 array->styles[i].offset = B_HOST_TO_BENDIAN_INT32( 2929 runArray->runs[i].offset); 2930 runArray->runs[i].font.GetFamilyAndStyle(&array->styles[i].family, 2931 &array->styles[i].style); 2932 array->styles[i].size = B_HOST_TO_BENDIAN_FLOAT( 2933 runArray->runs[i].font.Size()); 2934 array->styles[i].shear = B_HOST_TO_BENDIAN_FLOAT( 2935 runArray->runs[i].font.Shear()); 2936 array->styles[i].face = B_HOST_TO_BENDIAN_INT16( 2937 runArray->runs[i].font.Face()); 2938 array->styles[i].red = runArray->runs[i].color.red; 2939 array->styles[i].green = runArray->runs[i].color.green; 2940 array->styles[i].blue = runArray->runs[i].color.blue; 2941 array->styles[i].alpha = 255; 2942 array->styles[i]._reserved_ = 0; 2943 } 2944 2945 if (_size) 2946 *_size = size; 2947 2948 return array; 2949 } 2950 2951 2952 /* static */ 2953 text_run_array* 2954 BTextView::UnflattenRunArray(const void* data, int32* _size) 2955 { 2956 CALLED(); 2957 flattened_text_run_array* array = (flattened_text_run_array*)data; 2958 2959 if (B_BENDIAN_TO_HOST_INT32(array->magic) != kFlattenedTextRunArrayMagic 2960 || B_BENDIAN_TO_HOST_INT32(array->version) 2961 != kFlattenedTextRunArrayVersion) { 2962 if (_size) 2963 *_size = 0; 2964 2965 return NULL; 2966 } 2967 2968 int32 count = B_BENDIAN_TO_HOST_INT32(array->count); 2969 2970 text_run_array* runArray = AllocRunArray(count, _size); 2971 if (runArray == NULL) 2972 return NULL; 2973 2974 for (int32 i = 0; i < count; i++) { 2975 runArray->runs[i].offset = B_BENDIAN_TO_HOST_INT32( 2976 array->styles[i].offset); 2977 2978 // Set family and style independently from each other, so that 2979 // even if the family doesn't exist, we try to preserve the style 2980 runArray->runs[i].font.SetFamilyAndStyle(array->styles[i].family, NULL); 2981 runArray->runs[i].font.SetFamilyAndStyle(NULL, array->styles[i].style); 2982 2983 runArray->runs[i].font.SetSize(B_BENDIAN_TO_HOST_FLOAT( 2984 array->styles[i].size)); 2985 runArray->runs[i].font.SetShear(B_BENDIAN_TO_HOST_FLOAT( 2986 array->styles[i].shear)); 2987 2988 uint16 face = B_BENDIAN_TO_HOST_INT16(array->styles[i].face); 2989 if (face != B_REGULAR_FACE) { 2990 // Be's version doesn't seem to set this correctly 2991 runArray->runs[i].font.SetFace(face); 2992 } 2993 2994 runArray->runs[i].color.red = array->styles[i].red; 2995 runArray->runs[i].color.green = array->styles[i].green; 2996 runArray->runs[i].color.blue = array->styles[i].blue; 2997 runArray->runs[i].color.alpha = array->styles[i].alpha; 2998 } 2999 3000 return runArray; 3001 } 3002 3003 3004 void 3005 BTextView::InsertText(const char* text, int32 length, int32 offset, 3006 const text_run_array* runs) 3007 { 3008 CALLED(); 3009 3010 if (length < 0) 3011 length = 0; 3012 3013 if (offset < 0) 3014 offset = 0; 3015 else if (offset > fText->Length()) 3016 offset = fText->Length(); 3017 3018 if (length > 0) { 3019 // add the text to the buffer 3020 fText->InsertText(text, length, offset); 3021 3022 // update the start offsets of each line below offset 3023 fLines->BumpOffset(length, _LineAt(offset) + 1); 3024 3025 // update the style runs 3026 fStyles->BumpOffset(length, fStyles->OffsetToRun(offset - 1) + 1); 3027 3028 // offset the caret/selection, if the text was inserted before it 3029 if (offset <= fSelEnd) { 3030 fSelStart += length; 3031 fCaretOffset = fSelEnd = fSelStart; 3032 } 3033 } 3034 3035 if (fStylable && runs != NULL) 3036 _SetRunArray(offset, offset + length, runs); 3037 else { 3038 // apply null-style to inserted text 3039 _ApplyStyleRange(offset, offset + length); 3040 } 3041 } 3042 3043 3044 void 3045 BTextView::DeleteText(int32 fromOffset, int32 toOffset) 3046 { 3047 CALLED(); 3048 3049 if (fromOffset < 0) 3050 fromOffset = 0; 3051 else if (fromOffset > fText->Length()) 3052 fromOffset = fText->Length(); 3053 3054 if (toOffset < 0) 3055 toOffset = 0; 3056 else if (toOffset > fText->Length()) 3057 toOffset = fText->Length(); 3058 3059 if (fromOffset >= toOffset) 3060 return; 3061 3062 // set nullStyle to style at beginning of range 3063 fStyles->InvalidateNullStyle(); 3064 fStyles->SyncNullStyle(fromOffset); 3065 3066 // remove from the text buffer 3067 fText->RemoveRange(fromOffset, toOffset); 3068 3069 // remove any lines that have been obliterated 3070 fLines->RemoveLineRange(fromOffset, toOffset); 3071 3072 // remove any style runs that have been obliterated 3073 fStyles->RemoveStyleRange(fromOffset, toOffset); 3074 3075 // adjust the selection accordingly, assumes fSelEnd >= fSelStart! 3076 int32 range = toOffset - fromOffset; 3077 if (fSelStart >= toOffset) { 3078 // selection is behind the range that was removed 3079 fSelStart -= range; 3080 fSelEnd -= range; 3081 } else if (fSelStart >= fromOffset && fSelEnd <= toOffset) { 3082 // the selection is within the range that was removed 3083 fSelStart = fSelEnd = fromOffset; 3084 } else if (fSelStart >= fromOffset && fSelEnd > toOffset) { 3085 // the selection starts within and ends after the range 3086 // the remaining part is the part that was after the range 3087 fSelStart = fromOffset; 3088 fSelEnd = fromOffset + fSelEnd - toOffset; 3089 } else if (fSelStart < fromOffset && fSelEnd < toOffset) { 3090 // the selection starts before, but ends within the range 3091 fSelEnd = fromOffset; 3092 } else if (fSelStart < fromOffset && fSelEnd >= toOffset) { 3093 // the selection starts before and ends after the range 3094 fSelEnd -= range; 3095 } 3096 } 3097 3098 3099 /*! Undoes the last changes. 3100 3101 \param clipboard A \a clipboard to use for the undo operation. 3102 */ 3103 void 3104 BTextView::Undo(BClipboard* clipboard) 3105 { 3106 if (fUndo) 3107 fUndo->Undo(clipboard); 3108 } 3109 3110 3111 undo_state 3112 BTextView::UndoState(bool* isRedo) const 3113 { 3114 return fUndo == NULL ? B_UNDO_UNAVAILABLE : fUndo->State(isRedo); 3115 } 3116 3117 3118 // #pragma mark - GetDragParameters() is protected 3119 3120 3121 void 3122 BTextView::GetDragParameters(BMessage* drag, BBitmap** bitmap, BPoint* point, 3123 BHandler** handler) 3124 { 3125 CALLED(); 3126 if (drag == NULL) 3127 return; 3128 3129 // Add originator and action 3130 drag->AddPointer("be:originator", this); 3131 drag->AddInt32("be_actions", B_TRASH_TARGET); 3132 3133 // add the text 3134 int32 numBytes = fSelEnd - fSelStart; 3135 const char* text = fText->GetString(fSelStart, &numBytes); 3136 drag->AddData("text/plain", B_MIME_TYPE, text, numBytes); 3137 3138 // add the corresponding styles 3139 int32 size = 0; 3140 text_run_array* styles = RunArray(fSelStart, fSelEnd, &size); 3141 3142 if (styles != NULL) { 3143 drag->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE, 3144 styles, size); 3145 3146 FreeRunArray(styles); 3147 } 3148 3149 if (bitmap != NULL) 3150 *bitmap = NULL; 3151 3152 if (handler != NULL) 3153 *handler = NULL; 3154 } 3155 3156 3157 // #pragma mark - FBC padding and forbidden methods 3158 3159 3160 void BTextView::_ReservedTextView3() {} 3161 void BTextView::_ReservedTextView4() {} 3162 void BTextView::_ReservedTextView5() {} 3163 void BTextView::_ReservedTextView6() {} 3164 void BTextView::_ReservedTextView7() {} 3165 void BTextView::_ReservedTextView8() {} 3166 void BTextView::_ReservedTextView9() {} 3167 void BTextView::_ReservedTextView10() {} 3168 void BTextView::_ReservedTextView11() {} 3169 void BTextView::_ReservedTextView12() {} 3170 3171 3172 // #pragma mark - Private methods 3173 3174 3175 /*! Inits the BTextView object. 3176 3177 \param textRect The BTextView's text rect. 3178 \param initialFont The font which the BTextView will use. 3179 \param initialColor The initial color of the text. 3180 */ 3181 void 3182 BTextView::_InitObject(BRect textRect, const BFont* initialFont, 3183 const rgb_color* initialColor) 3184 { 3185 BFont font; 3186 if (initialFont == NULL) 3187 GetFont(&font); 3188 else 3189 font = *initialFont; 3190 3191 _NormalizeFont(&font); 3192 3193 rgb_color documentTextColor = ui_color(B_DOCUMENT_TEXT_COLOR); 3194 3195 if (initialColor == NULL) 3196 initialColor = &documentTextColor; 3197 3198 fText = new BPrivate::TextGapBuffer; 3199 fLines = new LineBuffer; 3200 fStyles = new StyleBuffer(&font, initialColor); 3201 3202 fInstalledNavigateCommandWordwiseShortcuts = false; 3203 fInstalledNavigateOptionWordwiseShortcuts = false; 3204 fInstalledNavigateOptionLinewiseShortcuts = false; 3205 fInstalledNavigateHomeEndDocwiseShortcuts = false; 3206 3207 fInstalledSelectCommandWordwiseShortcuts = false; 3208 fInstalledSelectOptionWordwiseShortcuts = false; 3209 fInstalledSelectOptionLinewiseShortcuts = false; 3210 fInstalledSelectHomeEndDocwiseShortcuts = false; 3211 3212 fInstalledRemoveCommandWordwiseShortcuts = false; 3213 fInstalledRemoveOptionWordwiseShortcuts = false; 3214 3215 // We put these here instead of in the constructor initializer list 3216 // to have less code duplication, and a single place where to do changes 3217 // if needed. 3218 fTextRect = textRect; 3219 // NOTE: The only places where text rect is changed: 3220 // * width and height are adjusted in _RecalculateLineBreaks(), 3221 // text rect maintains constant insets, use SetInsets() to change. 3222 fMinTextRectWidth = fTextRect.Width(); 3223 // see SetTextRect() 3224 fSelStart = fSelEnd = 0; 3225 fCaretVisible = false; 3226 fCaretTime = 0; 3227 fCaretOffset = 0; 3228 fClickCount = 0; 3229 fClickTime = 0; 3230 fDragOffset = -1; 3231 fCursor = 0; 3232 fActive = false; 3233 fStylable = false; 3234 fTabWidth = 28.0; 3235 fSelectable = true; 3236 fEditable = true; 3237 fWrap = true; 3238 fMaxBytes = INT32_MAX; 3239 fDisallowedChars = NULL; 3240 fAlignment = B_ALIGN_LEFT; 3241 fAutoindent = false; 3242 fOffscreen = NULL; 3243 fColorSpace = B_CMAP8; 3244 fResizable = false; 3245 fContainerView = NULL; 3246 fUndo = NULL; 3247 fInline = NULL; 3248 fDragRunner = NULL; 3249 fClickRunner = NULL; 3250 fTrackingMouse = NULL; 3251 3252 fLayoutData = new LayoutData; 3253 _UpdateInsets(textRect); 3254 3255 fLastClickOffset = -1; 3256 3257 SetDoesUndo(true); 3258 } 3259 3260 3261 //! Handles when Backspace key is pressed. 3262 void 3263 BTextView::_HandleBackspace(int32 modifiers) 3264 { 3265 if (!fEditable) 3266 return; 3267 3268 if (modifiers < 0) { 3269 BMessage* currentMessage = Window()->CurrentMessage(); 3270 if (currentMessage == NULL 3271 || currentMessage->FindInt32("modifiers", &modifiers) != B_OK) { 3272 modifiers = 0; 3273 } 3274 } 3275 3276 bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0; 3277 bool optionKeyDown = (modifiers & B_OPTION_KEY) != 0; 3278 bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0; 3279 3280 if ((commandKeyDown || optionKeyDown) && !controlKeyDown) { 3281 fSelStart = _PreviousWordStart(fCaretOffset - 1); 3282 fSelEnd = fCaretOffset; 3283 } 3284 3285 if (fUndo) { 3286 TypingUndoBuffer* undoBuffer = dynamic_cast<TypingUndoBuffer*>(fUndo); 3287 if (!undoBuffer) { 3288 delete fUndo; 3289 fUndo = undoBuffer = new TypingUndoBuffer(this); 3290 } 3291 undoBuffer->BackwardErase(); 3292 } 3293 3294 // we may draw twice, so turn updates off for now 3295 if (Window() != NULL) 3296 Window()->DisableUpdates(); 3297 3298 if (fSelStart == fSelEnd) { 3299 if (fSelStart != 0) 3300 fSelStart = _PreviousInitialByte(fSelStart); 3301 } else 3302 Highlight(fSelStart, fSelEnd); 3303 3304 DeleteText(fSelStart, fSelEnd); 3305 fCaretOffset = fSelEnd = fSelStart; 3306 3307 _Refresh(fSelStart, fSelEnd, fCaretOffset); 3308 3309 // turn updates back on 3310 if (Window() != NULL) 3311 Window()->EnableUpdates(); 3312 } 3313 3314 3315 //! Handles when an arrow key is pressed. 3316 void 3317 BTextView::_HandleArrowKey(uint32 arrowKey, int32 modifiers) 3318 { 3319 // return if there's nowhere to go 3320 if (fText->Length() == 0) 3321 return; 3322 3323 int32 selStart = fSelStart; 3324 int32 selEnd = fSelEnd; 3325 3326 if (modifiers < 0) { 3327 BMessage* currentMessage = Window()->CurrentMessage(); 3328 if (currentMessage == NULL 3329 || currentMessage->FindInt32("modifiers", &modifiers) != B_OK) { 3330 modifiers = 0; 3331 } 3332 } 3333 3334 bool shiftKeyDown = (modifiers & B_SHIFT_KEY) != 0; 3335 bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0; 3336 bool optionKeyDown = (modifiers & B_OPTION_KEY) != 0; 3337 bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0; 3338 3339 int32 lastClickOffset = fCaretOffset; 3340 3341 switch (arrowKey) { 3342 case B_LEFT_ARROW: 3343 if (!fEditable && !fSelectable) 3344 _ScrollBy(-kHorizontalScrollBarStep, 0); 3345 else if (fSelStart != fSelEnd && !shiftKeyDown) 3346 fCaretOffset = fSelStart; 3347 else { 3348 if ((commandKeyDown || optionKeyDown) && !controlKeyDown) 3349 fCaretOffset = _PreviousWordStart(fCaretOffset - 1); 3350 else 3351 fCaretOffset = _PreviousInitialByte(fCaretOffset); 3352 3353 if (shiftKeyDown && fCaretOffset != lastClickOffset) { 3354 if (fCaretOffset < fSelStart) { 3355 // extend selection to the left 3356 selStart = fCaretOffset; 3357 if (lastClickOffset > fSelStart) { 3358 // caret has jumped across "anchor" 3359 selEnd = fSelStart; 3360 } 3361 } else { 3362 // shrink selection from the right 3363 selEnd = fCaretOffset; 3364 } 3365 } 3366 } 3367 break; 3368 3369 case B_RIGHT_ARROW: 3370 if (!fEditable && !fSelectable) 3371 _ScrollBy(kHorizontalScrollBarStep, 0); 3372 else if (fSelStart != fSelEnd && !shiftKeyDown) 3373 fCaretOffset = fSelEnd; 3374 else { 3375 if ((commandKeyDown || optionKeyDown) && !controlKeyDown) 3376 fCaretOffset = _NextWordEnd(fCaretOffset); 3377 else 3378 fCaretOffset = _NextInitialByte(fCaretOffset); 3379 3380 if (shiftKeyDown && fCaretOffset != lastClickOffset) { 3381 if (fCaretOffset > fSelEnd) { 3382 // extend selection to the right 3383 selEnd = fCaretOffset; 3384 if (lastClickOffset < fSelEnd) { 3385 // caret has jumped across "anchor" 3386 selStart = fSelEnd; 3387 } 3388 } else { 3389 // shrink selection from the left 3390 selStart = fCaretOffset; 3391 } 3392 } 3393 } 3394 break; 3395 3396 case B_UP_ARROW: 3397 { 3398 if (!fEditable && !fSelectable) 3399 _ScrollBy(0, -kVerticalScrollBarStep); 3400 else if (fSelStart != fSelEnd && !shiftKeyDown) 3401 fCaretOffset = fSelStart; 3402 else { 3403 if (optionKeyDown && !commandKeyDown && !controlKeyDown) 3404 fCaretOffset = _PreviousLineStart(fCaretOffset); 3405 else if (commandKeyDown && !optionKeyDown && !controlKeyDown) { 3406 _ScrollTo(0, 0); 3407 fCaretOffset = 0; 3408 } else { 3409 float height; 3410 BPoint point = PointAt(fCaretOffset, &height); 3411 // find the caret position on the previous 3412 // line by gently stepping onto this line 3413 for (int i = 1; i <= height; i++) { 3414 point.y--; 3415 int32 offset = OffsetAt(point); 3416 if (offset < fCaretOffset || i == height) { 3417 fCaretOffset = offset; 3418 break; 3419 } 3420 } 3421 } 3422 3423 if (shiftKeyDown && fCaretOffset != lastClickOffset) { 3424 if (fCaretOffset < fSelStart) { 3425 // extend selection to the top 3426 selStart = fCaretOffset; 3427 if (lastClickOffset > fSelStart) { 3428 // caret has jumped across "anchor" 3429 selEnd = fSelStart; 3430 } 3431 } else { 3432 // shrink selection from the bottom 3433 selEnd = fCaretOffset; 3434 } 3435 } 3436 } 3437 break; 3438 } 3439 3440 case B_DOWN_ARROW: 3441 { 3442 if (!fEditable && !fSelectable) 3443 _ScrollBy(0, kVerticalScrollBarStep); 3444 else if (fSelStart != fSelEnd && !shiftKeyDown) 3445 fCaretOffset = fSelEnd; 3446 else { 3447 if (optionKeyDown && !commandKeyDown && !controlKeyDown) 3448 fCaretOffset = _NextLineEnd(fCaretOffset); 3449 else if (commandKeyDown && !optionKeyDown && !controlKeyDown) { 3450 _ScrollTo(0, fTextRect.bottom + fLayoutData->bottomInset); 3451 fCaretOffset = fText->Length(); 3452 } else { 3453 float height; 3454 BPoint point = PointAt(fCaretOffset, &height); 3455 point.y += height; 3456 fCaretOffset = OffsetAt(point); 3457 } 3458 3459 if (shiftKeyDown && fCaretOffset != lastClickOffset) { 3460 if (fCaretOffset > fSelEnd) { 3461 // extend selection to the bottom 3462 selEnd = fCaretOffset; 3463 if (lastClickOffset < fSelEnd) { 3464 // caret has jumped across "anchor" 3465 selStart = fSelEnd; 3466 } 3467 } else { 3468 // shrink selection from the top 3469 selStart = fCaretOffset; 3470 } 3471 } 3472 } 3473 break; 3474 } 3475 } 3476 3477 fStyles->InvalidateNullStyle(); 3478 3479 if (fEditable || fSelectable) { 3480 if (shiftKeyDown) 3481 Select(selStart, selEnd); 3482 else 3483 Select(fCaretOffset, fCaretOffset); 3484 3485 // scroll if needed 3486 ScrollToOffset(fCaretOffset); 3487 } 3488 } 3489 3490 3491 //! Handles when the Delete key is pressed. 3492 void 3493 BTextView::_HandleDelete(int32 modifiers) 3494 { 3495 if (!fEditable) 3496 return; 3497 3498 if (modifiers < 0) { 3499 BMessage* currentMessage = Window()->CurrentMessage(); 3500 if (currentMessage == NULL 3501 || currentMessage->FindInt32("modifiers", &modifiers) != B_OK) { 3502 modifiers = 0; 3503 } 3504 } 3505 3506 bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0; 3507 bool optionKeyDown = (modifiers & B_OPTION_KEY) != 0; 3508 bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0; 3509 3510 if ((commandKeyDown || optionKeyDown) && !controlKeyDown) { 3511 fSelStart = fCaretOffset; 3512 fSelEnd = _NextWordEnd(fCaretOffset) + 1; 3513 } 3514 3515 if (fUndo) { 3516 TypingUndoBuffer* undoBuffer = dynamic_cast<TypingUndoBuffer*>(fUndo); 3517 if (!undoBuffer) { 3518 delete fUndo; 3519 fUndo = undoBuffer = new TypingUndoBuffer(this); 3520 } 3521 undoBuffer->ForwardErase(); 3522 } 3523 3524 // we may draw twice, so turn updates off for now 3525 if (Window() != NULL) 3526 Window()->DisableUpdates(); 3527 3528 if (fSelStart == fSelEnd) { 3529 if (fSelEnd != fText->Length()) 3530 fSelEnd = _NextInitialByte(fSelEnd); 3531 } else 3532 Highlight(fSelStart, fSelEnd); 3533 3534 DeleteText(fSelStart, fSelEnd); 3535 fCaretOffset = fSelEnd = fSelStart; 3536 3537 _Refresh(fSelStart, fSelEnd, fCaretOffset); 3538 3539 // turn updates back on 3540 if (Window() != NULL) 3541 Window()->EnableUpdates(); 3542 } 3543 3544 3545 //! Handles when the Page Up, Page Down, Home, or End key is pressed. 3546 void 3547 BTextView::_HandlePageKey(uint32 pageKey, int32 modifiers) 3548 { 3549 if (modifiers < 0) { 3550 BMessage* currentMessage = Window()->CurrentMessage(); 3551 if (currentMessage == NULL 3552 || currentMessage->FindInt32("modifiers", &modifiers) != B_OK) { 3553 modifiers = 0; 3554 } 3555 } 3556 3557 bool shiftKeyDown = (modifiers & B_SHIFT_KEY) != 0; 3558 bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0; 3559 bool optionKeyDown = (modifiers & B_OPTION_KEY) != 0; 3560 bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0; 3561 3562 STELine* line = NULL; 3563 int32 selStart = fSelStart; 3564 int32 selEnd = fSelEnd; 3565 3566 int32 lastClickOffset = fCaretOffset; 3567 switch (pageKey) { 3568 case B_HOME: 3569 if (!fEditable && !fSelectable) { 3570 fCaretOffset = 0; 3571 _ScrollTo(0, 0); 3572 break; 3573 } else { 3574 if (commandKeyDown && !optionKeyDown && !controlKeyDown) { 3575 _ScrollTo(0, 0); 3576 fCaretOffset = 0; 3577 } else { 3578 // get the start of the last line if caret is on it 3579 line = (*fLines)[_LineAt(lastClickOffset)]; 3580 fCaretOffset = line->offset; 3581 } 3582 3583 if (!shiftKeyDown) 3584 selStart = selEnd = fCaretOffset; 3585 else if (fCaretOffset != lastClickOffset) { 3586 if (fCaretOffset < fSelStart) { 3587 // extend selection to the left 3588 selStart = fCaretOffset; 3589 if (lastClickOffset > fSelStart) { 3590 // caret has jumped across "anchor" 3591 selEnd = fSelStart; 3592 } 3593 } else { 3594 // shrink selection from the right 3595 selEnd = fCaretOffset; 3596 } 3597 } 3598 } 3599 break; 3600 3601 case B_END: 3602 if (!fEditable && !fSelectable) { 3603 fCaretOffset = fText->Length(); 3604 _ScrollTo(0, fTextRect.bottom + fLayoutData->bottomInset); 3605 break; 3606 } else { 3607 if (commandKeyDown && !optionKeyDown && !controlKeyDown) { 3608 _ScrollTo(0, fTextRect.bottom + fLayoutData->bottomInset); 3609 fCaretOffset = fText->Length(); 3610 } else { 3611 // If we are on the last line, just go to the last 3612 // character in the buffer, otherwise get the starting 3613 // offset of the next line, and go to the previous character 3614 int32 currentLine = _LineAt(lastClickOffset); 3615 if (currentLine + 1 < fLines->NumLines()) { 3616 line = (*fLines)[currentLine + 1]; 3617 fCaretOffset = _PreviousInitialByte(line->offset); 3618 } else { 3619 // This check is needed to avoid moving the cursor 3620 // when the cursor is on the last line, and that line 3621 // is empty 3622 if (fCaretOffset != fText->Length()) { 3623 fCaretOffset = fText->Length(); 3624 if (ByteAt(fCaretOffset - 1) == B_ENTER) 3625 fCaretOffset--; 3626 } 3627 } 3628 } 3629 3630 if (!shiftKeyDown) 3631 selStart = selEnd = fCaretOffset; 3632 else if (fCaretOffset != lastClickOffset) { 3633 if (fCaretOffset > fSelEnd) { 3634 // extend selection to the right 3635 selEnd = fCaretOffset; 3636 if (lastClickOffset < fSelEnd) { 3637 // caret has jumped across "anchor" 3638 selStart = fSelEnd; 3639 } 3640 } else { 3641 // shrink selection from the left 3642 selStart = fCaretOffset; 3643 } 3644 } 3645 } 3646 break; 3647 3648 case B_PAGE_UP: 3649 { 3650 float lineHeight; 3651 BPoint currentPos = PointAt(fCaretOffset, &lineHeight); 3652 BPoint nextPos(currentPos.x, 3653 currentPos.y + lineHeight - Bounds().Height()); 3654 fCaretOffset = OffsetAt(nextPos); 3655 nextPos = PointAt(fCaretOffset); 3656 _ScrollBy(0, nextPos.y - currentPos.y); 3657 3658 if (!fEditable && !fSelectable) 3659 break; 3660 3661 if (!shiftKeyDown) 3662 selStart = selEnd = fCaretOffset; 3663 else if (fCaretOffset != lastClickOffset) { 3664 if (fCaretOffset < fSelStart) { 3665 // extend selection to the top 3666 selStart = fCaretOffset; 3667 if (lastClickOffset > fSelStart) { 3668 // caret has jumped across "anchor" 3669 selEnd = fSelStart; 3670 } 3671 } else { 3672 // shrink selection from the bottom 3673 selEnd = fCaretOffset; 3674 } 3675 } 3676 3677 break; 3678 } 3679 3680 case B_PAGE_DOWN: 3681 { 3682 BPoint currentPos = PointAt(fCaretOffset); 3683 BPoint nextPos(currentPos.x, currentPos.y + Bounds().Height()); 3684 fCaretOffset = OffsetAt(nextPos); 3685 nextPos = PointAt(fCaretOffset); 3686 _ScrollBy(0, nextPos.y - currentPos.y); 3687 3688 if (!fEditable && !fSelectable) 3689 break; 3690 3691 if (!shiftKeyDown) 3692 selStart = selEnd = fCaretOffset; 3693 else if (fCaretOffset != lastClickOffset) { 3694 if (fCaretOffset > fSelEnd) { 3695 // extend selection to the bottom 3696 selEnd = fCaretOffset; 3697 if (lastClickOffset < fSelEnd) { 3698 // caret has jumped across "anchor" 3699 selStart = fSelEnd; 3700 } 3701 } else { 3702 // shrink selection from the top 3703 selStart = fCaretOffset; 3704 } 3705 } 3706 3707 break; 3708 } 3709 } 3710 3711 if (fEditable || fSelectable) { 3712 if (shiftKeyDown) 3713 Select(selStart, selEnd); 3714 else 3715 Select(fCaretOffset, fCaretOffset); 3716 3717 ScrollToOffset(fCaretOffset); 3718 } 3719 } 3720 3721 3722 /*! Handles when an alpha-numeric key is pressed. 3723 3724 \param bytes The string or character associated with the key. 3725 \param numBytes The amount of bytes containes in "bytes". 3726 */ 3727 void 3728 BTextView::_HandleAlphaKey(const char* bytes, int32 numBytes) 3729 { 3730 if (!fEditable) 3731 return; 3732 3733 if (fUndo) { 3734 TypingUndoBuffer* undoBuffer = dynamic_cast<TypingUndoBuffer*>(fUndo); 3735 if (!undoBuffer) { 3736 delete fUndo; 3737 fUndo = undoBuffer = new TypingUndoBuffer(this); 3738 } 3739 undoBuffer->InputCharacter(numBytes); 3740 } 3741 3742 if (fSelStart != fSelEnd) { 3743 Highlight(fSelStart, fSelEnd); 3744 DeleteText(fSelStart, fSelEnd); 3745 } 3746 3747 // we may draw twice, so turn updates off for now 3748 if (Window() != NULL) 3749 Window()->DisableUpdates(); 3750 3751 if (fAutoindent && numBytes == 1 && *bytes == B_ENTER) { 3752 int32 start, offset; 3753 start = offset = OffsetAt(_LineAt(fSelStart)); 3754 3755 while (ByteAt(offset) != '\0' && 3756 (ByteAt(offset) == B_TAB || ByteAt(offset) == B_SPACE) 3757 && offset < fSelStart) 3758 offset++; 3759 3760 _DoInsertText(bytes, numBytes, fSelStart, NULL); 3761 if (start != offset) 3762 _DoInsertText(Text() + start, offset - start, fSelStart, NULL); 3763 } else 3764 _DoInsertText(bytes, numBytes, fSelStart, NULL); 3765 3766 fCaretOffset = fSelEnd; 3767 ScrollToOffset(fCaretOffset); 3768 3769 // turn updates back on 3770 if (Window() != NULL) 3771 Window()->EnableUpdates(); 3772 } 3773 3774 3775 /*! Redraw the text between the two given offsets, recalculating line-breaks 3776 if needed. 3777 3778 \param fromOffset The offset from where to refresh. 3779 \param toOffset The offset where to refresh to. 3780 \param scrollTo Scroll the view to \a scrollTo offset if not \c INT32_MIN. 3781 */ 3782 void 3783 BTextView::_Refresh(int32 fromOffset, int32 toOffset, int32 scrollTo) 3784 { 3785 // TODO: Cleanup 3786 float saveHeight = fTextRect.Height(); 3787 float saveWidth = fTextRect.Width(); 3788 int32 fromLine = _LineAt(fromOffset); 3789 int32 toLine = _LineAt(toOffset); 3790 int32 saveFromLine = fromLine; 3791 int32 saveToLine = toLine; 3792 3793 _RecalculateLineBreaks(&fromLine, &toLine); 3794 3795 // TODO: Maybe there is still something we can do without a window... 3796 if (!Window()) 3797 return; 3798 3799 BRect bounds = Bounds(); 3800 float newHeight = fTextRect.Height(); 3801 3802 // if the line breaks have changed, force an erase 3803 if (fromLine != saveFromLine || toLine != saveToLine 3804 || newHeight != saveHeight) { 3805 fromOffset = -1; 3806 } 3807 3808 if (newHeight != saveHeight) { 3809 // the text area has changed 3810 if (newHeight < saveHeight) 3811 toLine = _LineAt(BPoint(0.0f, saveHeight + fTextRect.top)); 3812 else 3813 toLine = _LineAt(BPoint(0.0f, newHeight + fTextRect.top)); 3814 } 3815 3816 // draw only those lines that are visible 3817 int32 fromVisible = _LineAt(BPoint(0.0f, bounds.top)); 3818 int32 toVisible = _LineAt(BPoint(0.0f, bounds.bottom)); 3819 fromLine = std::max(fromVisible, fromLine); 3820 toLine = std::min(toLine, toVisible); 3821 3822 _AutoResize(false); 3823 3824 _RequestDrawLines(fromLine, toLine); 3825 3826 // erase the area below the text 3827 BRect eraseRect = bounds; 3828 eraseRect.top = fTextRect.top + (*fLines)[fLines->NumLines()]->origin; 3829 eraseRect.bottom = fTextRect.top + saveHeight; 3830 if (eraseRect.bottom > eraseRect.top && eraseRect.Intersects(bounds)) { 3831 SetLowColor(ViewColor()); 3832 FillRect(eraseRect, B_SOLID_LOW); 3833 } 3834 3835 // update the scroll bars if the text area has changed 3836 if (newHeight != saveHeight || fMinTextRectWidth != saveWidth) 3837 _UpdateScrollbars(); 3838 3839 if (scrollTo != INT32_MIN) 3840 ScrollToOffset(scrollTo); 3841 3842 Flush(); 3843 } 3844 3845 3846 /*! Recalculate line breaks between two lines. 3847 3848 \param startLine The line number to start recalculating line breaks. 3849 \param endLine The line number to stop recalculating line breaks. 3850 */ 3851 void 3852 BTextView::_RecalculateLineBreaks(int32* startLine, int32* endLine) 3853 { 3854 CALLED(); 3855 3856 float width = fTextRect.Width(); 3857 3858 // don't try to compute anything if the text rect is not set 3859 if (!fTextRect.IsValid() || width == 0) 3860 return; 3861 3862 // sanity check 3863 *startLine = (*startLine < 0) ? 0 : *startLine; 3864 *endLine = (*endLine > fLines->NumLines() - 1) ? fLines->NumLines() - 1 3865 : *endLine; 3866 3867 int32 textLength = fText->Length(); 3868 int32 lineIndex = (*startLine > 0) ? *startLine - 1 : 0; 3869 int32 recalThreshold = (*fLines)[*endLine + 1]->offset; 3870 STELine* curLine = (*fLines)[lineIndex]; 3871 STELine* nextLine = curLine + 1; 3872 3873 do { 3874 float ascent, descent; 3875 int32 fromOffset = curLine->offset; 3876 int32 toOffset = _FindLineBreak(fromOffset, &ascent, &descent, &width); 3877 3878 curLine->ascent = ascent; 3879 curLine->width = width; 3880 3881 // we want to advance at least by one character 3882 int32 nextOffset = _NextInitialByte(fromOffset); 3883 if (toOffset < nextOffset && fromOffset < textLength) 3884 toOffset = nextOffset; 3885 3886 lineIndex++; 3887 STELine saveLine = *nextLine; 3888 if (lineIndex > fLines->NumLines() || toOffset < nextLine->offset) { 3889 // the new line comes before the old line start, add a line 3890 STELine newLine; 3891 newLine.offset = toOffset; 3892 newLine.origin = ceilf(curLine->origin + ascent + descent) + 1; 3893 newLine.ascent = 0; 3894 fLines->InsertLine(&newLine, lineIndex); 3895 } else { 3896 // update the existing line 3897 nextLine->offset = toOffset; 3898 nextLine->origin = ceilf(curLine->origin + ascent + descent) + 1; 3899 3900 // remove any lines that start before the current line 3901 while (lineIndex < fLines->NumLines() 3902 && toOffset >= ((*fLines)[lineIndex] + 1)->offset) { 3903 fLines->RemoveLines(lineIndex + 1); 3904 } 3905 3906 nextLine = (*fLines)[lineIndex]; 3907 if (nextLine->offset == saveLine.offset) { 3908 if (nextLine->offset >= recalThreshold) { 3909 if (nextLine->origin != saveLine.origin) 3910 fLines->BumpOrigin(nextLine->origin - saveLine.origin, 3911 lineIndex + 1); 3912 break; 3913 } 3914 } else { 3915 if (lineIndex > 0 && lineIndex == *startLine) 3916 *startLine = lineIndex - 1; 3917 } 3918 } 3919 3920 curLine = (*fLines)[lineIndex]; 3921 nextLine = curLine + 1; 3922 } while (curLine->offset < textLength); 3923 3924 // make sure that the sentinel line (which starts at the end of the buffer) 3925 // has always a width of 0 3926 (*fLines)[fLines->NumLines()]->width = 0; 3927 3928 // update text rect 3929 fTextRect.left = Bounds().left + fLayoutData->leftInset; 3930 fTextRect.right = Bounds().right - fLayoutData->rightInset; 3931 3932 // always set text rect bottom 3933 float newHeight = TextHeight(0, fLines->NumLines() - 1); 3934 fTextRect.bottom = fTextRect.top + newHeight; 3935 3936 if (!fWrap) { 3937 fMinTextRectWidth = fLines->MaxWidth() - 1; 3938 3939 // expand width if needed 3940 switch (fAlignment) { 3941 default: 3942 case B_ALIGN_LEFT: 3943 // move right edge 3944 fTextRect.right = fTextRect.left + fMinTextRectWidth; 3945 break; 3946 3947 case B_ALIGN_RIGHT: 3948 // move left edge 3949 fTextRect.left = fTextRect.right - fMinTextRectWidth; 3950 break; 3951 3952 case B_ALIGN_CENTER: 3953 // move both edges 3954 fTextRect.InsetBy(roundf((fTextRect.Width() 3955 - fMinTextRectWidth) / 2), 0); 3956 break; 3957 } 3958 3959 _ValidateTextRect(); 3960 } 3961 3962 *endLine = lineIndex - 1; 3963 *startLine = std::min(*startLine, *endLine); 3964 } 3965 3966 3967 void 3968 BTextView::_ValidateTextRect() 3969 { 3970 // text rect right must be greater than left 3971 if (fTextRect.right <= fTextRect.left) 3972 fTextRect.right = fTextRect.left + 1; 3973 // text rect bottom must be greater than top 3974 if (fTextRect.bottom <= fTextRect.top) 3975 fTextRect.bottom = fTextRect.top + 1; 3976 } 3977 3978 3979 int32 3980 BTextView::_FindLineBreak(int32 fromOffset, float* _ascent, float* _descent, 3981 float* inOutWidth) 3982 { 3983 *_ascent = 0.0; 3984 *_descent = 0.0; 3985 3986 const int32 limit = fText->Length(); 3987 3988 // is fromOffset at the end? 3989 if (fromOffset >= limit) { 3990 // try to return valid height info anyway 3991 if (fStyles->NumRuns() > 0) { 3992 fStyles->Iterate(fromOffset, 1, fInline, NULL, NULL, _ascent, 3993 _descent); 3994 } else { 3995 if (fStyles->IsValidNullStyle()) { 3996 const BFont* font = NULL; 3997 fStyles->GetNullStyle(&font, NULL); 3998 3999 font_height fh; 4000 font->GetHeight(&fh); 4001 *_ascent = fh.ascent; 4002 *_descent = fh.descent + fh.leading; 4003 } 4004 } 4005 *inOutWidth = 0; 4006 4007 return limit; 4008 } 4009 4010 int32 offset = fromOffset; 4011 4012 if (!fWrap) { 4013 // Text wrapping is turned off. 4014 // Just find the offset of the first \n character 4015 offset = limit - fromOffset; 4016 fText->FindChar(B_ENTER, fromOffset, &offset); 4017 offset += fromOffset; 4018 int32 toOffset = (offset < limit) ? offset : limit; 4019 4020 *inOutWidth = _TabExpandedStyledWidth(fromOffset, toOffset - fromOffset, 4021 _ascent, _descent); 4022 4023 return offset < limit ? offset + 1 : limit; 4024 } 4025 4026 bool done = false; 4027 float ascent = 0.0; 4028 float descent = 0.0; 4029 int32 delta = 0; 4030 float deltaWidth = 0.0; 4031 float strWidth = 0.0; 4032 uchar theChar; 4033 4034 // wrap the text 4035 while (offset < limit && !done) { 4036 // find the next line break candidate 4037 for (; (offset + delta) < limit; delta++) { 4038 if (CanEndLine(offset + delta)) { 4039 theChar = fText->RealCharAt(offset + delta); 4040 if (theChar != B_SPACE && theChar != B_TAB 4041 && theChar != B_ENTER) { 4042 // we are scanning for trailing whitespace below, so we 4043 // have to skip non-whitespace characters, that can end 4044 // the line, here 4045 delta++; 4046 } 4047 break; 4048 } 4049 } 4050 4051 int32 deltaBeforeWhitespace = delta; 4052 // now skip over trailing whitespace, if any 4053 for (; (offset + delta) < limit; delta++) { 4054 theChar = fText->RealCharAt(offset + delta); 4055 if (theChar == B_ENTER) { 4056 // found a newline, we're done! 4057 done = true; 4058 delta++; 4059 break; 4060 } else if (theChar != B_SPACE && theChar != B_TAB) { 4061 // stop at anything else than trailing whitespace 4062 break; 4063 } 4064 } 4065 4066 delta = std::max(delta, (int32)1); 4067 4068 // do not include B_ENTER-terminator into width & height calculations 4069 deltaWidth = _TabExpandedStyledWidth(offset, 4070 done ? delta - 1 : delta, &ascent, &descent); 4071 strWidth += deltaWidth; 4072 4073 if (strWidth >= *inOutWidth) { 4074 // we've found where the line will wrap 4075 done = true; 4076 4077 // we have included trailing whitespace in the width computation 4078 // above, but that is not being shown anyway, so we try again 4079 // without the trailing whitespace 4080 if (delta == deltaBeforeWhitespace) { 4081 // there is no trailing whitespace, no point in trying 4082 break; 4083 } 4084 4085 // reset string width to start of current run ... 4086 strWidth -= deltaWidth; 4087 4088 // ... and compute the resulting width (of visible characters) 4089 strWidth += _StyledWidth(offset, deltaBeforeWhitespace, NULL, NULL); 4090 if (strWidth >= *inOutWidth) { 4091 // width of visible characters exceeds line, we need to wrap 4092 // before the current "word" 4093 break; 4094 } 4095 } 4096 4097 *_ascent = std::max(ascent, *_ascent); 4098 *_descent = std::max(descent, *_descent); 4099 4100 offset += delta; 4101 delta = 0; 4102 } 4103 4104 if (offset - fromOffset < 1) { 4105 // there weren't any words that fit entirely in this line 4106 // force a break in the middle of a word 4107 *_ascent = 0.0; 4108 *_descent = 0.0; 4109 strWidth = 0.0; 4110 4111 int32 current = fromOffset; 4112 for (offset = _NextInitialByte(current); current < limit; 4113 current = offset, offset = _NextInitialByte(offset)) { 4114 strWidth += _StyledWidth(current, offset - current, &ascent, 4115 &descent); 4116 if (strWidth >= *inOutWidth) { 4117 offset = _PreviousInitialByte(offset); 4118 break; 4119 } 4120 4121 *_ascent = std::max(ascent, *_ascent); 4122 *_descent = std::max(descent, *_descent); 4123 } 4124 } 4125 4126 return std::min(offset, limit); 4127 } 4128 4129 4130 int32 4131 BTextView::_PreviousLineStart(int32 offset) 4132 { 4133 if (offset <= 0) 4134 return 0; 4135 4136 while (offset > 0) { 4137 offset = _PreviousInitialByte(offset); 4138 if (_CharClassification(offset) == CHAR_CLASS_WHITESPACE 4139 && ByteAt(offset) == B_ENTER) { 4140 return offset + 1; 4141 } 4142 } 4143 4144 return offset; 4145 } 4146 4147 4148 int32 4149 BTextView::_NextLineEnd(int32 offset) 4150 { 4151 int32 textLen = fText->Length(); 4152 if (offset >= textLen) 4153 return textLen; 4154 4155 while (offset < textLen) { 4156 if (_CharClassification(offset) == CHAR_CLASS_WHITESPACE 4157 && ByteAt(offset) == B_ENTER) { 4158 break; 4159 } 4160 offset = _NextInitialByte(offset); 4161 } 4162 4163 return offset; 4164 } 4165 4166 4167 int32 4168 BTextView::_PreviousWordBoundary(int32 offset) 4169 { 4170 uint32 charType = _CharClassification(offset); 4171 int32 previous; 4172 while (offset > 0) { 4173 previous = _PreviousInitialByte(offset); 4174 if (_CharClassification(previous) != charType) 4175 break; 4176 offset = previous; 4177 } 4178 4179 return offset; 4180 } 4181 4182 4183 int32 4184 BTextView::_NextWordBoundary(int32 offset) 4185 { 4186 int32 textLen = fText->Length(); 4187 uint32 charType = _CharClassification(offset); 4188 while (offset < textLen) { 4189 offset = _NextInitialByte(offset); 4190 if (_CharClassification(offset) != charType) 4191 break; 4192 } 4193 4194 return offset; 4195 } 4196 4197 4198 int32 4199 BTextView::_PreviousWordStart(int32 offset) 4200 { 4201 if (offset <= 1) 4202 return 0; 4203 4204 --offset; 4205 // need to look at previous char 4206 if (_CharClassification(offset) != CHAR_CLASS_DEFAULT) { 4207 // skip non-word characters 4208 while (offset > 0) { 4209 offset = _PreviousInitialByte(offset); 4210 if (_CharClassification(offset) == CHAR_CLASS_DEFAULT) 4211 break; 4212 } 4213 } 4214 while (offset > 0) { 4215 // skip to start of word 4216 int32 previous = _PreviousInitialByte(offset); 4217 if (_CharClassification(previous) != CHAR_CLASS_DEFAULT) 4218 break; 4219 offset = previous; 4220 } 4221 4222 return offset; 4223 } 4224 4225 4226 int32 4227 BTextView::_NextWordEnd(int32 offset) 4228 { 4229 int32 textLen = fText->Length(); 4230 if (_CharClassification(offset) != CHAR_CLASS_DEFAULT) { 4231 // skip non-word characters 4232 while (offset < textLen) { 4233 offset = _NextInitialByte(offset); 4234 if (_CharClassification(offset) == CHAR_CLASS_DEFAULT) 4235 break; 4236 } 4237 } 4238 while (offset < textLen) { 4239 // skip to end of word 4240 offset = _NextInitialByte(offset); 4241 if (_CharClassification(offset) != CHAR_CLASS_DEFAULT) 4242 break; 4243 } 4244 4245 return offset; 4246 } 4247 4248 4249 /*! Returns the width used by the characters starting at the given 4250 offset with the given length, expanding all tab characters as needed. 4251 */ 4252 float 4253 BTextView::_TabExpandedStyledWidth(int32 offset, int32 length, float* _ascent, 4254 float* _descent) const 4255 { 4256 float ascent = 0.0; 4257 float descent = 0.0; 4258 float maxAscent = 0.0; 4259 float maxDescent = 0.0; 4260 4261 float width = 0.0; 4262 int32 numBytes = length; 4263 bool foundTab = false; 4264 do { 4265 foundTab = fText->FindChar(B_TAB, offset, &numBytes); 4266 width += _StyledWidth(offset, numBytes, &ascent, &descent); 4267 4268 if (maxAscent < ascent) 4269 maxAscent = ascent; 4270 if (maxDescent < descent) 4271 maxDescent = descent; 4272 4273 if (foundTab) { 4274 width += _ActualTabWidth(width); 4275 numBytes++; 4276 } 4277 4278 offset += numBytes; 4279 length -= numBytes; 4280 numBytes = length; 4281 } while (foundTab && length > 0); 4282 4283 if (_ascent != NULL) 4284 *_ascent = maxAscent; 4285 if (_descent != NULL) 4286 *_descent = maxDescent; 4287 4288 return width; 4289 } 4290 4291 4292 /*! Calculate the width of the text within the given limits. 4293 4294 \param fromOffset The offset where to start. 4295 \param length The length of the text to examine. 4296 \param _ascent A pointer to a float which will contain the maximum ascent. 4297 \param _descent A pointer to a float which will contain the maximum descent. 4298 4299 \return The width for the text within the given limits. 4300 */ 4301 float 4302 BTextView::_StyledWidth(int32 fromOffset, int32 length, float* _ascent, 4303 float* _descent) const 4304 { 4305 if (length == 0) { 4306 // determine height of char at given offset, but return empty width 4307 fStyles->Iterate(fromOffset, 1, fInline, NULL, NULL, _ascent, 4308 _descent); 4309 return 0.0; 4310 } 4311 4312 float result = 0.0; 4313 float ascent = 0.0; 4314 float descent = 0.0; 4315 float maxAscent = 0.0; 4316 float maxDescent = 0.0; 4317 4318 // iterate through the style runs 4319 const BFont* font = NULL; 4320 int32 numBytes; 4321 while ((numBytes = fStyles->Iterate(fromOffset, length, fInline, &font, 4322 NULL, &ascent, &descent)) != 0) { 4323 maxAscent = std::max(ascent, maxAscent); 4324 maxDescent = std::max(descent, maxDescent); 4325 4326 #if USE_WIDTHBUFFER 4327 // Use _BWidthBuffer_ if possible 4328 if (BPrivate::gWidthBuffer != NULL) { 4329 result += BPrivate::gWidthBuffer->StringWidth(*fText, fromOffset, 4330 numBytes, font); 4331 } else { 4332 #endif 4333 const char* text = fText->GetString(fromOffset, &numBytes); 4334 result += font->StringWidth(text, numBytes); 4335 4336 #if USE_WIDTHBUFFER 4337 } 4338 #endif 4339 4340 fromOffset += numBytes; 4341 length -= numBytes; 4342 } 4343 4344 if (_ascent != NULL) 4345 *_ascent = maxAscent; 4346 if (_descent != NULL) 4347 *_descent = maxDescent; 4348 4349 return result; 4350 } 4351 4352 4353 //! Calculate the actual tab width for the given location. 4354 float 4355 BTextView::_ActualTabWidth(float location) const 4356 { 4357 float tabWidth = fTabWidth - fmod(location, fTabWidth); 4358 if (round(tabWidth) == 0) 4359 tabWidth = fTabWidth; 4360 4361 return tabWidth; 4362 } 4363 4364 4365 void 4366 BTextView::_DoInsertText(const char* text, int32 length, int32 offset, 4367 const text_run_array* runs) 4368 { 4369 _CancelInputMethod(); 4370 4371 if (fText->Length() + length > MaxBytes()) 4372 return; 4373 4374 if (fSelStart != fSelEnd) 4375 Select(fSelStart, fSelStart); 4376 4377 const int32 textLength = fText->Length(); 4378 if (offset > textLength) 4379 offset = textLength; 4380 4381 // copy data into buffer 4382 InsertText(text, length, offset, runs); 4383 4384 // recalc line breaks and draw the text 4385 _Refresh(offset, offset + length); 4386 } 4387 4388 4389 void 4390 BTextView::_DoDeleteText(int32 fromOffset, int32 toOffset) 4391 { 4392 CALLED(); 4393 } 4394 4395 4396 void 4397 BTextView::_DrawLine(BView* view, const int32 &lineNum, 4398 const int32 &startOffset, const bool &erase, BRect &eraseRect, 4399 BRegion &inputRegion) 4400 { 4401 STELine* line = (*fLines)[lineNum]; 4402 float startLeft = fTextRect.left; 4403 if (startOffset != -1) { 4404 if (ByteAt(startOffset) == B_ENTER) { 4405 // StartOffset is a newline 4406 startLeft = PointAt(line->offset).x; 4407 } else 4408 startLeft = PointAt(startOffset).x; 4409 } else if (fAlignment != B_ALIGN_LEFT) { 4410 float alignmentOffset = fTextRect.Width() + 1 - LineWidth(lineNum); 4411 if (fAlignment == B_ALIGN_CENTER) 4412 alignmentOffset = floorf(alignmentOffset / 2); 4413 startLeft += alignmentOffset; 4414 } 4415 4416 int32 length = (line + 1)->offset; 4417 if (startOffset != -1) 4418 length -= startOffset; 4419 else 4420 length -= line->offset; 4421 4422 // DrawString() chokes if you draw a newline 4423 if (ByteAt((line + 1)->offset - 1) == B_ENTER) 4424 length--; 4425 4426 view->MovePenTo(startLeft, 4427 line->origin + line->ascent + fTextRect.top + 1); 4428 4429 if (erase) { 4430 eraseRect.top = line->origin + fTextRect.top; 4431 eraseRect.bottom = (line + 1)->origin + fTextRect.top; 4432 view->FillRect(eraseRect, B_SOLID_LOW); 4433 } 4434 4435 // do we have any text to draw? 4436 if (length <= 0) 4437 return; 4438 4439 bool foundTab = false; 4440 int32 tabChars = 0; 4441 int32 numTabs = 0; 4442 int32 offset = startOffset != -1 ? startOffset : line->offset; 4443 const BFont* font = NULL; 4444 const rgb_color* color = NULL; 4445 int32 numBytes; 4446 drawing_mode defaultTextRenderingMode = DrawingMode(); 4447 // iterate through each style on this line 4448 while ((numBytes = fStyles->Iterate(offset, length, fInline, &font, 4449 &color)) != 0) { 4450 view->SetFont(font); 4451 view->SetHighColor(*color); 4452 4453 tabChars = std::min(numBytes, length); 4454 do { 4455 foundTab = fText->FindChar(B_TAB, offset, &tabChars); 4456 if (foundTab) { 4457 do { 4458 numTabs++; 4459 if (ByteAt(offset + tabChars + numTabs) != B_TAB) 4460 break; 4461 } while ((tabChars + numTabs) < numBytes); 4462 } 4463 4464 drawing_mode textRenderingMode = defaultTextRenderingMode; 4465 4466 if (inputRegion.CountRects() > 0 4467 && ((offset <= fInline->Offset() 4468 && fInline->Offset() < offset + tabChars) 4469 || (fInline->Offset() <= offset 4470 && offset < fInline->Offset() + fInline->Length()))) { 4471 4472 textRenderingMode = B_OP_OVER; 4473 4474 BRegion textRegion; 4475 GetTextRegion(offset, offset + length, &textRegion); 4476 4477 textRegion.IntersectWith(&inputRegion); 4478 view->PushState(); 4479 4480 // Highlight in blue the inputted text 4481 view->SetHighColor(kBlueInputColor); 4482 view->FillRect(textRegion.Frame()); 4483 4484 // Highlight in red the selected part 4485 if (fInline->SelectionLength() > 0) { 4486 BRegion selectedRegion; 4487 GetTextRegion(fInline->Offset() 4488 + fInline->SelectionOffset(), fInline->Offset() 4489 + fInline->SelectionOffset() 4490 + fInline->SelectionLength(), &selectedRegion); 4491 4492 textRegion.IntersectWith(&selectedRegion); 4493 4494 view->SetHighColor(kRedInputColor); 4495 view->FillRect(textRegion.Frame()); 4496 } 4497 4498 view->PopState(); 4499 } 4500 4501 int32 size = tabChars; 4502 const char* stringToDraw = fText->GetString(offset, &size); 4503 view->SetDrawingMode(textRenderingMode); 4504 view->DrawString(stringToDraw, size); 4505 4506 if (foundTab) { 4507 float penPos = PenLocation().x - fTextRect.left; 4508 switch (fAlignment) { 4509 default: 4510 case B_ALIGN_LEFT: 4511 // nothing more to do 4512 break; 4513 4514 case B_ALIGN_RIGHT: 4515 // subtract distance from left to line 4516 penPos -= fTextRect.Width() - LineWidth(lineNum); 4517 break; 4518 4519 case B_ALIGN_CENTER: 4520 // subtract half distance from left to line 4521 penPos -= floorf((fTextRect.Width() + 1 4522 - LineWidth(lineNum)) / 2); 4523 break; 4524 } 4525 float tabWidth = _ActualTabWidth(penPos); 4526 4527 // add in the rest of the tabs (if there are any) 4528 tabWidth += ((numTabs - 1) * fTabWidth); 4529 4530 // move pen by tab(s) width 4531 view->MovePenBy(tabWidth, 0.0); 4532 tabChars += numTabs; 4533 } 4534 4535 offset += tabChars; 4536 length -= tabChars; 4537 numBytes -= tabChars; 4538 tabChars = std::min(numBytes, length); 4539 numTabs = 0; 4540 } while (foundTab && tabChars > 0); 4541 } 4542 } 4543 4544 4545 void 4546 BTextView::_DrawLines(int32 startLine, int32 endLine, int32 startOffset, 4547 bool erase) 4548 { 4549 if (!Window()) 4550 return; 4551 4552 const BRect bounds(Bounds()); 4553 4554 // clip the text extending to end of selection 4555 BRect clipRect(fTextRect); 4556 clipRect.left = std::min(fTextRect.left, 4557 bounds.left + fLayoutData->leftInset); 4558 clipRect.right = std::max(fTextRect.right, 4559 bounds.right - fLayoutData->rightInset); 4560 clipRect = bounds & clipRect; 4561 4562 BRegion newClip; 4563 newClip.Set(clipRect); 4564 ConstrainClippingRegion(&newClip); 4565 4566 // set the low color to the view color so that 4567 // drawing to a non-white background will work 4568 SetLowColor(ViewColor()); 4569 4570 BView* view = NULL; 4571 if (fOffscreen == NULL) 4572 view = this; 4573 else { 4574 fOffscreen->Lock(); 4575 view = fOffscreen->ChildAt(0); 4576 view->SetLowColor(ViewColor()); 4577 view->FillRect(view->Bounds(), B_SOLID_LOW); 4578 } 4579 4580 long maxLine = fLines->NumLines() - 1; 4581 if (startLine < 0) 4582 startLine = 0; 4583 if (endLine > maxLine) 4584 endLine = maxLine; 4585 4586 // TODO: See if we can avoid this 4587 if (fAlignment != B_ALIGN_LEFT) 4588 erase = true; 4589 4590 BRect eraseRect = clipRect; 4591 int32 startEraseLine = startLine; 4592 STELine* line = (*fLines)[startLine]; 4593 4594 if (erase && startOffset != -1 && fAlignment == B_ALIGN_LEFT) { 4595 // erase only to the right of startOffset 4596 startEraseLine++; 4597 int32 startErase = startOffset; 4598 4599 BPoint erasePoint = PointAt(startErase); 4600 eraseRect.left = erasePoint.x; 4601 eraseRect.top = erasePoint.y; 4602 eraseRect.bottom = (line + 1)->origin + fTextRect.top; 4603 4604 view->FillRect(eraseRect, B_SOLID_LOW); 4605 4606 eraseRect = clipRect; 4607 } 4608 4609 BRegion inputRegion; 4610 if (fInline != NULL && fInline->IsActive()) { 4611 GetTextRegion(fInline->Offset(), fInline->Offset() + fInline->Length(), 4612 &inputRegion); 4613 } 4614 4615 //BPoint leftTop(startLeft, line->origin); 4616 for (int32 lineNum = startLine; lineNum <= endLine; lineNum++) { 4617 const bool eraseThisLine = erase && lineNum >= startEraseLine; 4618 _DrawLine(view, lineNum, startOffset, eraseThisLine, eraseRect, 4619 inputRegion); 4620 startOffset = -1; 4621 // Set this to -1 so the next iteration will use the line offset 4622 } 4623 4624 // draw the caret/hilite the selection 4625 if (fActive) { 4626 if (fSelStart != fSelEnd) { 4627 if (fSelectable) 4628 Highlight(fSelStart, fSelEnd); 4629 } else { 4630 if (fCaretVisible) 4631 _DrawCaret(fSelStart, true); 4632 } 4633 } 4634 4635 if (fOffscreen != NULL) { 4636 view->Sync(); 4637 /*BPoint penLocation = view->PenLocation(); 4638 BRect drawRect(leftTop.x, leftTop.y, penLocation.x, penLocation.y); 4639 DrawBitmap(fOffscreen, drawRect, drawRect);*/ 4640 fOffscreen->Unlock(); 4641 } 4642 4643 ConstrainClippingRegion(NULL); 4644 } 4645 4646 4647 void 4648 BTextView::_RequestDrawLines(int32 startLine, int32 endLine) 4649 { 4650 if (!Window()) 4651 return; 4652 4653 long maxLine = fLines->NumLines() - 1; 4654 4655 STELine* from = (*fLines)[startLine]; 4656 STELine* to = endLine == maxLine ? NULL : (*fLines)[endLine + 1]; 4657 BRect invalidRect(Bounds().left, from->origin + fTextRect.top, 4658 Bounds().right, 4659 to != NULL ? to->origin + fTextRect.top : fTextRect.bottom); 4660 Invalidate(invalidRect); 4661 } 4662 4663 4664 void 4665 BTextView::_DrawCaret(int32 offset, bool visible) 4666 { 4667 float lineHeight; 4668 BPoint caretPoint = PointAt(offset, &lineHeight); 4669 caretPoint.x = std::min(caretPoint.x, fTextRect.right); 4670 4671 BRect caretRect; 4672 caretRect.left = caretRect.right = caretPoint.x; 4673 caretRect.top = caretPoint.y; 4674 caretRect.bottom = caretPoint.y + lineHeight - 1; 4675 4676 if (visible) 4677 InvertRect(caretRect); 4678 else 4679 Invalidate(caretRect); 4680 } 4681 4682 4683 inline void 4684 BTextView::_ShowCaret() 4685 { 4686 if (fActive && !fCaretVisible && fEditable && fSelStart == fSelEnd) 4687 _InvertCaret(); 4688 } 4689 4690 4691 inline void 4692 BTextView::_HideCaret() 4693 { 4694 if (fCaretVisible && fSelStart == fSelEnd) 4695 _InvertCaret(); 4696 } 4697 4698 4699 //! Hides the caret if it is being shown, and if it's hidden, shows it. 4700 void 4701 BTextView::_InvertCaret() 4702 { 4703 fCaretVisible = !fCaretVisible; 4704 _DrawCaret(fSelStart, fCaretVisible); 4705 fCaretTime = system_time(); 4706 } 4707 4708 4709 /*! Place the dragging caret at the given offset. 4710 4711 \param offset The offset (zero based within the object's text) where to 4712 place the dragging caret. If it's -1, hide the caret. 4713 */ 4714 void 4715 BTextView::_DragCaret(int32 offset) 4716 { 4717 // does the caret need to move? 4718 if (offset == fDragOffset) 4719 return; 4720 4721 // hide the previous drag caret 4722 if (fDragOffset != -1) 4723 _DrawCaret(fDragOffset, false); 4724 4725 // do we have a new location? 4726 if (offset != -1) { 4727 if (fActive) { 4728 // ignore if offset is within active selection 4729 if (offset >= fSelStart && offset <= fSelEnd) { 4730 fDragOffset = -1; 4731 return; 4732 } 4733 } 4734 4735 _DrawCaret(offset, true); 4736 } 4737 4738 fDragOffset = offset; 4739 } 4740 4741 4742 void 4743 BTextView::_StopMouseTracking() 4744 { 4745 delete fTrackingMouse; 4746 fTrackingMouse = NULL; 4747 } 4748 4749 4750 bool 4751 BTextView::_PerformMouseUp(BPoint where) 4752 { 4753 if (fTrackingMouse == NULL) 4754 return false; 4755 4756 if (fTrackingMouse->selectionRect.IsValid()) 4757 Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset); 4758 4759 _StopMouseTracking(); 4760 // adjust cursor if necessary 4761 _TrackMouse(where, NULL, true); 4762 4763 return true; 4764 } 4765 4766 4767 bool 4768 BTextView::_PerformMouseMoved(BPoint where, uint32 code) 4769 { 4770 fWhere = where; 4771 4772 if (fTrackingMouse == NULL) 4773 return false; 4774 4775 int32 currentOffset = OffsetAt(where); 4776 if (fTrackingMouse->selectionRect.IsValid()) { 4777 // we are tracking the mouse for drag action, if the mouse has moved 4778 // to another index or more than three pixels from where it was clicked, 4779 // we initiate a drag now: 4780 if (currentOffset != fTrackingMouse->clickOffset 4781 || fabs(fTrackingMouse->where.x - where.x) > 3 4782 || fabs(fTrackingMouse->where.y - where.y) > 3) { 4783 _StopMouseTracking(); 4784 _InitiateDrag(); 4785 return true; 4786 } 4787 return false; 4788 } 4789 4790 switch (fClickCount) { 4791 case 3: 4792 // triple click, extend selection linewise 4793 if (currentOffset <= fTrackingMouse->anchor) { 4794 fTrackingMouse->selStart 4795 = (*fLines)[_LineAt(currentOffset)]->offset; 4796 fTrackingMouse->selEnd = fTrackingMouse->shiftDown 4797 ? fSelEnd 4798 : (*fLines)[_LineAt(fTrackingMouse->anchor) + 1]->offset; 4799 } else { 4800 fTrackingMouse->selStart 4801 = fTrackingMouse->shiftDown 4802 ? fSelStart 4803 : (*fLines)[_LineAt(fTrackingMouse->anchor)]->offset; 4804 fTrackingMouse->selEnd 4805 = (*fLines)[_LineAt(currentOffset) + 1]->offset; 4806 } 4807 break; 4808 4809 case 2: 4810 // double click, extend selection wordwise 4811 if (currentOffset <= fTrackingMouse->anchor) { 4812 fTrackingMouse->selStart = _PreviousWordBoundary(currentOffset); 4813 fTrackingMouse->selEnd 4814 = fTrackingMouse->shiftDown 4815 ? fSelEnd 4816 : _NextWordBoundary(fTrackingMouse->anchor); 4817 } else { 4818 fTrackingMouse->selStart 4819 = fTrackingMouse->shiftDown 4820 ? fSelStart 4821 : _PreviousWordBoundary(fTrackingMouse->anchor); 4822 fTrackingMouse->selEnd = _NextWordBoundary(currentOffset); 4823 } 4824 break; 4825 4826 default: 4827 // new click, extend selection char by char 4828 if (currentOffset <= fTrackingMouse->anchor) { 4829 fTrackingMouse->selStart = currentOffset; 4830 fTrackingMouse->selEnd 4831 = fTrackingMouse->shiftDown 4832 ? fSelEnd : fTrackingMouse->anchor; 4833 } else { 4834 fTrackingMouse->selStart 4835 = fTrackingMouse->shiftDown 4836 ? fSelStart : fTrackingMouse->anchor; 4837 fTrackingMouse->selEnd = currentOffset; 4838 } 4839 break; 4840 } 4841 4842 // position caret to follow the direction of the selection 4843 if (fTrackingMouse->selEnd != fSelEnd) 4844 fCaretOffset = fTrackingMouse->selEnd; 4845 else if (fTrackingMouse->selStart != fSelStart) 4846 fCaretOffset = fTrackingMouse->selStart; 4847 4848 Select(fTrackingMouse->selStart, fTrackingMouse->selEnd); 4849 _TrackMouse(where, NULL); 4850 4851 return true; 4852 } 4853 4854 4855 /*! Tracks the mouse position, doing special actions like changing the 4856 view cursor. 4857 4858 \param where The point where the mouse has moved. 4859 \param message The dragging message, if there is any. 4860 \param force Passed as second parameter of SetViewCursor() 4861 */ 4862 void 4863 BTextView::_TrackMouse(BPoint where, const BMessage* message, bool force) 4864 { 4865 BRegion textRegion; 4866 GetTextRegion(fSelStart, fSelEnd, &textRegion); 4867 4868 if (message && AcceptsDrop(message)) 4869 _TrackDrag(where); 4870 else if ((fSelectable || fEditable) 4871 && (fTrackingMouse != NULL || !textRegion.Contains(where))) { 4872 SetViewCursor(B_CURSOR_I_BEAM, force); 4873 } else 4874 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, force); 4875 } 4876 4877 4878 //! Tracks the mouse position when the user is dragging some data. 4879 void 4880 BTextView::_TrackDrag(BPoint where) 4881 { 4882 CALLED(); 4883 if (Bounds().Contains(where)) 4884 _DragCaret(OffsetAt(where)); 4885 } 4886 4887 4888 //! Initiates a drag operation. 4889 void 4890 BTextView::_InitiateDrag() 4891 { 4892 BMessage dragMessage(B_MIME_DATA); 4893 BBitmap* dragBitmap = NULL; 4894 BPoint bitmapPoint; 4895 BHandler* dragHandler = NULL; 4896 4897 GetDragParameters(&dragMessage, &dragBitmap, &bitmapPoint, &dragHandler); 4898 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 4899 4900 if (dragBitmap != NULL) 4901 DragMessage(&dragMessage, dragBitmap, bitmapPoint, dragHandler); 4902 else { 4903 BRegion region; 4904 GetTextRegion(fSelStart, fSelEnd, ®ion); 4905 BRect bounds = Bounds(); 4906 BRect dragRect = region.Frame(); 4907 if (!bounds.Contains(dragRect)) 4908 dragRect = bounds & dragRect; 4909 4910 DragMessage(&dragMessage, dragRect, dragHandler); 4911 } 4912 4913 BMessenger messenger(this); 4914 BMessage message(_DISPOSE_DRAG_); 4915 fDragRunner = new (nothrow) BMessageRunner(messenger, &message, 100000); 4916 } 4917 4918 4919 //! Handles when some data is dropped on the view. 4920 bool 4921 BTextView::_MessageDropped(BMessage* message, BPoint where, BPoint offset) 4922 { 4923 ASSERT(message); 4924 4925 void* from = NULL; 4926 bool internalDrop = false; 4927 if (message->FindPointer("be:originator", &from) == B_OK 4928 && from == this && fSelEnd != fSelStart) 4929 internalDrop = true; 4930 4931 _DragCaret(-1); 4932 4933 delete fDragRunner; 4934 fDragRunner = NULL; 4935 4936 _TrackMouse(where, NULL); 4937 4938 // are we sure we like this message? 4939 if (!AcceptsDrop(message)) 4940 return false; 4941 4942 int32 dropOffset = OffsetAt(where); 4943 if (dropOffset > fText->Length()) 4944 dropOffset = fText->Length(); 4945 4946 // if this view initiated the drag, move instead of copy 4947 if (internalDrop) { 4948 // dropping onto itself? 4949 if (dropOffset >= fSelStart && dropOffset <= fSelEnd) 4950 return true; 4951 } 4952 4953 ssize_t dataLength = 0; 4954 const char* text = NULL; 4955 entry_ref ref; 4956 if (message->FindData("text/plain", B_MIME_TYPE, (const void**)&text, 4957 &dataLength) == B_OK) { 4958 text_run_array* runArray = NULL; 4959 ssize_t runLength = 0; 4960 if (fStylable) { 4961 message->FindData("application/x-vnd.Be-text_run_array", 4962 B_MIME_TYPE, (const void**)&runArray, &runLength); 4963 } 4964 4965 _FilterDisallowedChars((char*)text, dataLength, runArray); 4966 4967 if (dataLength < 1) { 4968 beep(); 4969 return true; 4970 } 4971 4972 if (fUndo) { 4973 delete fUndo; 4974 fUndo = new DropUndoBuffer(this, text, dataLength, runArray, 4975 runLength, dropOffset, internalDrop); 4976 } 4977 4978 if (internalDrop) { 4979 if (dropOffset > fSelEnd) 4980 dropOffset -= dataLength; 4981 Delete(); 4982 } 4983 4984 Insert(dropOffset, text, dataLength, runArray); 4985 if (IsFocus()) 4986 Select(dropOffset, dropOffset + dataLength); 4987 } 4988 4989 return true; 4990 } 4991 4992 4993 void 4994 BTextView::_PerformAutoScrolling() 4995 { 4996 // Scroll the view a bit if mouse is outside the view bounds 4997 BRect bounds = Bounds(); 4998 BPoint scrollBy(B_ORIGIN); 4999 5000 // R5 does a pretty soft auto-scroll, we try to do the same by 5001 // simply scrolling the distance between cursor and border 5002 if (fWhere.x > bounds.right) 5003 scrollBy.x = fWhere.x - bounds.right; 5004 else if (fWhere.x < bounds.left) 5005 scrollBy.x = fWhere.x - bounds.left; // negative value 5006 5007 // prevent horizontal scrolling if text rect is inside view rect 5008 if (fTextRect.left > bounds.left && fTextRect.right < bounds.right) 5009 scrollBy.x = 0; 5010 5011 if (CountLines() > 1) { 5012 // scroll in Y only if multiple lines! 5013 if (fWhere.y > bounds.bottom) 5014 scrollBy.y = fWhere.y - bounds.bottom; 5015 else if (fWhere.y < bounds.top) 5016 scrollBy.y = fWhere.y - bounds.top; // negative value 5017 } 5018 5019 // prevent vertical scrolling if text rect is inside view rect 5020 if (fTextRect.top > bounds.top && fTextRect.bottom < bounds.bottom) 5021 scrollBy.y = 0; 5022 5023 if (scrollBy != B_ORIGIN) 5024 _ScrollBy(scrollBy.x, scrollBy.y); 5025 } 5026 5027 5028 //! Updates the scrollbars associated with the object (if any). 5029 void 5030 BTextView::_UpdateScrollbars() 5031 { 5032 BRect bounds(Bounds()); 5033 BScrollBar* horizontalScrollBar = ScrollBar(B_HORIZONTAL); 5034 BScrollBar* verticalScrollBar = ScrollBar(B_VERTICAL); 5035 5036 // do we have a horizontal scroll bar? 5037 if (horizontalScrollBar != NULL) { 5038 long viewWidth = bounds.IntegerWidth(); 5039 long dataWidth = (long)ceilf(_TextWidth()); 5040 5041 long maxRange = dataWidth - viewWidth; 5042 maxRange = std::max(maxRange, 0l); 5043 5044 horizontalScrollBar->SetRange(0, (float)maxRange); 5045 horizontalScrollBar->SetProportion((float)viewWidth 5046 / (float)dataWidth); 5047 horizontalScrollBar->SetSteps(kHorizontalScrollBarStep, 5048 dataWidth / 10); 5049 } 5050 5051 // how about a vertical scroll bar? 5052 if (verticalScrollBar != NULL) { 5053 long viewHeight = bounds.IntegerHeight(); 5054 long dataHeight = (long)ceilf(_TextHeight()); 5055 5056 long maxRange = dataHeight - viewHeight; 5057 maxRange = std::max(maxRange, 0l); 5058 5059 verticalScrollBar->SetRange(0, maxRange); 5060 verticalScrollBar->SetProportion((float)viewHeight 5061 / (float)dataHeight); 5062 verticalScrollBar->SetSteps(kVerticalScrollBarStep, 5063 viewHeight); 5064 } 5065 } 5066 5067 5068 //! Scrolls by the given offsets 5069 void 5070 BTextView::_ScrollBy(float horizontal, float vertical) 5071 { 5072 BRect bounds = Bounds(); 5073 _ScrollTo(bounds.left + horizontal, bounds.top + vertical); 5074 } 5075 5076 5077 //! Scrolls to the given position, making sure not to scroll out of bounds. 5078 void 5079 BTextView::_ScrollTo(float x, float y) 5080 { 5081 BRect bounds = Bounds(); 5082 long viewWidth = bounds.IntegerWidth(); 5083 long viewHeight = bounds.IntegerHeight(); 5084 5085 float minWidth = fTextRect.left - fLayoutData->leftInset; 5086 float maxWidth = fTextRect.right + fLayoutData->rightInset - viewWidth; 5087 float minHeight = fTextRect.top - fLayoutData->topInset; 5088 float maxHeight = fTextRect.bottom + fLayoutData->bottomInset - viewHeight; 5089 5090 // set horizontal scroll limits 5091 if (x > maxWidth) 5092 x = maxWidth; 5093 if (x < minWidth) 5094 x = minWidth; 5095 5096 // set vertical scroll limits 5097 if (y > maxHeight) 5098 y = maxHeight; 5099 if (y < minHeight) 5100 y = minHeight; 5101 5102 ScrollTo(x, y); 5103 } 5104 5105 5106 //! Autoresizes the view to fit the contained text. 5107 void 5108 BTextView::_AutoResize(bool redraw) 5109 { 5110 if (!fResizable || fContainerView == NULL) 5111 return; 5112 5113 // NOTE: This container view thing is only used by Tracker. 5114 // move container view if not left aligned 5115 float oldWidth = Bounds().Width(); 5116 float newWidth = _TextWidth(); 5117 float right = oldWidth - newWidth; 5118 5119 if (fAlignment == B_ALIGN_CENTER) 5120 fContainerView->MoveBy(roundf(right / 2), 0); 5121 else if (fAlignment == B_ALIGN_RIGHT) 5122 fContainerView->MoveBy(right, 0); 5123 5124 // resize container view 5125 float grow = newWidth - oldWidth; 5126 fContainerView->ResizeBy(grow, 0); 5127 5128 // reposition text view 5129 fTextRect.OffsetTo(fLayoutData->leftInset, fLayoutData->topInset); 5130 5131 // scroll rect to start, there is room for full text 5132 ScrollToOffset(0); 5133 5134 if (redraw) 5135 _RequestDrawLines(0, 0); 5136 } 5137 5138 5139 //! Creates a new offscreen BBitmap with an associated BView. 5140 void 5141 BTextView::_NewOffscreen(float padding) 5142 { 5143 if (fOffscreen != NULL) 5144 _DeleteOffscreen(); 5145 5146 #if USE_DOUBLEBUFFERING 5147 BRect bitmapRect(0, 0, fTextRect.Width() + padding, fTextRect.Height()); 5148 fOffscreen = new BBitmap(bitmapRect, fColorSpace, true, false); 5149 if (fOffscreen != NULL && fOffscreen->Lock()) { 5150 BView* bufferView = new BView(bitmapRect, "drawing view", 0, 0); 5151 fOffscreen->AddChild(bufferView); 5152 fOffscreen->Unlock(); 5153 } 5154 #endif 5155 } 5156 5157 5158 //! Deletes the textview's offscreen bitmap, if any. 5159 void 5160 BTextView::_DeleteOffscreen() 5161 { 5162 if (fOffscreen != NULL && fOffscreen->Lock()) { 5163 delete fOffscreen; 5164 fOffscreen = NULL; 5165 } 5166 } 5167 5168 5169 /*! Creates a new offscreen bitmap, highlight the selection, and set the 5170 cursor to \c B_CURSOR_I_BEAM. 5171 */ 5172 void 5173 BTextView::_Activate() 5174 { 5175 fActive = true; 5176 5177 // Create a new offscreen BBitmap 5178 _NewOffscreen(); 5179 5180 if (fSelStart != fSelEnd) { 5181 if (fSelectable) 5182 Highlight(fSelStart, fSelEnd); 5183 } else 5184 _ShowCaret(); 5185 5186 BPoint where; 5187 uint32 buttons; 5188 GetMouse(&where, &buttons, false); 5189 if (Bounds().Contains(where)) 5190 _TrackMouse(where, NULL); 5191 5192 if (Window() != NULL) { 5193 BMessage* message; 5194 5195 if (!Window()->HasShortcut(B_LEFT_ARROW, B_COMMAND_KEY) 5196 && !Window()->HasShortcut(B_RIGHT_ARROW, B_COMMAND_KEY)) { 5197 message = new BMessage(kMsgNavigateArrow); 5198 message->AddInt32("key", B_LEFT_ARROW); 5199 message->AddInt32("modifiers", B_COMMAND_KEY); 5200 Window()->AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY, message, this); 5201 5202 message = new BMessage(kMsgNavigateArrow); 5203 message->AddInt32("key", B_RIGHT_ARROW); 5204 message->AddInt32("modifiers", B_COMMAND_KEY); 5205 Window()->AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY, message, this); 5206 5207 fInstalledNavigateCommandWordwiseShortcuts = true; 5208 } 5209 if (!Window()->HasShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY) 5210 && !Window()->HasShortcut(B_RIGHT_ARROW, 5211 B_COMMAND_KEY | B_SHIFT_KEY)) { 5212 message = new BMessage(kMsgNavigateArrow); 5213 message->AddInt32("key", B_LEFT_ARROW); 5214 message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY); 5215 Window()->AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY, 5216 message, this); 5217 5218 message = new BMessage(kMsgNavigateArrow); 5219 message->AddInt32("key", B_RIGHT_ARROW); 5220 message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY); 5221 Window()->AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY, 5222 message, this); 5223 5224 fInstalledSelectCommandWordwiseShortcuts = true; 5225 } 5226 if (!Window()->HasShortcut(B_DELETE, B_COMMAND_KEY) 5227 && !Window()->HasShortcut(B_BACKSPACE, B_COMMAND_KEY)) { 5228 message = new BMessage(kMsgRemoveWord); 5229 message->AddInt32("key", B_DELETE); 5230 message->AddInt32("modifiers", B_COMMAND_KEY); 5231 Window()->AddShortcut(B_DELETE, B_COMMAND_KEY, message, this); 5232 5233 message = new BMessage(kMsgRemoveWord); 5234 message->AddInt32("key", B_BACKSPACE); 5235 message->AddInt32("modifiers", B_COMMAND_KEY); 5236 Window()->AddShortcut(B_BACKSPACE, B_COMMAND_KEY, message, this); 5237 5238 fInstalledRemoveCommandWordwiseShortcuts = true; 5239 } 5240 5241 if (!Window()->HasShortcut(B_LEFT_ARROW, B_OPTION_KEY) 5242 && !Window()->HasShortcut(B_RIGHT_ARROW, B_OPTION_KEY)) { 5243 message = new BMessage(kMsgNavigateArrow); 5244 message->AddInt32("key", B_LEFT_ARROW); 5245 message->AddInt32("modifiers", B_OPTION_KEY); 5246 Window()->AddShortcut(B_LEFT_ARROW, B_OPTION_KEY, message, this); 5247 5248 message = new BMessage(kMsgNavigateArrow); 5249 message->AddInt32("key", B_RIGHT_ARROW); 5250 message->AddInt32("modifiers", B_OPTION_KEY); 5251 Window()->AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY, message, this); 5252 5253 fInstalledNavigateOptionWordwiseShortcuts = true; 5254 } 5255 if (!Window()->HasShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_SHIFT_KEY) 5256 && !Window()->HasShortcut(B_RIGHT_ARROW, 5257 B_OPTION_KEY | B_SHIFT_KEY)) { 5258 message = new BMessage(kMsgNavigateArrow); 5259 message->AddInt32("key", B_LEFT_ARROW); 5260 message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY); 5261 Window()->AddShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_SHIFT_KEY, 5262 message, this); 5263 5264 message = new BMessage(kMsgNavigateArrow); 5265 message->AddInt32("key", B_RIGHT_ARROW); 5266 message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY); 5267 Window()->AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_SHIFT_KEY, 5268 message, this); 5269 5270 fInstalledSelectOptionWordwiseShortcuts = true; 5271 } 5272 if (!Window()->HasShortcut(B_DELETE, B_OPTION_KEY) 5273 && !Window()->HasShortcut(B_BACKSPACE, B_OPTION_KEY)) { 5274 message = new BMessage(kMsgRemoveWord); 5275 message->AddInt32("key", B_DELETE); 5276 message->AddInt32("modifiers", B_OPTION_KEY); 5277 Window()->AddShortcut(B_DELETE, B_OPTION_KEY, message, this); 5278 5279 message = new BMessage(kMsgRemoveWord); 5280 message->AddInt32("key", B_BACKSPACE); 5281 message->AddInt32("modifiers", B_OPTION_KEY); 5282 Window()->AddShortcut(B_BACKSPACE, B_OPTION_KEY, message, this); 5283 5284 fInstalledRemoveOptionWordwiseShortcuts = true; 5285 } 5286 5287 if (!Window()->HasShortcut(B_UP_ARROW, B_OPTION_KEY) 5288 && !Window()->HasShortcut(B_DOWN_ARROW, B_OPTION_KEY)) { 5289 message = new BMessage(kMsgNavigateArrow); 5290 message->AddInt32("key", B_UP_ARROW); 5291 message->AddInt32("modifiers", B_OPTION_KEY); 5292 Window()->AddShortcut(B_UP_ARROW, B_OPTION_KEY, message, this); 5293 5294 message = new BMessage(kMsgNavigateArrow); 5295 message->AddInt32("key", B_DOWN_ARROW); 5296 message->AddInt32("modifiers", B_OPTION_KEY); 5297 Window()->AddShortcut(B_DOWN_ARROW, B_OPTION_KEY, message, this); 5298 5299 fInstalledNavigateOptionLinewiseShortcuts = true; 5300 } 5301 if (!Window()->HasShortcut(B_UP_ARROW, B_OPTION_KEY | B_SHIFT_KEY) 5302 && !Window()->HasShortcut(B_DOWN_ARROW, 5303 B_OPTION_KEY | B_SHIFT_KEY)) { 5304 message = new BMessage(kMsgNavigateArrow); 5305 message->AddInt32("key", B_UP_ARROW); 5306 message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY); 5307 Window()->AddShortcut(B_UP_ARROW, B_OPTION_KEY | B_SHIFT_KEY, 5308 message, this); 5309 5310 message = new BMessage(kMsgNavigateArrow); 5311 message->AddInt32("key", B_DOWN_ARROW); 5312 message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY); 5313 Window()->AddShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_SHIFT_KEY, 5314 message, this); 5315 5316 fInstalledSelectOptionLinewiseShortcuts = true; 5317 } 5318 5319 if (!Window()->HasShortcut(B_HOME, B_COMMAND_KEY) 5320 && !Window()->HasShortcut(B_END, B_COMMAND_KEY)) { 5321 message = new BMessage(kMsgNavigatePage); 5322 message->AddInt32("key", B_HOME); 5323 message->AddInt32("modifiers", B_COMMAND_KEY); 5324 Window()->AddShortcut(B_HOME, B_COMMAND_KEY, message, this); 5325 5326 message = new BMessage(kMsgNavigatePage); 5327 message->AddInt32("key", B_END); 5328 message->AddInt32("modifiers", B_COMMAND_KEY); 5329 Window()->AddShortcut(B_END, B_COMMAND_KEY, message, this); 5330 5331 fInstalledNavigateHomeEndDocwiseShortcuts = true; 5332 } 5333 if (!Window()->HasShortcut(B_HOME, B_COMMAND_KEY | B_SHIFT_KEY) 5334 && !Window()->HasShortcut(B_END, B_COMMAND_KEY | B_SHIFT_KEY)) { 5335 message = new BMessage(kMsgNavigatePage); 5336 message->AddInt32("key", B_HOME); 5337 message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY); 5338 Window()->AddShortcut(B_HOME, B_COMMAND_KEY | B_SHIFT_KEY, 5339 message, this); 5340 5341 message = new BMessage(kMsgNavigatePage); 5342 message->AddInt32("key", B_END); 5343 message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY); 5344 Window()->AddShortcut(B_END, B_COMMAND_KEY | B_SHIFT_KEY, 5345 message, this); 5346 5347 fInstalledSelectHomeEndDocwiseShortcuts = true; 5348 } 5349 } 5350 } 5351 5352 5353 //! Unhilights the selection, set the cursor to \c B_CURSOR_SYSTEM_DEFAULT. 5354 void 5355 BTextView::_Deactivate() 5356 { 5357 fActive = false; 5358 5359 _CancelInputMethod(); 5360 _DeleteOffscreen(); 5361 5362 if (fSelStart != fSelEnd) { 5363 if (fSelectable) 5364 Highlight(fSelStart, fSelEnd); 5365 } else 5366 _HideCaret(); 5367 5368 if (Window() != NULL) { 5369 if (fInstalledNavigateCommandWordwiseShortcuts) { 5370 Window()->RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY); 5371 Window()->RemoveShortcut(B_RIGHT_ARROW, B_COMMAND_KEY); 5372 fInstalledNavigateCommandWordwiseShortcuts = false; 5373 } 5374 if (fInstalledSelectCommandWordwiseShortcuts) { 5375 Window()->RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY); 5376 Window()->RemoveShortcut(B_RIGHT_ARROW, 5377 B_COMMAND_KEY | B_SHIFT_KEY); 5378 fInstalledSelectCommandWordwiseShortcuts = false; 5379 } 5380 if (fInstalledRemoveCommandWordwiseShortcuts) { 5381 Window()->RemoveShortcut(B_DELETE, B_COMMAND_KEY); 5382 Window()->RemoveShortcut(B_BACKSPACE, B_COMMAND_KEY); 5383 fInstalledRemoveCommandWordwiseShortcuts = false; 5384 } 5385 5386 if (fInstalledNavigateOptionWordwiseShortcuts) { 5387 Window()->RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY); 5388 Window()->RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY); 5389 fInstalledNavigateOptionWordwiseShortcuts = false; 5390 } 5391 if (fInstalledSelectOptionWordwiseShortcuts) { 5392 Window()->RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_SHIFT_KEY); 5393 Window()->RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_SHIFT_KEY); 5394 fInstalledSelectOptionWordwiseShortcuts = false; 5395 } 5396 if (fInstalledRemoveOptionWordwiseShortcuts) { 5397 Window()->RemoveShortcut(B_DELETE, B_OPTION_KEY); 5398 Window()->RemoveShortcut(B_BACKSPACE, B_OPTION_KEY); 5399 fInstalledRemoveOptionWordwiseShortcuts = false; 5400 } 5401 5402 if (fInstalledNavigateOptionLinewiseShortcuts) { 5403 Window()->RemoveShortcut(B_UP_ARROW, B_OPTION_KEY); 5404 Window()->RemoveShortcut(B_DOWN_ARROW, B_OPTION_KEY); 5405 fInstalledNavigateOptionLinewiseShortcuts = false; 5406 } 5407 if (fInstalledSelectOptionLinewiseShortcuts) { 5408 Window()->RemoveShortcut(B_UP_ARROW, B_OPTION_KEY | B_SHIFT_KEY); 5409 Window()->RemoveShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_SHIFT_KEY); 5410 fInstalledSelectOptionLinewiseShortcuts = false; 5411 } 5412 5413 if (fInstalledNavigateHomeEndDocwiseShortcuts) { 5414 Window()->RemoveShortcut(B_HOME, B_COMMAND_KEY); 5415 Window()->RemoveShortcut(B_END, B_COMMAND_KEY); 5416 fInstalledNavigateHomeEndDocwiseShortcuts = false; 5417 } 5418 if (fInstalledSelectHomeEndDocwiseShortcuts) { 5419 Window()->RemoveShortcut(B_HOME, B_COMMAND_KEY | B_SHIFT_KEY); 5420 Window()->RemoveShortcut(B_END, B_COMMAND_KEY | B_SHIFT_KEY); 5421 fInstalledSelectHomeEndDocwiseShortcuts = false; 5422 } 5423 } 5424 } 5425 5426 5427 /*! Changes the passed in font to be displayable by the object. 5428 5429 Set font rotation to 0, removes any font flag, set font spacing 5430 to \c B_BITMAP_SPACING and font encoding to \c B_UNICODE_UTF8. 5431 */ 5432 void 5433 BTextView::_NormalizeFont(BFont* font) 5434 { 5435 if (font) { 5436 font->SetRotation(0.0f); 5437 font->SetFlags(0); 5438 font->SetSpacing(B_BITMAP_SPACING); 5439 font->SetEncoding(B_UNICODE_UTF8); 5440 } 5441 } 5442 5443 5444 void 5445 BTextView::_SetRunArray(int32 startOffset, int32 endOffset, 5446 const text_run_array* runs) 5447 { 5448 const int32 numStyles = runs->count; 5449 if (numStyles > 0) { 5450 const text_run* theRun = &runs->runs[0]; 5451 for (int32 index = 0; index < numStyles; index++) { 5452 int32 fromOffset = theRun->offset + startOffset; 5453 int32 toOffset = endOffset; 5454 if (index + 1 < numStyles) { 5455 toOffset = (theRun + 1)->offset + startOffset; 5456 toOffset = (toOffset > endOffset) ? endOffset : toOffset; 5457 } 5458 5459 _ApplyStyleRange(fromOffset, toOffset, B_FONT_ALL, &theRun->font, 5460 &theRun->color, false); 5461 5462 theRun++; 5463 } 5464 fStyles->InvalidateNullStyle(); 5465 } 5466 } 5467 5468 5469 /*! Returns the character class of the character at the given offset. 5470 5471 \param offset The offset where the wanted character can be found. 5472 5473 \return A value which represents the character's classification. 5474 */ 5475 uint32 5476 BTextView::_CharClassification(int32 offset) const 5477 { 5478 // TODO: Should check against a list of characters containing also 5479 // japanese word breakers. 5480 // And what about other languages ? Isn't there a better way to check 5481 // for separator characters ? 5482 // Andrew suggested to have a look at UnicodeBlockObject.h 5483 switch (fText->RealCharAt(offset)) { 5484 case '\0': 5485 return CHAR_CLASS_END_OF_TEXT; 5486 5487 case B_SPACE: 5488 case B_TAB: 5489 case B_ENTER: 5490 return CHAR_CLASS_WHITESPACE; 5491 5492 case '=': 5493 case '+': 5494 case '@': 5495 case '#': 5496 case '$': 5497 case '%': 5498 case '^': 5499 case '&': 5500 case '*': 5501 case '\\': 5502 case '|': 5503 case '<': 5504 case '>': 5505 case '/': 5506 case '~': 5507 return CHAR_CLASS_GRAPHICAL; 5508 5509 case '\'': 5510 case '"': 5511 return CHAR_CLASS_QUOTE; 5512 5513 case ',': 5514 case '.': 5515 case '?': 5516 case '!': 5517 case ';': 5518 case ':': 5519 case '-': 5520 return CHAR_CLASS_PUNCTUATION; 5521 5522 case '(': 5523 case '[': 5524 case '{': 5525 return CHAR_CLASS_PARENS_OPEN; 5526 5527 case ')': 5528 case ']': 5529 case '}': 5530 return CHAR_CLASS_PARENS_CLOSE; 5531 5532 default: 5533 return CHAR_CLASS_DEFAULT; 5534 } 5535 } 5536 5537 5538 /*! Returns the offset of the next UTF-8 character. 5539 5540 \param offset The offset where to start looking. 5541 5542 \return The offset of the next UTF-8 character. 5543 */ 5544 int32 5545 BTextView::_NextInitialByte(int32 offset) const 5546 { 5547 if (offset >= fText->Length()) 5548 return offset; 5549 5550 for (++offset; (ByteAt(offset) & 0xC0) == 0x80; ++offset) 5551 ; 5552 5553 return offset; 5554 } 5555 5556 5557 /*! Returns the offset of the previous UTF-8 character. 5558 5559 \param offset The offset where to start looking. 5560 5561 \return The offset of the previous UTF-8 character. 5562 */ 5563 int32 5564 BTextView::_PreviousInitialByte(int32 offset) const 5565 { 5566 if (offset <= 0) 5567 return 0; 5568 5569 int32 count = 6; 5570 5571 for (--offset; offset > 0 && count; --offset, --count) { 5572 if ((ByteAt(offset) & 0xC0) != 0x80) 5573 break; 5574 } 5575 5576 return count ? offset : 0; 5577 } 5578 5579 5580 bool 5581 BTextView::_GetProperty(BMessage* message, BMessage* specifier, 5582 const char* property, BMessage* reply) 5583 { 5584 CALLED(); 5585 if (strcmp(property, "selection") == 0) { 5586 reply->what = B_REPLY; 5587 reply->AddInt32("result", fSelStart); 5588 reply->AddInt32("result", fSelEnd); 5589 reply->AddInt32("error", B_OK); 5590 5591 return true; 5592 } else if (strcmp(property, "Text") == 0) { 5593 if (IsTypingHidden()) { 5594 // Do not allow stealing passwords via scripting 5595 beep(); 5596 return false; 5597 } 5598 5599 int32 index, range; 5600 specifier->FindInt32("index", &index); 5601 specifier->FindInt32("range", &range); 5602 5603 char* buffer = new char[range + 1]; 5604 GetText(index, range, buffer); 5605 5606 reply->what = B_REPLY; 5607 reply->AddString("result", buffer); 5608 reply->AddInt32("error", B_OK); 5609 5610 delete[] buffer; 5611 5612 return true; 5613 } else if (strcmp(property, "text_run_array") == 0) 5614 return false; 5615 5616 return false; 5617 } 5618 5619 5620 bool 5621 BTextView::_SetProperty(BMessage* message, BMessage* specifier, 5622 const char* property, BMessage* reply) 5623 { 5624 CALLED(); 5625 if (strcmp(property, "selection") == 0) { 5626 int32 index, range; 5627 5628 specifier->FindInt32("index", &index); 5629 specifier->FindInt32("range", &range); 5630 5631 Select(index, index + range); 5632 5633 reply->what = B_REPLY; 5634 reply->AddInt32("error", B_OK); 5635 5636 return true; 5637 } else if (strcmp(property, "Text") == 0) { 5638 int32 index, range; 5639 specifier->FindInt32("index", &index); 5640 specifier->FindInt32("range", &range); 5641 5642 const char* buffer = NULL; 5643 if (message->FindString("data", &buffer) == B_OK) { 5644 InsertText(buffer, range, index, NULL); 5645 _Refresh(index, index + range); 5646 } else { 5647 DeleteText(index, index + range); 5648 _Refresh(index, index); 5649 } 5650 5651 reply->what = B_REPLY; 5652 reply->AddInt32("error", B_OK); 5653 5654 return true; 5655 } else if (strcmp(property, "text_run_array") == 0) 5656 return false; 5657 5658 return false; 5659 } 5660 5661 5662 bool 5663 BTextView::_CountProperties(BMessage* message, BMessage* specifier, 5664 const char* property, BMessage* reply) 5665 { 5666 CALLED(); 5667 if (strcmp(property, "Text") == 0) { 5668 reply->what = B_REPLY; 5669 reply->AddInt32("result", fText->Length()); 5670 reply->AddInt32("error", B_OK); 5671 return true; 5672 } 5673 5674 return false; 5675 } 5676 5677 5678 //! Called when the object receives a \c B_INPUT_METHOD_CHANGED message. 5679 void 5680 BTextView::_HandleInputMethodChanged(BMessage* message) 5681 { 5682 // TODO: block input if not editable (Andrew) 5683 ASSERT(fInline != NULL); 5684 5685 const char* string = NULL; 5686 if (message->FindString("be:string", &string) < B_OK || string == NULL) 5687 return; 5688 5689 _HideCaret(); 5690 5691 if (IsFocus()) 5692 be_app->ObscureCursor(); 5693 5694 // If we find the "be:confirmed" boolean (and the boolean is true), 5695 // it means it's over for now, so the current InlineInput object 5696 // should become inactive. We will probably receive a 5697 // B_INPUT_METHOD_STOPPED message after this one. 5698 bool confirmed; 5699 if (message->FindBool("be:confirmed", &confirmed) != B_OK) 5700 confirmed = false; 5701 5702 // Delete the previously inserted text (if any) 5703 if (fInline->IsActive()) { 5704 const int32 oldOffset = fInline->Offset(); 5705 DeleteText(oldOffset, oldOffset + fInline->Length()); 5706 if (confirmed) 5707 fInline->SetActive(false); 5708 fCaretOffset = fSelStart = fSelEnd = oldOffset; 5709 } 5710 5711 const int32 stringLen = strlen(string); 5712 5713 fInline->SetOffset(fSelStart); 5714 fInline->SetLength(stringLen); 5715 fInline->ResetClauses(); 5716 5717 if (!confirmed && !fInline->IsActive()) 5718 fInline->SetActive(true); 5719 5720 // Get the clauses, and pass them to the InlineInput object 5721 // TODO: Find out if what we did it's ok, currently we don't consider 5722 // clauses at all, while the bebook says we should; though the visual 5723 // effect we obtained seems correct. Weird. 5724 int32 clauseCount = 0; 5725 int32 clauseStart; 5726 int32 clauseEnd; 5727 while (message->FindInt32("be:clause_start", clauseCount, &clauseStart) 5728 == B_OK 5729 && message->FindInt32("be:clause_end", clauseCount, &clauseEnd) 5730 == B_OK) { 5731 if (!fInline->AddClause(clauseStart, clauseEnd)) 5732 break; 5733 clauseCount++; 5734 } 5735 5736 if (confirmed) { 5737 _Refresh(fSelStart, fSelEnd, fSelEnd); 5738 _ShowCaret(); 5739 5740 // now we need to feed ourselves the individual characters as if the 5741 // user would have pressed them now - this lets KeyDown() pick out all 5742 // the special characters like B_BACKSPACE, cursor keys and the like: 5743 const char* currPos = string; 5744 const char* prevPos = currPos; 5745 while (*currPos != '\0') { 5746 if ((*currPos & 0xC0) == 0xC0) { 5747 // found the start of an UTF-8 char, we collect while it lasts 5748 ++currPos; 5749 while ((*currPos & 0xC0) == 0x80) 5750 ++currPos; 5751 } else if ((*currPos & 0xC0) == 0x80) { 5752 // illegal: character starts with utf-8 intermediate byte, 5753 // skip it 5754 prevPos = ++currPos; 5755 } else { 5756 // single byte character/code, just feed that 5757 ++currPos; 5758 } 5759 KeyDown(prevPos, currPos - prevPos); 5760 prevPos = currPos; 5761 } 5762 5763 _Refresh(fSelStart, fSelEnd, fSelEnd); 5764 } else { 5765 // temporarily show transient state of inline input 5766 int32 selectionStart = 0; 5767 int32 selectionEnd = 0; 5768 message->FindInt32("be:selection", 0, &selectionStart); 5769 message->FindInt32("be:selection", 1, &selectionEnd); 5770 5771 fInline->SetSelectionOffset(selectionStart); 5772 fInline->SetSelectionLength(selectionEnd - selectionStart); 5773 5774 const int32 inlineOffset = fInline->Offset(); 5775 InsertText(string, stringLen, fSelStart, NULL); 5776 5777 _Refresh(inlineOffset, fSelEnd, fSelEnd); 5778 _ShowCaret(); 5779 } 5780 } 5781 5782 5783 /*! Called when the object receives a \c B_INPUT_METHOD_LOCATION_REQUEST 5784 message. 5785 */ 5786 void 5787 BTextView::_HandleInputMethodLocationRequest() 5788 { 5789 ASSERT(fInline != NULL); 5790 5791 int32 offset = fInline->Offset(); 5792 const int32 limit = offset + fInline->Length(); 5793 5794 BMessage message(B_INPUT_METHOD_EVENT); 5795 message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST); 5796 5797 // Add the location of the UTF8 characters 5798 while (offset < limit) { 5799 float height; 5800 BPoint where = PointAt(offset, &height); 5801 ConvertToScreen(&where); 5802 5803 message.AddPoint("be:location_reply", where); 5804 message.AddFloat("be:height_reply", height); 5805 5806 offset = _NextInitialByte(offset); 5807 } 5808 5809 fInline->Method()->SendMessage(&message); 5810 } 5811 5812 5813 //! Tells the Input Server method add-on to stop the current transaction. 5814 void 5815 BTextView::_CancelInputMethod() 5816 { 5817 if (!fInline) 5818 return; 5819 5820 InlineInput* inlineInput = fInline; 5821 fInline = NULL; 5822 5823 if (inlineInput->IsActive() && Window()) { 5824 _Refresh(inlineInput->Offset(), fText->Length() 5825 - inlineInput->Offset()); 5826 5827 BMessage message(B_INPUT_METHOD_EVENT); 5828 message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED); 5829 inlineInput->Method()->SendMessage(&message); 5830 } 5831 5832 delete inlineInput; 5833 } 5834 5835 5836 /*! Returns the line number of the character at the given \a offset. 5837 5838 \note This will never return the last line (use LineAt() if you 5839 need to be correct about that.) N.B. 5840 5841 \param offset The offset of the wanted character. 5842 5843 \return The line number of the character at the given \a offset as an int32. 5844 */ 5845 int32 5846 BTextView::_LineAt(int32 offset) const 5847 { 5848 return fLines->OffsetToLine(offset); 5849 } 5850 5851 5852 /*! Returns the line number that the given \a point is on. 5853 5854 \note This will never return the last line (use LineAt() if you 5855 need to be correct about that.) N.B. 5856 5857 \param point The \a point the get the line number of. 5858 5859 \return The line number of the given \a point as an int32. 5860 */ 5861 int32 5862 BTextView::_LineAt(const BPoint& point) const 5863 { 5864 return fLines->PixelToLine(point.y - fTextRect.top); 5865 } 5866 5867 5868 /*! Returns whether or not the given \a offset is on the empty line at the end 5869 of the buffer. 5870 */ 5871 bool 5872 BTextView::_IsOnEmptyLastLine(int32 offset) const 5873 { 5874 return (offset == fText->Length() && offset > 0 5875 && fText->RealCharAt(offset - 1) == B_ENTER); 5876 } 5877 5878 5879 void 5880 BTextView::_ApplyStyleRange(int32 fromOffset, int32 toOffset, uint32 mode, 5881 const BFont* font, const rgb_color* color, bool syncNullStyle) 5882 { 5883 BFont normalized; 5884 // Declared before the if so it stays allocated until the call to 5885 // SetStyleRange 5886 if (font != NULL) { 5887 // if a font has been given, normalize it 5888 normalized = *font; 5889 _NormalizeFont(&normalized); 5890 font = &normalized; 5891 } 5892 5893 if (!fStylable) { 5894 // always apply font and color to full range for non-stylable textviews 5895 fromOffset = 0; 5896 toOffset = fText->Length(); 5897 } 5898 5899 if (syncNullStyle) 5900 fStyles->SyncNullStyle(fromOffset); 5901 5902 fStyles->SetStyleRange(fromOffset, toOffset, fText->Length(), mode, 5903 font, color); 5904 } 5905 5906 5907 float 5908 BTextView::_NullStyleHeight() const 5909 { 5910 const BFont* font = NULL; 5911 fStyles->GetNullStyle(&font, NULL); 5912 5913 font_height fontHeight; 5914 font->GetHeight(&fontHeight); 5915 return ceilf(fontHeight.ascent + fontHeight.descent + 1); 5916 } 5917 5918 5919 void 5920 BTextView::_ShowContextMenu(BPoint where) 5921 { 5922 bool isRedo; 5923 undo_state state = UndoState(&isRedo); 5924 bool isUndo = state != B_UNDO_UNAVAILABLE && !isRedo; 5925 5926 int32 start; 5927 int32 finish; 5928 GetSelection(&start, &finish); 5929 5930 bool canEdit = IsEditable(); 5931 int32 length = fText->Length(); 5932 5933 BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 5934 5935 BLayoutBuilder::Menu<>(menu) 5936 .AddItem(TRANSLATE("Undo"), B_UNDO/*, 'Z'*/) 5937 .SetEnabled(canEdit && isUndo) 5938 .AddItem(TRANSLATE("Redo"), B_UNDO/*, 'Z', B_SHIFT_KEY*/) 5939 .SetEnabled(canEdit && isRedo) 5940 .AddSeparator() 5941 .AddItem(TRANSLATE("Cut"), B_CUT, 'X') 5942 .SetEnabled(canEdit && start != finish) 5943 .AddItem(TRANSLATE("Copy"), B_COPY, 'C') 5944 .SetEnabled(start != finish) 5945 .AddItem(TRANSLATE("Paste"), B_PASTE, 'V') 5946 .SetEnabled(canEdit && be_clipboard->SystemCount() > 0) 5947 .AddSeparator() 5948 .AddItem(TRANSLATE("Select all"), B_SELECT_ALL, 'A') 5949 .SetEnabled(!(start == 0 && finish == length)) 5950 ; 5951 5952 menu->SetTargetForItems(this); 5953 ConvertToScreen(&where); 5954 menu->Go(where, true, true, true); 5955 } 5956 5957 5958 void 5959 BTextView::_FilterDisallowedChars(char* text, ssize_t& length, 5960 text_run_array* runArray) 5961 { 5962 if (!fDisallowedChars) 5963 return; 5964 5965 if (fDisallowedChars->IsEmpty() || !text) 5966 return; 5967 5968 ssize_t stringIndex = 0; 5969 if (runArray) { 5970 ssize_t remNext = 0; 5971 5972 for (int i = 0; i < runArray->count; i++) { 5973 runArray->runs[i].offset -= remNext; 5974 while (stringIndex < runArray->runs[i].offset 5975 && stringIndex < length) { 5976 if (fDisallowedChars->HasItem( 5977 reinterpret_cast<void*>(text[stringIndex]))) { 5978 memmove(text + stringIndex, text + stringIndex + 1, 5979 length - stringIndex - 1); 5980 length--; 5981 runArray->runs[i].offset--; 5982 remNext++; 5983 } else 5984 stringIndex++; 5985 } 5986 } 5987 } 5988 5989 while (stringIndex < length) { 5990 if (fDisallowedChars->HasItem( 5991 reinterpret_cast<void*>(text[stringIndex]))) { 5992 memmove(text + stringIndex, text + stringIndex + 1, 5993 length - stringIndex - 1); 5994 length--; 5995 } else 5996 stringIndex++; 5997 } 5998 } 5999 6000 6001 void 6002 BTextView::_UpdateInsets(const BRect& rect) 6003 { 6004 // do not update insets if SetInsets() was called 6005 if (fLayoutData->overridden) 6006 return; 6007 6008 const BRect& bounds = Bounds(); 6009 6010 // we disallow negative insets, as they would cause parts of the 6011 // text to be hidden 6012 fLayoutData->leftInset = rect.left >= bounds.left 6013 ? rect.left - bounds.left : 0; 6014 fLayoutData->topInset = rect.top >= bounds.top 6015 ? rect.top - bounds.top : 0; 6016 fLayoutData->rightInset = bounds.right >= rect.right 6017 ? bounds.right - rect.right : 0; 6018 fLayoutData->bottomInset = bounds.bottom >= rect.bottom 6019 ? bounds.bottom - rect.bottom : 0; 6020 6021 // only add default insets if text rect is set to bounds 6022 if (rect == bounds && (fEditable || fSelectable)) { 6023 float hPadding = be_control_look->DefaultLabelSpacing(); 6024 float hInset = floorf(hPadding / 2.0f); 6025 float vInset = 1; 6026 fLayoutData->leftInset += hInset; 6027 fLayoutData->topInset += vInset; 6028 fLayoutData->rightInset += hInset; 6029 fLayoutData->bottomInset += vInset; 6030 } 6031 } 6032 6033 6034 float 6035 BTextView::_ViewWidth() 6036 { 6037 return Bounds().Width() 6038 - fLayoutData->leftInset 6039 - fLayoutData->rightInset; 6040 } 6041 6042 6043 float 6044 BTextView::_ViewHeight() 6045 { 6046 return Bounds().Height() 6047 - fLayoutData->topInset 6048 - fLayoutData->bottomInset; 6049 } 6050 6051 6052 BRect 6053 BTextView::_ViewRect() 6054 { 6055 BRect rect(Bounds()); 6056 rect.left += fLayoutData->leftInset; 6057 rect.top += fLayoutData->topInset; 6058 rect.right -= fLayoutData->rightInset; 6059 rect.bottom -= fLayoutData->bottomInset; 6060 6061 return rect; 6062 } 6063 6064 6065 float 6066 BTextView::_TextWidth() 6067 { 6068 return fTextRect.Width() 6069 + fLayoutData->leftInset 6070 + fLayoutData->rightInset; 6071 } 6072 6073 6074 float 6075 BTextView::_TextHeight() 6076 { 6077 return fTextRect.Height() 6078 + fLayoutData->topInset 6079 + fLayoutData->bottomInset; 6080 } 6081 6082 6083 BRect 6084 BTextView::_TextRect() 6085 { 6086 BRect rect(fTextRect); 6087 rect.left -= fLayoutData->leftInset; 6088 rect.top -= fLayoutData->topInset; 6089 rect.right += fLayoutData->rightInset; 6090 rect.bottom += fLayoutData->bottomInset; 6091 6092 return rect; 6093 } 6094 6095 6096 // #pragma mark - BTextView::TextTrackState 6097 6098 6099 BTextView::TextTrackState::TextTrackState(BMessenger messenger) 6100 : 6101 clickOffset(0), 6102 shiftDown(false), 6103 anchor(0), 6104 selStart(0), 6105 selEnd(0), 6106 fRunner(NULL) 6107 { 6108 BMessage message(_PING_); 6109 const bigtime_t scrollSpeed = 25 * 1000; // 40 scroll steps per second 6110 fRunner = new (nothrow) BMessageRunner(messenger, &message, scrollSpeed); 6111 } 6112 6113 6114 BTextView::TextTrackState::~TextTrackState() 6115 { 6116 delete fRunner; 6117 } 6118 6119 6120 void 6121 BTextView::TextTrackState::SimulateMouseMovement(BTextView* textView) 6122 { 6123 BPoint where; 6124 uint32 buttons; 6125 // When the mouse cursor is still and outside the textview, 6126 // no B_MOUSE_MOVED message are sent, obviously. But scrolling 6127 // has to work neverthless, so we "fake" a MouseMoved() call here. 6128 textView->GetMouse(&where, &buttons); 6129 textView->_PerformMouseMoved(where, B_INSIDE_VIEW); 6130 } 6131 6132 6133 // #pragma mark - Binary ABI compat 6134 6135 6136 extern "C" void 6137 B_IF_GCC_2(InvalidateLayout__9BTextViewb, _ZN9BTextView16InvalidateLayoutEb)( 6138 BTextView* view, bool descendants) 6139 { 6140 perform_data_layout_invalidated data; 6141 data.descendants = descendants; 6142 6143 view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data); 6144 } 6145