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