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 && 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 _HideCaret(); 2384 _CancelInputMethod(); 2385 } 2386 } 2387 } 2388 2389 2390 bool 2391 BTextView::IsEditable() const 2392 { 2393 return fEditable; 2394 } 2395 2396 2397 void 2398 BTextView::SetWordWrap(bool wrap) 2399 { 2400 if (wrap == fWrap) 2401 return; 2402 2403 bool updateOnScreen = fActive && Window() != NULL; 2404 if (updateOnScreen) { 2405 // hide the caret, unhilite the selection 2406 if (fSelStart != fSelEnd) { 2407 if (fSelectable) 2408 Highlight(fSelStart, fSelEnd); 2409 } else 2410 _HideCaret(); 2411 } 2412 2413 BRect savedBounds = Bounds(); 2414 2415 fWrap = wrap; 2416 if (wrap) 2417 _ResetTextRect(); // calls _Refresh 2418 else 2419 _Refresh(0, fText->Length()); 2420 2421 if (fEditable) 2422 ScrollToOffset(fCaretOffset); 2423 2424 // redraw text rect and update scroll bars if bounds have changed 2425 if (Bounds() != savedBounds) 2426 FrameResized(Bounds().Width(), Bounds().Height()); 2427 2428 if (updateOnScreen) { 2429 // show the caret, hilite the selection 2430 if (fSelStart != fSelEnd) { 2431 if (fSelectable) 2432 Highlight(fSelStart, fSelEnd); 2433 } else 2434 _ShowCaret(); 2435 } 2436 } 2437 2438 2439 bool 2440 BTextView::DoesWordWrap() const 2441 { 2442 return fWrap; 2443 } 2444 2445 2446 void 2447 BTextView::SetMaxBytes(int32 max) 2448 { 2449 const int32 textLength = fText->Length(); 2450 fMaxBytes = max; 2451 2452 if (fMaxBytes < textLength) { 2453 int32 offset = fMaxBytes; 2454 // Delete the text after fMaxBytes, but 2455 // respect multibyte characters boundaries. 2456 const int32 previousInitial = _PreviousInitialByte(offset); 2457 if (_NextInitialByte(previousInitial) != offset) 2458 offset = previousInitial; 2459 2460 Delete(offset, textLength); 2461 } 2462 } 2463 2464 2465 int32 2466 BTextView::MaxBytes() const 2467 { 2468 return fMaxBytes; 2469 } 2470 2471 2472 void 2473 BTextView::DisallowChar(uint32 character) 2474 { 2475 if (fDisallowedChars == NULL) 2476 fDisallowedChars = new BList; 2477 if (!fDisallowedChars->HasItem(reinterpret_cast<void*>(character))) 2478 fDisallowedChars->AddItem(reinterpret_cast<void*>(character)); 2479 } 2480 2481 2482 void 2483 BTextView::AllowChar(uint32 character) 2484 { 2485 if (fDisallowedChars != NULL) 2486 fDisallowedChars->RemoveItem(reinterpret_cast<void*>(character)); 2487 } 2488 2489 2490 void 2491 BTextView::SetAlignment(alignment align) 2492 { 2493 // Do a reality check 2494 if (fAlignment != align && 2495 (align == B_ALIGN_LEFT || 2496 align == B_ALIGN_RIGHT || 2497 align == B_ALIGN_CENTER)) { 2498 fAlignment = align; 2499 2500 // After setting new alignment, update the view/window 2501 if (Window() != NULL) { 2502 FrameResized(Bounds().Width(), Bounds().Height()); 2503 // text rect position and scroll bars may change 2504 Invalidate(); 2505 } 2506 } 2507 } 2508 2509 2510 alignment 2511 BTextView::Alignment() const 2512 { 2513 return fAlignment; 2514 } 2515 2516 2517 void 2518 BTextView::SetAutoindent(bool state) 2519 { 2520 fAutoindent = state; 2521 } 2522 2523 2524 bool 2525 BTextView::DoesAutoindent() const 2526 { 2527 return fAutoindent; 2528 } 2529 2530 2531 void 2532 BTextView::SetColorSpace(color_space colors) 2533 { 2534 if (colors != fColorSpace && fOffscreen) { 2535 fColorSpace = colors; 2536 _DeleteOffscreen(); 2537 _NewOffscreen(); 2538 } 2539 } 2540 2541 2542 color_space 2543 BTextView::ColorSpace() const 2544 { 2545 return fColorSpace; 2546 } 2547 2548 2549 void 2550 BTextView::MakeResizable(bool resize, BView* resizeView) 2551 { 2552 if (resize) { 2553 fResizable = true; 2554 fContainerView = resizeView; 2555 2556 // Wrapping mode and resizable mode can't live together 2557 if (fWrap) { 2558 fWrap = false; 2559 2560 if (fActive && Window() != NULL) { 2561 if (fSelStart != fSelEnd) { 2562 if (fSelectable) 2563 Highlight(fSelStart, fSelEnd); 2564 } else 2565 _HideCaret(); 2566 } 2567 } 2568 // We need to reset the right inset, as otherwise the auto-resize would 2569 // get confused about just how wide the textview needs to be. 2570 // This seems to be an artifact of how Tracker creates the textview 2571 // during a rename action. 2572 fLayoutData->rightInset = fLayoutData->leftInset; 2573 } else { 2574 fResizable = false; 2575 fContainerView = NULL; 2576 if (fOffscreen) 2577 _DeleteOffscreen(); 2578 _NewOffscreen(); 2579 } 2580 2581 _Refresh(0, fText->Length()); 2582 } 2583 2584 2585 bool 2586 BTextView::IsResizable() const 2587 { 2588 return fResizable; 2589 } 2590 2591 2592 void 2593 BTextView::SetDoesUndo(bool undo) 2594 { 2595 if (undo && fUndo == NULL) 2596 fUndo = new UndoBuffer(this, B_UNDO_UNAVAILABLE); 2597 else if (!undo && fUndo != NULL) { 2598 delete fUndo; 2599 fUndo = NULL; 2600 } 2601 } 2602 2603 2604 bool 2605 BTextView::DoesUndo() const 2606 { 2607 return fUndo != NULL; 2608 } 2609 2610 2611 void 2612 BTextView::HideTyping(bool enabled) 2613 { 2614 if (enabled) 2615 Delete(0, fText->Length()); 2616 2617 fText->SetPasswordMode(enabled); 2618 } 2619 2620 2621 bool 2622 BTextView::IsTypingHidden() const 2623 { 2624 return fText->PasswordMode(); 2625 } 2626 2627 2628 // #pragma mark - Size methods 2629 2630 2631 void 2632 BTextView::ResizeToPreferred() 2633 { 2634 BView::ResizeToPreferred(); 2635 } 2636 2637 2638 void 2639 BTextView::GetPreferredSize(float* _width, float* _height) 2640 { 2641 CALLED(); 2642 2643 _ValidateLayoutData(); 2644 2645 if (_width) { 2646 float width = Bounds().Width(); 2647 if (width < fLayoutData->min.width 2648 || (Flags() & B_SUPPORTS_LAYOUT) != 0) { 2649 width = fLayoutData->min.width; 2650 } 2651 *_width = width; 2652 } 2653 2654 if (_height) { 2655 float height = Bounds().Height(); 2656 if (height < fLayoutData->min.height 2657 || (Flags() & B_SUPPORTS_LAYOUT) != 0) { 2658 height = fLayoutData->min.height; 2659 } 2660 *_height = height; 2661 } 2662 } 2663 2664 2665 BSize 2666 BTextView::MinSize() 2667 { 2668 CALLED(); 2669 2670 _ValidateLayoutData(); 2671 return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min); 2672 } 2673 2674 2675 BSize 2676 BTextView::MaxSize() 2677 { 2678 CALLED(); 2679 2680 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), 2681 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); 2682 } 2683 2684 2685 BSize 2686 BTextView::PreferredSize() 2687 { 2688 CALLED(); 2689 2690 _ValidateLayoutData(); 2691 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), 2692 fLayoutData->preferred); 2693 } 2694 2695 2696 bool 2697 BTextView::HasHeightForWidth() 2698 { 2699 if (IsEditable()) 2700 return BView::HasHeightForWidth(); 2701 2702 // When not editable, we assume that all text is supposed to be visible. 2703 return true; 2704 } 2705 2706 2707 void 2708 BTextView::GetHeightForWidth(float width, float* min, float* max, 2709 float* preferred) 2710 { 2711 if (IsEditable()) { 2712 BView::GetHeightForWidth(width, min, max, preferred); 2713 return; 2714 } 2715 2716 // TODO: don't change the actual text rect! 2717 fTextRect.right = fTextRect.left + width; 2718 _Refresh(0, fText->Length()); 2719 2720 if (min != NULL) 2721 *min = fTextRect.Height(); 2722 if (max != NULL) 2723 *max = B_SIZE_UNLIMITED; 2724 if (preferred != NULL) 2725 *preferred = fTextRect.Height(); 2726 } 2727 2728 2729 // #pragma mark - Layout methods 2730 2731 2732 void 2733 BTextView::LayoutInvalidated(bool descendants) 2734 { 2735 CALLED(); 2736 2737 fLayoutData->valid = false; 2738 } 2739 2740 2741 void 2742 BTextView::DoLayout() 2743 { 2744 // Bail out, if we shan't do layout. 2745 if (!(Flags() & B_SUPPORTS_LAYOUT)) 2746 return; 2747 2748 CALLED(); 2749 2750 // If the user set a layout, we let the base class version call its 2751 // hook. 2752 if (GetLayout()) { 2753 BView::DoLayout(); 2754 return; 2755 } 2756 2757 _ValidateLayoutData(); 2758 2759 // validate current size 2760 BSize size(Bounds().Size()); 2761 if (size.width < fLayoutData->min.width) 2762 size.width = fLayoutData->min.width; 2763 if (size.height < fLayoutData->min.height) 2764 size.height = fLayoutData->min.height; 2765 2766 _ResetTextRect(); 2767 } 2768 2769 2770 void 2771 BTextView::_ValidateLayoutData() 2772 { 2773 if (fLayoutData->valid) 2774 return; 2775 2776 CALLED(); 2777 2778 float lineHeight = ceilf(LineHeight(0)); 2779 TRACE("line height: %.2f\n", lineHeight); 2780 2781 // compute our minimal size 2782 BSize min(lineHeight * 3, lineHeight); 2783 min.width += fLayoutData->leftInset + fLayoutData->rightInset; 2784 min.height += fLayoutData->topInset + fLayoutData->bottomInset; 2785 2786 fLayoutData->min = min; 2787 2788 // compute our preferred size 2789 fLayoutData->preferred.height = fTextRect.Height() 2790 + fLayoutData->topInset + fLayoutData->bottomInset; 2791 2792 if (fWrap) 2793 fLayoutData->preferred.width = min.width + 5 * lineHeight; 2794 else { 2795 float maxWidth = fLines->MaxWidth(); 2796 if (maxWidth < min.width) 2797 maxWidth = min.width; 2798 2799 fLayoutData->preferred.width 2800 = maxWidth + fLayoutData->leftInset + fLayoutData->rightInset; 2801 } 2802 2803 fLayoutData->valid = true; 2804 ResetLayoutInvalidation(); 2805 2806 TRACE("width: %.2f, height: %.2f\n", min.width, min.height); 2807 } 2808 2809 2810 // #pragma mark - 2811 2812 2813 void 2814 BTextView::AllAttached() 2815 { 2816 BView::AllAttached(); 2817 } 2818 2819 2820 void 2821 BTextView::AllDetached() 2822 { 2823 BView::AllDetached(); 2824 } 2825 2826 2827 /* static */ 2828 text_run_array* 2829 BTextView::AllocRunArray(int32 entryCount, int32* outSize) 2830 { 2831 int32 size = sizeof(text_run_array) + (entryCount - 1) * sizeof(text_run); 2832 2833 text_run_array* runArray = (text_run_array*)calloc(size, 1); 2834 if (runArray == NULL) { 2835 if (outSize != NULL) 2836 *outSize = 0; 2837 return NULL; 2838 } 2839 2840 runArray->count = entryCount; 2841 2842 // Call constructors explicitly as the text_run_array 2843 // was allocated with malloc (and has to, for backwards 2844 // compatibility) 2845 for (int32 i = 0; i < runArray->count; i++) 2846 new (&runArray->runs[i].font) BFont; 2847 2848 if (outSize != NULL) 2849 *outSize = size; 2850 2851 return runArray; 2852 } 2853 2854 2855 /* static */ 2856 text_run_array* 2857 BTextView::CopyRunArray(const text_run_array* orig, int32 countDelta) 2858 { 2859 text_run_array* copy = AllocRunArray(countDelta, NULL); 2860 if (copy != NULL) { 2861 for (int32 i = 0; i < countDelta; i++) { 2862 copy->runs[i].offset = orig->runs[i].offset; 2863 copy->runs[i].font = orig->runs[i].font; 2864 copy->runs[i].color = orig->runs[i].color; 2865 } 2866 } 2867 return copy; 2868 } 2869 2870 2871 /* static */ 2872 void 2873 BTextView::FreeRunArray(text_run_array* array) 2874 { 2875 if (array == NULL) 2876 return; 2877 2878 // Call destructors explicitly 2879 for (int32 i = 0; i < array->count; i++) 2880 array->runs[i].font.~BFont(); 2881 2882 free(array); 2883 } 2884 2885 2886 /* static */ 2887 void* 2888 BTextView::FlattenRunArray(const text_run_array* runArray, int32* _size) 2889 { 2890 CALLED(); 2891 int32 size = sizeof(flattened_text_run_array) + (runArray->count - 1) 2892 * sizeof(flattened_text_run); 2893 2894 flattened_text_run_array* array = (flattened_text_run_array*)malloc(size); 2895 if (array == NULL) { 2896 if (_size) 2897 *_size = 0; 2898 return NULL; 2899 } 2900 2901 array->magic = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayMagic); 2902 array->version = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayVersion); 2903 array->count = B_HOST_TO_BENDIAN_INT32(runArray->count); 2904 2905 for (int32 i = 0; i < runArray->count; i++) { 2906 array->styles[i].offset = B_HOST_TO_BENDIAN_INT32( 2907 runArray->runs[i].offset); 2908 runArray->runs[i].font.GetFamilyAndStyle(&array->styles[i].family, 2909 &array->styles[i].style); 2910 array->styles[i].size = B_HOST_TO_BENDIAN_FLOAT( 2911 runArray->runs[i].font.Size()); 2912 array->styles[i].shear = B_HOST_TO_BENDIAN_FLOAT( 2913 runArray->runs[i].font.Shear()); 2914 array->styles[i].face = B_HOST_TO_BENDIAN_INT16( 2915 runArray->runs[i].font.Face()); 2916 array->styles[i].red = runArray->runs[i].color.red; 2917 array->styles[i].green = runArray->runs[i].color.green; 2918 array->styles[i].blue = runArray->runs[i].color.blue; 2919 array->styles[i].alpha = 255; 2920 array->styles[i]._reserved_ = 0; 2921 } 2922 2923 if (_size) 2924 *_size = size; 2925 2926 return array; 2927 } 2928 2929 2930 /* static */ 2931 text_run_array* 2932 BTextView::UnflattenRunArray(const void* data, int32* _size) 2933 { 2934 CALLED(); 2935 flattened_text_run_array* array = (flattened_text_run_array*)data; 2936 2937 if (B_BENDIAN_TO_HOST_INT32(array->magic) != kFlattenedTextRunArrayMagic 2938 || B_BENDIAN_TO_HOST_INT32(array->version) 2939 != kFlattenedTextRunArrayVersion) { 2940 if (_size) 2941 *_size = 0; 2942 2943 return NULL; 2944 } 2945 2946 int32 count = B_BENDIAN_TO_HOST_INT32(array->count); 2947 2948 text_run_array* runArray = AllocRunArray(count, _size); 2949 if (runArray == NULL) 2950 return NULL; 2951 2952 for (int32 i = 0; i < count; i++) { 2953 runArray->runs[i].offset = B_BENDIAN_TO_HOST_INT32( 2954 array->styles[i].offset); 2955 2956 // Set family and style independently from each other, so that 2957 // even if the family doesn't exist, we try to preserve the style 2958 runArray->runs[i].font.SetFamilyAndStyle(array->styles[i].family, NULL); 2959 runArray->runs[i].font.SetFamilyAndStyle(NULL, array->styles[i].style); 2960 2961 runArray->runs[i].font.SetSize(B_BENDIAN_TO_HOST_FLOAT( 2962 array->styles[i].size)); 2963 runArray->runs[i].font.SetShear(B_BENDIAN_TO_HOST_FLOAT( 2964 array->styles[i].shear)); 2965 2966 uint16 face = B_BENDIAN_TO_HOST_INT16(array->styles[i].face); 2967 if (face != B_REGULAR_FACE) { 2968 // Be's version doesn't seem to set this correctly 2969 runArray->runs[i].font.SetFace(face); 2970 } 2971 2972 runArray->runs[i].color.red = array->styles[i].red; 2973 runArray->runs[i].color.green = array->styles[i].green; 2974 runArray->runs[i].color.blue = array->styles[i].blue; 2975 runArray->runs[i].color.alpha = array->styles[i].alpha; 2976 } 2977 2978 return runArray; 2979 } 2980 2981 2982 void 2983 BTextView::InsertText(const char* text, int32 length, int32 offset, 2984 const text_run_array* runs) 2985 { 2986 CALLED(); 2987 2988 if (length < 0) 2989 length = 0; 2990 2991 if (offset < 0) 2992 offset = 0; 2993 else if (offset > fText->Length()) 2994 offset = fText->Length(); 2995 2996 if (length > 0) { 2997 // add the text to the buffer 2998 fText->InsertText(text, length, offset); 2999 3000 // update the start offsets of each line below offset 3001 fLines->BumpOffset(length, _LineAt(offset) + 1); 3002 3003 // update the style runs 3004 fStyles->BumpOffset(length, fStyles->OffsetToRun(offset - 1) + 1); 3005 3006 // offset the caret/selection, if the text was inserted before it 3007 if (offset <= fSelEnd) { 3008 fSelStart += length; 3009 fCaretOffset = fSelEnd = fSelStart; 3010 } 3011 } 3012 3013 if (fStylable && runs != NULL) 3014 _SetRunArray(offset, offset + length, runs); 3015 else { 3016 // apply null-style to inserted text 3017 _ApplyStyleRange(offset, offset + length); 3018 } 3019 } 3020 3021 3022 void 3023 BTextView::DeleteText(int32 fromOffset, int32 toOffset) 3024 { 3025 CALLED(); 3026 3027 if (fromOffset < 0) 3028 fromOffset = 0; 3029 else if (fromOffset > fText->Length()) 3030 fromOffset = fText->Length(); 3031 3032 if (toOffset < 0) 3033 toOffset = 0; 3034 else if (toOffset > fText->Length()) 3035 toOffset = fText->Length(); 3036 3037 if (fromOffset >= toOffset) 3038 return; 3039 3040 // set nullStyle to style at beginning of range 3041 fStyles->InvalidateNullStyle(); 3042 fStyles->SyncNullStyle(fromOffset); 3043 3044 // remove from the text buffer 3045 fText->RemoveRange(fromOffset, toOffset); 3046 3047 // remove any lines that have been obliterated 3048 fLines->RemoveLineRange(fromOffset, toOffset); 3049 3050 // remove any style runs that have been obliterated 3051 fStyles->RemoveStyleRange(fromOffset, toOffset); 3052 3053 // adjust the selection accordingly, assumes fSelEnd >= fSelStart! 3054 int32 range = toOffset - fromOffset; 3055 if (fSelStart >= toOffset) { 3056 // selection is behind the range that was removed 3057 fSelStart -= range; 3058 fSelEnd -= range; 3059 } else if (fSelStart >= fromOffset && fSelEnd <= toOffset) { 3060 // the selection is within the range that was removed 3061 fSelStart = fSelEnd = fromOffset; 3062 } else if (fSelStart >= fromOffset && fSelEnd > toOffset) { 3063 // the selection starts within and ends after the range 3064 // the remaining part is the part that was after the range 3065 fSelStart = fromOffset; 3066 fSelEnd = fromOffset + fSelEnd - toOffset; 3067 } else if (fSelStart < fromOffset && fSelEnd < toOffset) { 3068 // the selection starts before, but ends within the range 3069 fSelEnd = fromOffset; 3070 } else if (fSelStart < fromOffset && fSelEnd >= toOffset) { 3071 // the selection starts before and ends after the range 3072 fSelEnd -= range; 3073 } 3074 } 3075 3076 3077 /*! Undoes the last changes. 3078 3079 \param clipboard A \a clipboard to use for the undo operation. 3080 */ 3081 void 3082 BTextView::Undo(BClipboard* clipboard) 3083 { 3084 if (fUndo) 3085 fUndo->Undo(clipboard); 3086 } 3087 3088 3089 undo_state 3090 BTextView::UndoState(bool* isRedo) const 3091 { 3092 return fUndo == NULL ? B_UNDO_UNAVAILABLE : fUndo->State(isRedo); 3093 } 3094 3095 3096 // #pragma mark - GetDragParameters() is protected 3097 3098 3099 void 3100 BTextView::GetDragParameters(BMessage* drag, BBitmap** bitmap, BPoint* point, 3101 BHandler** handler) 3102 { 3103 CALLED(); 3104 if (drag == NULL) 3105 return; 3106 3107 // Add originator and action 3108 drag->AddPointer("be:originator", this); 3109 drag->AddInt32("be_actions", B_TRASH_TARGET); 3110 3111 // add the text 3112 int32 numBytes = fSelEnd - fSelStart; 3113 const char* text = fText->GetString(fSelStart, &numBytes); 3114 drag->AddData("text/plain", B_MIME_TYPE, text, numBytes); 3115 3116 // add the corresponding styles 3117 int32 size = 0; 3118 text_run_array* styles = RunArray(fSelStart, fSelEnd, &size); 3119 3120 if (styles != NULL) { 3121 drag->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE, 3122 styles, size); 3123 3124 FreeRunArray(styles); 3125 } 3126 3127 if (bitmap != NULL) 3128 *bitmap = NULL; 3129 3130 if (handler != NULL) 3131 *handler = NULL; 3132 } 3133 3134 3135 // #pragma mark - FBC padding and forbidden methods 3136 3137 3138 void BTextView::_ReservedTextView3() {} 3139 void BTextView::_ReservedTextView4() {} 3140 void BTextView::_ReservedTextView5() {} 3141 void BTextView::_ReservedTextView6() {} 3142 void BTextView::_ReservedTextView7() {} 3143 void BTextView::_ReservedTextView8() {} 3144 void BTextView::_ReservedTextView9() {} 3145 void BTextView::_ReservedTextView10() {} 3146 void BTextView::_ReservedTextView11() {} 3147 void BTextView::_ReservedTextView12() {} 3148 3149 3150 // #pragma mark - Private methods 3151 3152 3153 /*! Inits the BTextView object. 3154 3155 \param textRect The BTextView's text rect. 3156 \param initialFont The font which the BTextView will use. 3157 \param initialColor The initial color of the text. 3158 */ 3159 void 3160 BTextView::_InitObject(BRect textRect, const BFont* initialFont, 3161 const rgb_color* initialColor) 3162 { 3163 BFont font; 3164 if (initialFont == NULL) 3165 GetFont(&font); 3166 else 3167 font = *initialFont; 3168 3169 _NormalizeFont(&font); 3170 3171 rgb_color documentTextColor = ui_color(B_DOCUMENT_TEXT_COLOR); 3172 3173 if (initialColor == NULL) 3174 initialColor = &documentTextColor; 3175 3176 fText = new BPrivate::TextGapBuffer; 3177 fLines = new LineBuffer; 3178 fStyles = new StyleBuffer(&font, initialColor); 3179 3180 fInstalledNavigateCommandWordwiseShortcuts = false; 3181 fInstalledNavigateOptionWordwiseShortcuts = false; 3182 fInstalledNavigateOptionLinewiseShortcuts = false; 3183 fInstalledNavigateHomeEndDocwiseShortcuts = false; 3184 3185 fInstalledSelectCommandWordwiseShortcuts = false; 3186 fInstalledSelectOptionWordwiseShortcuts = false; 3187 fInstalledSelectOptionLinewiseShortcuts = false; 3188 fInstalledSelectHomeEndDocwiseShortcuts = false; 3189 3190 fInstalledRemoveCommandWordwiseShortcuts = false; 3191 fInstalledRemoveOptionWordwiseShortcuts = false; 3192 3193 // We put these here instead of in the constructor initializer list 3194 // to have less code duplication, and a single place where to do changes 3195 // if needed. 3196 fTextRect = textRect; 3197 // NOTE: The only places where text rect is changed: 3198 // * width and height are adjusted in _RecalculateLineBreaks(), 3199 // text rect maintains constant insets, use SetInsets() to change. 3200 fMinTextRectWidth = fTextRect.Width(); 3201 // see SetTextRect() 3202 fSelStart = fSelEnd = 0; 3203 fCaretVisible = false; 3204 fCaretTime = 0; 3205 fCaretOffset = 0; 3206 fClickCount = 0; 3207 fClickTime = 0; 3208 fDragOffset = -1; 3209 fCursor = 0; 3210 fActive = false; 3211 fStylable = false; 3212 fTabWidth = 28.0; 3213 fSelectable = true; 3214 fEditable = true; 3215 fWrap = true; 3216 fMaxBytes = INT32_MAX; 3217 fDisallowedChars = NULL; 3218 fAlignment = B_ALIGN_LEFT; 3219 fAutoindent = false; 3220 fOffscreen = NULL; 3221 fColorSpace = B_CMAP8; 3222 fResizable = false; 3223 fContainerView = NULL; 3224 fUndo = NULL; 3225 fInline = NULL; 3226 fDragRunner = NULL; 3227 fClickRunner = NULL; 3228 fTrackingMouse = NULL; 3229 3230 fLayoutData = new LayoutData; 3231 _UpdateInsets(textRect); 3232 3233 fLastClickOffset = -1; 3234 3235 SetDoesUndo(true); 3236 } 3237 3238 3239 //! Handles when Backspace key is pressed. 3240 void 3241 BTextView::_HandleBackspace(int32 modifiers) 3242 { 3243 if (modifiers < 0) { 3244 BMessage* currentMessage = Window()->CurrentMessage(); 3245 if (currentMessage == NULL 3246 || currentMessage->FindInt32("modifiers", &modifiers) != B_OK) { 3247 modifiers = 0; 3248 } 3249 } 3250 3251 bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0; 3252 bool optionKeyDown = (modifiers & B_OPTION_KEY) != 0; 3253 bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0; 3254 3255 if ((commandKeyDown || optionKeyDown) && !controlKeyDown) { 3256 fSelStart = _PreviousWordStart(fCaretOffset - 1); 3257 fSelEnd = fCaretOffset; 3258 } 3259 3260 if (fUndo) { 3261 TypingUndoBuffer* undoBuffer = dynamic_cast<TypingUndoBuffer*>(fUndo); 3262 if (!undoBuffer) { 3263 delete fUndo; 3264 fUndo = undoBuffer = new TypingUndoBuffer(this); 3265 } 3266 undoBuffer->BackwardErase(); 3267 } 3268 3269 // we may draw twice, so turn updates off for now 3270 if (Window() != NULL) 3271 Window()->DisableUpdates(); 3272 3273 if (fSelStart == fSelEnd) { 3274 if (fSelStart != 0) 3275 fSelStart = _PreviousInitialByte(fSelStart); 3276 } else 3277 Highlight(fSelStart, fSelEnd); 3278 3279 DeleteText(fSelStart, fSelEnd); 3280 fCaretOffset = fSelEnd = fSelStart; 3281 3282 _Refresh(fSelStart, fSelEnd, fCaretOffset); 3283 3284 // turn updates back on 3285 if (Window() != NULL) 3286 Window()->EnableUpdates(); 3287 } 3288 3289 3290 //! Handles when an arrow key is pressed. 3291 void 3292 BTextView::_HandleArrowKey(uint32 arrowKey, int32 modifiers) 3293 { 3294 // return if there's nowhere to go 3295 if (fText->Length() == 0) 3296 return; 3297 3298 int32 selStart = fSelStart; 3299 int32 selEnd = fSelEnd; 3300 3301 if (modifiers < 0) { 3302 BMessage* currentMessage = Window()->CurrentMessage(); 3303 if (currentMessage == NULL 3304 || currentMessage->FindInt32("modifiers", &modifiers) != B_OK) { 3305 modifiers = 0; 3306 } 3307 } 3308 3309 bool shiftKeyDown = (modifiers & B_SHIFT_KEY) != 0; 3310 bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0; 3311 bool optionKeyDown = (modifiers & B_OPTION_KEY) != 0; 3312 bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0; 3313 3314 int32 lastClickOffset = fCaretOffset; 3315 3316 switch (arrowKey) { 3317 case B_LEFT_ARROW: 3318 if (!fEditable) 3319 _ScrollBy(-1 * kHorizontalScrollBarStep, 0); 3320 else if (fSelStart != fSelEnd && !shiftKeyDown) 3321 fCaretOffset = fSelStart; 3322 else { 3323 if ((commandKeyDown || optionKeyDown) && !controlKeyDown) 3324 fCaretOffset = _PreviousWordStart(fCaretOffset - 1); 3325 else 3326 fCaretOffset = _PreviousInitialByte(fCaretOffset); 3327 3328 if (shiftKeyDown && fCaretOffset != lastClickOffset) { 3329 if (fCaretOffset < fSelStart) { 3330 // extend selection to the left 3331 selStart = fCaretOffset; 3332 if (lastClickOffset > fSelStart) { 3333 // caret has jumped across "anchor" 3334 selEnd = fSelStart; 3335 } 3336 } else { 3337 // shrink selection from the right 3338 selEnd = fCaretOffset; 3339 } 3340 } 3341 } 3342 break; 3343 3344 case B_RIGHT_ARROW: 3345 if (!fEditable) 3346 _ScrollBy(kHorizontalScrollBarStep, 0); 3347 else if (fSelStart != fSelEnd && !shiftKeyDown) 3348 fCaretOffset = fSelEnd; 3349 else { 3350 if ((commandKeyDown || optionKeyDown) && !controlKeyDown) 3351 fCaretOffset = _NextWordEnd(fCaretOffset); 3352 else 3353 fCaretOffset = _NextInitialByte(fCaretOffset); 3354 3355 if (shiftKeyDown && fCaretOffset != lastClickOffset) { 3356 if (fCaretOffset > fSelEnd) { 3357 // extend selection to the right 3358 selEnd = fCaretOffset; 3359 if (lastClickOffset < fSelEnd) { 3360 // caret has jumped across "anchor" 3361 selStart = fSelEnd; 3362 } 3363 } else { 3364 // shrink selection from the left 3365 selStart = fCaretOffset; 3366 } 3367 } 3368 } 3369 break; 3370 3371 case B_UP_ARROW: 3372 { 3373 if (!fEditable) 3374 _ScrollBy(0, -1 * kVerticalScrollBarStep); 3375 else if (fSelStart != fSelEnd && !shiftKeyDown) 3376 fCaretOffset = fSelStart; 3377 else { 3378 if (optionKeyDown && !commandKeyDown && !controlKeyDown) 3379 fCaretOffset = _PreviousLineStart(fCaretOffset); 3380 else if (commandKeyDown && !optionKeyDown && !controlKeyDown) { 3381 _ScrollTo(0, 0); 3382 fCaretOffset = 0; 3383 } else { 3384 float height; 3385 BPoint point = PointAt(fCaretOffset, &height); 3386 // find the caret position on the previous 3387 // line by gently stepping onto this line 3388 for (int i = 1; i <= height; i++) { 3389 point.y--; 3390 int32 offset = OffsetAt(point); 3391 if (offset < fCaretOffset || i == height) { 3392 fCaretOffset = offset; 3393 break; 3394 } 3395 } 3396 } 3397 3398 if (shiftKeyDown && fCaretOffset != lastClickOffset) { 3399 if (fCaretOffset < fSelStart) { 3400 // extend selection to the top 3401 selStart = fCaretOffset; 3402 if (lastClickOffset > fSelStart) { 3403 // caret has jumped across "anchor" 3404 selEnd = fSelStart; 3405 } 3406 } else { 3407 // shrink selection from the bottom 3408 selEnd = fCaretOffset; 3409 } 3410 } 3411 } 3412 break; 3413 } 3414 3415 case B_DOWN_ARROW: 3416 { 3417 if (!fEditable) 3418 _ScrollBy(0, kVerticalScrollBarStep); 3419 else if (fSelStart != fSelEnd && !shiftKeyDown) 3420 fCaretOffset = fSelEnd; 3421 else { 3422 if (optionKeyDown && !commandKeyDown && !controlKeyDown) 3423 fCaretOffset = _NextLineEnd(fCaretOffset); 3424 else if (commandKeyDown && !optionKeyDown && !controlKeyDown) { 3425 _ScrollTo(0, fTextRect.bottom + fLayoutData->bottomInset); 3426 fCaretOffset = fText->Length(); 3427 } else { 3428 float height; 3429 BPoint point = PointAt(fCaretOffset, &height); 3430 point.y += height; 3431 fCaretOffset = OffsetAt(point); 3432 } 3433 3434 if (shiftKeyDown && fCaretOffset != lastClickOffset) { 3435 if (fCaretOffset > fSelEnd) { 3436 // extend selection to the bottom 3437 selEnd = fCaretOffset; 3438 if (lastClickOffset < fSelEnd) { 3439 // caret has jumped across "anchor" 3440 selStart = fSelEnd; 3441 } 3442 } else { 3443 // shrink selection from the top 3444 selStart = fCaretOffset; 3445 } 3446 } 3447 } 3448 break; 3449 } 3450 } 3451 3452 fStyles->InvalidateNullStyle(); 3453 3454 if (fEditable) { 3455 if (shiftKeyDown) 3456 Select(selStart, selEnd); 3457 else 3458 Select(fCaretOffset, fCaretOffset); 3459 3460 // scroll if needed 3461 ScrollToOffset(fCaretOffset); 3462 } 3463 } 3464 3465 3466 //! Handles when the Delete key is pressed. 3467 void 3468 BTextView::_HandleDelete(int32 modifiers) 3469 { 3470 if (modifiers < 0) { 3471 BMessage* currentMessage = Window()->CurrentMessage(); 3472 if (currentMessage == NULL 3473 || currentMessage->FindInt32("modifiers", &modifiers) != B_OK) { 3474 modifiers = 0; 3475 } 3476 } 3477 3478 bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0; 3479 bool optionKeyDown = (modifiers & B_OPTION_KEY) != 0; 3480 bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0; 3481 3482 if ((commandKeyDown || optionKeyDown) && !controlKeyDown) { 3483 fSelStart = fCaretOffset; 3484 fSelEnd = _NextWordEnd(fCaretOffset) + 1; 3485 } 3486 3487 if (fUndo) { 3488 TypingUndoBuffer* undoBuffer = dynamic_cast<TypingUndoBuffer*>(fUndo); 3489 if (!undoBuffer) { 3490 delete fUndo; 3491 fUndo = undoBuffer = new TypingUndoBuffer(this); 3492 } 3493 undoBuffer->ForwardErase(); 3494 } 3495 3496 // we may draw twice, so turn updates off for now 3497 if (Window() != NULL) 3498 Window()->DisableUpdates(); 3499 3500 if (fSelStart == fSelEnd) { 3501 if (fSelEnd != fText->Length()) 3502 fSelEnd = _NextInitialByte(fSelEnd); 3503 } else 3504 Highlight(fSelStart, fSelEnd); 3505 3506 DeleteText(fSelStart, fSelEnd); 3507 fCaretOffset = fSelEnd = fSelStart; 3508 3509 _Refresh(fSelStart, fSelEnd, fCaretOffset); 3510 3511 // turn updates back on 3512 if (Window() != NULL) 3513 Window()->EnableUpdates(); 3514 } 3515 3516 3517 //! Handles when the Page Up, Page Down, Home, or End key is pressed. 3518 void 3519 BTextView::_HandlePageKey(uint32 pageKey, int32 modifiers) 3520 { 3521 if (modifiers < 0) { 3522 BMessage* currentMessage = Window()->CurrentMessage(); 3523 if (currentMessage == NULL 3524 || currentMessage->FindInt32("modifiers", &modifiers) != B_OK) { 3525 modifiers = 0; 3526 } 3527 } 3528 3529 bool shiftKeyDown = (modifiers & B_SHIFT_KEY) != 0; 3530 bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0; 3531 bool optionKeyDown = (modifiers & B_OPTION_KEY) != 0; 3532 bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0; 3533 3534 STELine* line = NULL; 3535 int32 selStart = fSelStart; 3536 int32 selEnd = fSelEnd; 3537 3538 int32 lastClickOffset = fCaretOffset; 3539 switch (pageKey) { 3540 case B_HOME: 3541 if (!fEditable) { 3542 fCaretOffset = 0; 3543 _ScrollTo(0, 0); 3544 break; 3545 } else { 3546 if (commandKeyDown && !optionKeyDown && !controlKeyDown) { 3547 _ScrollTo(0, 0); 3548 fCaretOffset = 0; 3549 } else { 3550 // get the start of the last line if caret is on it 3551 line = (*fLines)[_LineAt(lastClickOffset)]; 3552 fCaretOffset = line->offset; 3553 } 3554 3555 if (!shiftKeyDown) 3556 selStart = selEnd = fCaretOffset; 3557 else if (fCaretOffset != lastClickOffset) { 3558 if (fCaretOffset < fSelStart) { 3559 // extend selection to the left 3560 selStart = fCaretOffset; 3561 if (lastClickOffset > fSelStart) { 3562 // caret has jumped across "anchor" 3563 selEnd = fSelStart; 3564 } 3565 } else { 3566 // shrink selection from the right 3567 selEnd = fCaretOffset; 3568 } 3569 } 3570 } 3571 break; 3572 3573 case B_END: 3574 if (!fEditable) { 3575 fCaretOffset = fText->Length(); 3576 _ScrollTo(0, fTextRect.bottom + fLayoutData->bottomInset); 3577 break; 3578 } else { 3579 if (commandKeyDown && !optionKeyDown && !controlKeyDown) { 3580 _ScrollTo(0, fTextRect.bottom + fLayoutData->bottomInset); 3581 fCaretOffset = fText->Length(); 3582 } else { 3583 // If we are on the last line, just go to the last 3584 // character in the buffer, otherwise get the starting 3585 // offset of the next line, and go to the previous character 3586 int32 currentLine = _LineAt(lastClickOffset); 3587 if (currentLine + 1 < fLines->NumLines()) { 3588 line = (*fLines)[currentLine + 1]; 3589 fCaretOffset = _PreviousInitialByte(line->offset); 3590 } else { 3591 // This check is needed to avoid moving the cursor 3592 // when the cursor is on the last line, and that line 3593 // is empty 3594 if (fCaretOffset != fText->Length()) { 3595 fCaretOffset = fText->Length(); 3596 if (ByteAt(fCaretOffset - 1) == B_ENTER) 3597 fCaretOffset--; 3598 } 3599 } 3600 } 3601 3602 if (!shiftKeyDown) 3603 selStart = selEnd = fCaretOffset; 3604 else if (fCaretOffset != lastClickOffset) { 3605 if (fCaretOffset > fSelEnd) { 3606 // extend selection to the right 3607 selEnd = fCaretOffset; 3608 if (lastClickOffset < fSelEnd) { 3609 // caret has jumped across "anchor" 3610 selStart = fSelEnd; 3611 } 3612 } else { 3613 // shrink selection from the left 3614 selStart = fCaretOffset; 3615 } 3616 } 3617 } 3618 break; 3619 3620 case B_PAGE_UP: 3621 { 3622 float lineHeight; 3623 BPoint currentPos = PointAt(fCaretOffset, &lineHeight); 3624 BPoint nextPos(currentPos.x, 3625 currentPos.y + lineHeight - Bounds().Height()); 3626 fCaretOffset = OffsetAt(nextPos); 3627 nextPos = PointAt(fCaretOffset); 3628 _ScrollBy(0, nextPos.y - currentPos.y); 3629 3630 if (!fEditable) 3631 break; 3632 3633 if (!shiftKeyDown) 3634 selStart = selEnd = fCaretOffset; 3635 else if (fCaretOffset != lastClickOffset) { 3636 if (fCaretOffset < fSelStart) { 3637 // extend selection to the top 3638 selStart = fCaretOffset; 3639 if (lastClickOffset > fSelStart) { 3640 // caret has jumped across "anchor" 3641 selEnd = fSelStart; 3642 } 3643 } else { 3644 // shrink selection from the bottom 3645 selEnd = fCaretOffset; 3646 } 3647 } 3648 3649 break; 3650 } 3651 3652 case B_PAGE_DOWN: 3653 { 3654 BPoint currentPos = PointAt(fCaretOffset); 3655 BPoint nextPos(currentPos.x, currentPos.y + Bounds().Height()); 3656 fCaretOffset = OffsetAt(nextPos); 3657 nextPos = PointAt(fCaretOffset); 3658 _ScrollBy(0, nextPos.y - currentPos.y); 3659 3660 if (!fEditable) 3661 break; 3662 3663 if (!shiftKeyDown) 3664 selStart = selEnd = fCaretOffset; 3665 else if (fCaretOffset != lastClickOffset) { 3666 if (fCaretOffset > fSelEnd) { 3667 // extend selection to the bottom 3668 selEnd = fCaretOffset; 3669 if (lastClickOffset < fSelEnd) { 3670 // caret has jumped across "anchor" 3671 selStart = fSelEnd; 3672 } 3673 } else { 3674 // shrink selection from the top 3675 selStart = fCaretOffset; 3676 } 3677 } 3678 3679 break; 3680 } 3681 } 3682 3683 if (fEditable) { 3684 if (shiftKeyDown) 3685 Select(selStart, selEnd); 3686 else 3687 Select(fCaretOffset, fCaretOffset); 3688 3689 ScrollToOffset(fCaretOffset); 3690 } 3691 } 3692 3693 3694 /*! Handles when an alpha-numeric key is pressed. 3695 3696 \param bytes The string or character associated with the key. 3697 \param numBytes The amount of bytes containes in "bytes". 3698 */ 3699 void 3700 BTextView::_HandleAlphaKey(const char* bytes, int32 numBytes) 3701 { 3702 // TODO: block input if not editable (Andrew) 3703 if (fUndo) { 3704 TypingUndoBuffer* undoBuffer = dynamic_cast<TypingUndoBuffer*>(fUndo); 3705 if (!undoBuffer) { 3706 delete fUndo; 3707 fUndo = undoBuffer = new TypingUndoBuffer(this); 3708 } 3709 undoBuffer->InputCharacter(numBytes); 3710 } 3711 3712 if (fSelStart != fSelEnd) { 3713 Highlight(fSelStart, fSelEnd); 3714 DeleteText(fSelStart, fSelEnd); 3715 } 3716 3717 // we may draw twice, so turn updates off for now 3718 if (Window() != NULL) 3719 Window()->DisableUpdates(); 3720 3721 if (fAutoindent && numBytes == 1 && *bytes == B_ENTER) { 3722 int32 start, offset; 3723 start = offset = OffsetAt(_LineAt(fSelStart)); 3724 3725 while (ByteAt(offset) != '\0' && 3726 (ByteAt(offset) == B_TAB || ByteAt(offset) == B_SPACE) 3727 && offset < fSelStart) 3728 offset++; 3729 3730 _DoInsertText(bytes, numBytes, fSelStart, NULL); 3731 if (start != offset) 3732 _DoInsertText(Text() + start, offset - start, fSelStart, NULL); 3733 } else 3734 _DoInsertText(bytes, numBytes, fSelStart, NULL); 3735 3736 fCaretOffset = fSelEnd; 3737 ScrollToOffset(fCaretOffset); 3738 3739 // turn updates back on 3740 if (Window() != NULL) 3741 Window()->EnableUpdates(); 3742 } 3743 3744 3745 /*! Redraw the text between the two given offsets, recalculating line-breaks 3746 if needed. 3747 3748 \param fromOffset The offset from where to refresh. 3749 \param toOffset The offset where to refresh to. 3750 \param scrollTo Scroll the view to \a scrollTo offset if not \c INT32_MIN. 3751 */ 3752 void 3753 BTextView::_Refresh(int32 fromOffset, int32 toOffset, int32 scrollTo) 3754 { 3755 // TODO: Cleanup 3756 float saveHeight = fTextRect.Height(); 3757 float saveWidth = fTextRect.Width(); 3758 int32 fromLine = _LineAt(fromOffset); 3759 int32 toLine = _LineAt(toOffset); 3760 int32 saveFromLine = fromLine; 3761 int32 saveToLine = toLine; 3762 3763 _RecalculateLineBreaks(&fromLine, &toLine); 3764 3765 // TODO: Maybe there is still something we can do without a window... 3766 if (!Window()) 3767 return; 3768 3769 BRect bounds = Bounds(); 3770 float newHeight = fTextRect.Height(); 3771 3772 // if the line breaks have changed, force an erase 3773 if (fromLine != saveFromLine || toLine != saveToLine 3774 || newHeight != saveHeight) { 3775 fromOffset = -1; 3776 } 3777 3778 if (newHeight != saveHeight) { 3779 // the text area has changed 3780 if (newHeight < saveHeight) 3781 toLine = _LineAt(BPoint(0.0f, saveHeight + fTextRect.top)); 3782 else 3783 toLine = _LineAt(BPoint(0.0f, newHeight + fTextRect.top)); 3784 } 3785 3786 // draw only those lines that are visible 3787 int32 fromVisible = _LineAt(BPoint(0.0f, bounds.top)); 3788 int32 toVisible = _LineAt(BPoint(0.0f, bounds.bottom)); 3789 fromLine = std::max(fromVisible, fromLine); 3790 toLine = std::min(toLine, toVisible); 3791 3792 _AutoResize(false); 3793 3794 _RequestDrawLines(fromLine, toLine); 3795 3796 // erase the area below the text 3797 BRect eraseRect = bounds; 3798 eraseRect.top = fTextRect.top + (*fLines)[fLines->NumLines()]->origin; 3799 eraseRect.bottom = fTextRect.top + saveHeight; 3800 if (eraseRect.bottom > eraseRect.top && eraseRect.Intersects(bounds)) { 3801 SetLowColor(ViewColor()); 3802 FillRect(eraseRect, B_SOLID_LOW); 3803 } 3804 3805 // update the scroll bars if the text area has changed 3806 if (newHeight != saveHeight || fMinTextRectWidth != saveWidth) 3807 _UpdateScrollbars(); 3808 3809 if (scrollTo != INT32_MIN) 3810 ScrollToOffset(scrollTo); 3811 3812 Flush(); 3813 } 3814 3815 3816 /*! Recalculate line breaks between two lines. 3817 3818 \param startLine The line number to start recalculating line breaks. 3819 \param endLine The line number to stop recalculating line breaks. 3820 */ 3821 void 3822 BTextView::_RecalculateLineBreaks(int32* startLine, int32* endLine) 3823 { 3824 CALLED(); 3825 3826 float width = fTextRect.Width(); 3827 3828 // don't try to compute anything if the text rect is not set 3829 if (!fTextRect.IsValid() || width == 0) 3830 return; 3831 3832 // sanity check 3833 *startLine = (*startLine < 0) ? 0 : *startLine; 3834 *endLine = (*endLine > fLines->NumLines() - 1) ? fLines->NumLines() - 1 3835 : *endLine; 3836 3837 int32 textLength = fText->Length(); 3838 int32 lineIndex = (*startLine > 0) ? *startLine - 1 : 0; 3839 int32 recalThreshold = (*fLines)[*endLine + 1]->offset; 3840 STELine* curLine = (*fLines)[lineIndex]; 3841 STELine* nextLine = curLine + 1; 3842 3843 do { 3844 float ascent, descent; 3845 int32 fromOffset = curLine->offset; 3846 int32 toOffset = _FindLineBreak(fromOffset, &ascent, &descent, &width); 3847 3848 curLine->ascent = ascent; 3849 curLine->width = width; 3850 3851 // we want to advance at least by one character 3852 int32 nextOffset = _NextInitialByte(fromOffset); 3853 if (toOffset < nextOffset && fromOffset < textLength) 3854 toOffset = nextOffset; 3855 3856 lineIndex++; 3857 STELine saveLine = *nextLine; 3858 if (lineIndex > fLines->NumLines() || toOffset < nextLine->offset) { 3859 // the new line comes before the old line start, add a line 3860 STELine newLine; 3861 newLine.offset = toOffset; 3862 newLine.origin = ceilf(curLine->origin + ascent + descent) + 1; 3863 newLine.ascent = 0; 3864 fLines->InsertLine(&newLine, lineIndex); 3865 } else { 3866 // update the existing line 3867 nextLine->offset = toOffset; 3868 nextLine->origin = ceilf(curLine->origin + ascent + descent) + 1; 3869 3870 // remove any lines that start before the current line 3871 while (lineIndex < fLines->NumLines() 3872 && toOffset >= ((*fLines)[lineIndex] + 1)->offset) { 3873 fLines->RemoveLines(lineIndex + 1); 3874 } 3875 3876 nextLine = (*fLines)[lineIndex]; 3877 if (nextLine->offset == saveLine.offset) { 3878 if (nextLine->offset >= recalThreshold) { 3879 if (nextLine->origin != saveLine.origin) 3880 fLines->BumpOrigin(nextLine->origin - saveLine.origin, 3881 lineIndex + 1); 3882 break; 3883 } 3884 } else { 3885 if (lineIndex > 0 && lineIndex == *startLine) 3886 *startLine = lineIndex - 1; 3887 } 3888 } 3889 3890 curLine = (*fLines)[lineIndex]; 3891 nextLine = curLine + 1; 3892 } while (curLine->offset < textLength); 3893 3894 // make sure that the sentinel line (which starts at the end of the buffer) 3895 // has always a width of 0 3896 (*fLines)[fLines->NumLines()]->width = 0; 3897 3898 // update text rect 3899 fTextRect.left = Bounds().left + fLayoutData->leftInset; 3900 fTextRect.right = Bounds().right - fLayoutData->rightInset; 3901 3902 // always set text rect bottom 3903 float newHeight = TextHeight(0, fLines->NumLines() - 1); 3904 fTextRect.bottom = fTextRect.top + newHeight; 3905 3906 if (!fWrap) { 3907 fMinTextRectWidth = fLines->MaxWidth(); 3908 3909 // expand width if needed 3910 switch (fAlignment) { 3911 default: 3912 case B_ALIGN_LEFT: 3913 // move right edge 3914 fTextRect.right = fTextRect.left + fMinTextRectWidth; 3915 break; 3916 3917 case B_ALIGN_RIGHT: 3918 // move left edge 3919 fTextRect.left = fTextRect.right - fMinTextRectWidth; 3920 break; 3921 3922 case B_ALIGN_CENTER: 3923 // move both edges 3924 fTextRect.InsetBy(roundf((fTextRect.Width() 3925 - fMinTextRectWidth) / 2), 0); 3926 break; 3927 } 3928 3929 _ValidateTextRect(); 3930 } 3931 3932 *endLine = lineIndex - 1; 3933 *startLine = std::min(*startLine, *endLine); 3934 } 3935 3936 3937 void 3938 BTextView::_ValidateTextRect() 3939 { 3940 // text rect right must be greater than left 3941 if (fTextRect.right <= fTextRect.left) 3942 fTextRect.right = fTextRect.left + 1; 3943 // text rect bottom must be greater than top 3944 if (fTextRect.bottom <= fTextRect.top) 3945 fTextRect.bottom = fTextRect.top + 1; 3946 } 3947 3948 3949 int32 3950 BTextView::_FindLineBreak(int32 fromOffset, float* _ascent, float* _descent, 3951 float* inOutWidth) 3952 { 3953 *_ascent = 0.0; 3954 *_descent = 0.0; 3955 3956 const int32 limit = fText->Length(); 3957 3958 // is fromOffset at the end? 3959 if (fromOffset >= limit) { 3960 // try to return valid height info anyway 3961 if (fStyles->NumRuns() > 0) { 3962 fStyles->Iterate(fromOffset, 1, fInline, NULL, NULL, _ascent, 3963 _descent); 3964 } else { 3965 if (fStyles->IsValidNullStyle()) { 3966 const BFont* font = NULL; 3967 fStyles->GetNullStyle(&font, NULL); 3968 3969 font_height fh; 3970 font->GetHeight(&fh); 3971 *_ascent = fh.ascent; 3972 *_descent = fh.descent + fh.leading; 3973 } 3974 } 3975 *inOutWidth = 0; 3976 3977 return limit; 3978 } 3979 3980 int32 offset = fromOffset; 3981 3982 if (!fWrap) { 3983 // Text wrapping is turned off. 3984 // Just find the offset of the first \n character 3985 offset = limit - fromOffset; 3986 fText->FindChar(B_ENTER, fromOffset, &offset); 3987 offset += fromOffset; 3988 int32 toOffset = (offset < limit) ? offset : limit; 3989 3990 *inOutWidth = _TabExpandedStyledWidth(fromOffset, toOffset - fromOffset, 3991 _ascent, _descent); 3992 3993 return offset < limit ? offset + 1 : limit; 3994 } 3995 3996 bool done = false; 3997 float ascent = 0.0; 3998 float descent = 0.0; 3999 int32 delta = 0; 4000 float deltaWidth = 0.0; 4001 float strWidth = 0.0; 4002 uchar theChar; 4003 4004 // wrap the text 4005 while (offset < limit && !done) { 4006 // find the next line break candidate 4007 for (; (offset + delta) < limit; delta++) { 4008 if (CanEndLine(offset + delta)) { 4009 theChar = fText->RealCharAt(offset + delta); 4010 if (theChar != B_SPACE && theChar != B_TAB 4011 && theChar != B_ENTER) { 4012 // we are scanning for trailing whitespace below, so we 4013 // have to skip non-whitespace characters, that can end 4014 // the line, here 4015 delta++; 4016 } 4017 break; 4018 } 4019 } 4020 4021 int32 deltaBeforeWhitespace = delta; 4022 // now skip over trailing whitespace, if any 4023 for (; (offset + delta) < limit; delta++) { 4024 theChar = fText->RealCharAt(offset + delta); 4025 if (theChar == B_ENTER) { 4026 // found a newline, we're done! 4027 done = true; 4028 delta++; 4029 break; 4030 } else if (theChar != B_SPACE && theChar != B_TAB) { 4031 // stop at anything else than trailing whitespace 4032 break; 4033 } 4034 } 4035 4036 delta = std::max(delta, (int32)1); 4037 4038 // do not include B_ENTER-terminator into width & height calculations 4039 deltaWidth = _TabExpandedStyledWidth(offset, 4040 done ? delta - 1 : delta, &ascent, &descent); 4041 strWidth += deltaWidth; 4042 4043 if (strWidth >= *inOutWidth) { 4044 // we've found where the line will wrap 4045 done = true; 4046 4047 // we have included trailing whitespace in the width computation 4048 // above, but that is not being shown anyway, so we try again 4049 // without the trailing whitespace 4050 if (delta == deltaBeforeWhitespace) { 4051 // there is no trailing whitespace, no point in trying 4052 break; 4053 } 4054 4055 // reset string width to start of current run ... 4056 strWidth -= deltaWidth; 4057 4058 // ... and compute the resulting width (of visible characters) 4059 strWidth += _StyledWidth(offset, deltaBeforeWhitespace, NULL, NULL); 4060 if (strWidth >= *inOutWidth) { 4061 // width of visible characters exceeds line, we need to wrap 4062 // before the current "word" 4063 break; 4064 } 4065 } 4066 4067 *_ascent = std::max(ascent, *_ascent); 4068 *_descent = std::max(descent, *_descent); 4069 4070 offset += delta; 4071 delta = 0; 4072 } 4073 4074 if (offset - fromOffset < 1) { 4075 // there weren't any words that fit entirely in this line 4076 // force a break in the middle of a word 4077 *_ascent = 0.0; 4078 *_descent = 0.0; 4079 strWidth = 0.0; 4080 4081 int32 current = fromOffset; 4082 for (offset = _NextInitialByte(current); current < limit; 4083 current = offset, offset = _NextInitialByte(offset)) { 4084 strWidth += _StyledWidth(current, offset - current, &ascent, 4085 &descent); 4086 if (strWidth >= *inOutWidth) { 4087 offset = _PreviousInitialByte(offset); 4088 break; 4089 } 4090 4091 *_ascent = std::max(ascent, *_ascent); 4092 *_descent = std::max(descent, *_descent); 4093 } 4094 } 4095 4096 return std::min(offset, limit); 4097 } 4098 4099 4100 int32 4101 BTextView::_PreviousLineStart(int32 offset) 4102 { 4103 if (offset <= 0) 4104 return 0; 4105 4106 while (offset > 0) { 4107 offset = _PreviousInitialByte(offset); 4108 if (_CharClassification(offset) == CHAR_CLASS_WHITESPACE 4109 && ByteAt(offset) == B_ENTER) { 4110 return offset + 1; 4111 } 4112 } 4113 4114 return offset; 4115 } 4116 4117 4118 int32 4119 BTextView::_NextLineEnd(int32 offset) 4120 { 4121 int32 textLen = fText->Length(); 4122 if (offset >= textLen) 4123 return textLen; 4124 4125 while (offset < textLen) { 4126 if (_CharClassification(offset) == CHAR_CLASS_WHITESPACE 4127 && ByteAt(offset) == B_ENTER) { 4128 break; 4129 } 4130 offset = _NextInitialByte(offset); 4131 } 4132 4133 return offset; 4134 } 4135 4136 4137 int32 4138 BTextView::_PreviousWordBoundary(int32 offset) 4139 { 4140 uint32 charType = _CharClassification(offset); 4141 int32 previous; 4142 while (offset > 0) { 4143 previous = _PreviousInitialByte(offset); 4144 if (_CharClassification(previous) != charType) 4145 break; 4146 offset = previous; 4147 } 4148 4149 return offset; 4150 } 4151 4152 4153 int32 4154 BTextView::_NextWordBoundary(int32 offset) 4155 { 4156 int32 textLen = fText->Length(); 4157 uint32 charType = _CharClassification(offset); 4158 while (offset < textLen) { 4159 offset = _NextInitialByte(offset); 4160 if (_CharClassification(offset) != charType) 4161 break; 4162 } 4163 4164 return offset; 4165 } 4166 4167 4168 int32 4169 BTextView::_PreviousWordStart(int32 offset) 4170 { 4171 if (offset <= 1) 4172 return 0; 4173 4174 --offset; 4175 // need to look at previous char 4176 if (_CharClassification(offset) != CHAR_CLASS_DEFAULT) { 4177 // skip non-word characters 4178 while (offset > 0) { 4179 offset = _PreviousInitialByte(offset); 4180 if (_CharClassification(offset) == CHAR_CLASS_DEFAULT) 4181 break; 4182 } 4183 } 4184 while (offset > 0) { 4185 // skip to start of word 4186 int32 previous = _PreviousInitialByte(offset); 4187 if (_CharClassification(previous) != CHAR_CLASS_DEFAULT) 4188 break; 4189 offset = previous; 4190 } 4191 4192 return offset; 4193 } 4194 4195 4196 int32 4197 BTextView::_NextWordEnd(int32 offset) 4198 { 4199 int32 textLen = fText->Length(); 4200 if (_CharClassification(offset) != CHAR_CLASS_DEFAULT) { 4201 // skip non-word characters 4202 while (offset < textLen) { 4203 offset = _NextInitialByte(offset); 4204 if (_CharClassification(offset) == CHAR_CLASS_DEFAULT) 4205 break; 4206 } 4207 } 4208 while (offset < textLen) { 4209 // skip to end of word 4210 offset = _NextInitialByte(offset); 4211 if (_CharClassification(offset) != CHAR_CLASS_DEFAULT) 4212 break; 4213 } 4214 4215 return offset; 4216 } 4217 4218 4219 /*! Returns the width used by the characters starting at the given 4220 offset with the given length, expanding all tab characters as needed. 4221 */ 4222 float 4223 BTextView::_TabExpandedStyledWidth(int32 offset, int32 length, float* _ascent, 4224 float* _descent) const 4225 { 4226 float ascent = 0.0; 4227 float descent = 0.0; 4228 float maxAscent = 0.0; 4229 float maxDescent = 0.0; 4230 4231 float width = 0.0; 4232 int32 numBytes = length; 4233 bool foundTab = false; 4234 do { 4235 foundTab = fText->FindChar(B_TAB, offset, &numBytes); 4236 width += _StyledWidth(offset, numBytes, &ascent, &descent); 4237 4238 if (maxAscent < ascent) 4239 maxAscent = ascent; 4240 if (maxDescent < descent) 4241 maxDescent = descent; 4242 4243 if (foundTab) { 4244 width += _ActualTabWidth(width); 4245 numBytes++; 4246 } 4247 4248 offset += numBytes; 4249 length -= numBytes; 4250 numBytes = length; 4251 } while (foundTab && length > 0); 4252 4253 if (_ascent != NULL) 4254 *_ascent = maxAscent; 4255 if (_descent != NULL) 4256 *_descent = maxDescent; 4257 4258 return width; 4259 } 4260 4261 4262 /*! Calculate the width of the text within the given limits. 4263 4264 \param fromOffset The offset where to start. 4265 \param length The length of the text to examine. 4266 \param _ascent A pointer to a float which will contain the maximum ascent. 4267 \param _descent A pointer to a float which will contain the maximum descent. 4268 4269 \return The width for the text within the given limits. 4270 */ 4271 float 4272 BTextView::_StyledWidth(int32 fromOffset, int32 length, float* _ascent, 4273 float* _descent) const 4274 { 4275 if (length == 0) { 4276 // determine height of char at given offset, but return empty width 4277 fStyles->Iterate(fromOffset, 1, fInline, NULL, NULL, _ascent, 4278 _descent); 4279 return 0.0; 4280 } 4281 4282 float result = 0.0; 4283 float ascent = 0.0; 4284 float descent = 0.0; 4285 float maxAscent = 0.0; 4286 float maxDescent = 0.0; 4287 4288 // iterate through the style runs 4289 const BFont* font = NULL; 4290 int32 numBytes; 4291 while ((numBytes = fStyles->Iterate(fromOffset, length, fInline, &font, 4292 NULL, &ascent, &descent)) != 0) { 4293 maxAscent = std::max(ascent, maxAscent); 4294 maxDescent = std::max(descent, maxDescent); 4295 4296 #if USE_WIDTHBUFFER 4297 // Use _BWidthBuffer_ if possible 4298 if (BPrivate::gWidthBuffer != NULL) { 4299 result += BPrivate::gWidthBuffer->StringWidth(*fText, fromOffset, 4300 numBytes, font); 4301 } else { 4302 #endif 4303 const char* text = fText->GetString(fromOffset, &numBytes); 4304 result += font->StringWidth(text, numBytes); 4305 4306 #if USE_WIDTHBUFFER 4307 } 4308 #endif 4309 4310 fromOffset += numBytes; 4311 length -= numBytes; 4312 } 4313 4314 if (_ascent != NULL) 4315 *_ascent = maxAscent; 4316 if (_descent != NULL) 4317 *_descent = maxDescent; 4318 4319 return result; 4320 } 4321 4322 4323 //! Calculate the actual tab width for the given location. 4324 float 4325 BTextView::_ActualTabWidth(float location) const 4326 { 4327 float tabWidth = fTabWidth - fmod(location, fTabWidth); 4328 if (round(tabWidth) == 0) 4329 tabWidth = fTabWidth; 4330 4331 return tabWidth; 4332 } 4333 4334 4335 void 4336 BTextView::_DoInsertText(const char* text, int32 length, int32 offset, 4337 const text_run_array* runs) 4338 { 4339 _CancelInputMethod(); 4340 4341 if (fText->Length() + length > MaxBytes()) 4342 return; 4343 4344 if (fSelStart != fSelEnd) 4345 Select(fSelStart, fSelStart); 4346 4347 const int32 textLength = fText->Length(); 4348 if (offset > textLength) 4349 offset = textLength; 4350 4351 // copy data into buffer 4352 InsertText(text, length, offset, runs); 4353 4354 // recalc line breaks and draw the text 4355 _Refresh(offset, offset + length); 4356 } 4357 4358 4359 void 4360 BTextView::_DoDeleteText(int32 fromOffset, int32 toOffset) 4361 { 4362 CALLED(); 4363 } 4364 4365 4366 void 4367 BTextView::_DrawLine(BView* view, const int32 &lineNum, 4368 const int32 &startOffset, const bool &erase, BRect &eraseRect, 4369 BRegion &inputRegion) 4370 { 4371 STELine* line = (*fLines)[lineNum]; 4372 float startLeft = fTextRect.left; 4373 if (startOffset != -1) { 4374 if (ByteAt(startOffset) == B_ENTER) { 4375 // StartOffset is a newline 4376 startLeft = PointAt(line->offset).x; 4377 } else 4378 startLeft = PointAt(startOffset).x; 4379 } else if (fAlignment != B_ALIGN_LEFT) { 4380 float alignmentOffset = fTextRect.Width() - LineWidth(lineNum); 4381 if (fAlignment == B_ALIGN_CENTER) 4382 alignmentOffset = floorf(alignmentOffset / 2); 4383 startLeft += alignmentOffset; 4384 } 4385 4386 int32 length = (line + 1)->offset; 4387 if (startOffset != -1) 4388 length -= startOffset; 4389 else 4390 length -= line->offset; 4391 4392 // DrawString() chokes if you draw a newline 4393 if (ByteAt((line + 1)->offset - 1) == B_ENTER) 4394 length--; 4395 4396 view->MovePenTo(startLeft, 4397 line->origin + line->ascent + fTextRect.top + 1); 4398 4399 if (erase) { 4400 eraseRect.top = line->origin + fTextRect.top; 4401 eraseRect.bottom = (line + 1)->origin + fTextRect.top; 4402 view->FillRect(eraseRect, B_SOLID_LOW); 4403 } 4404 4405 // do we have any text to draw? 4406 if (length <= 0) 4407 return; 4408 4409 bool foundTab = false; 4410 int32 tabChars = 0; 4411 int32 numTabs = 0; 4412 int32 offset = startOffset != -1 ? startOffset : line->offset; 4413 const BFont* font = NULL; 4414 const rgb_color* color = NULL; 4415 int32 numBytes; 4416 drawing_mode defaultTextRenderingMode = DrawingMode(); 4417 // iterate through each style on this line 4418 while ((numBytes = fStyles->Iterate(offset, length, fInline, &font, 4419 &color)) != 0) { 4420 view->SetFont(font); 4421 view->SetHighColor(*color); 4422 4423 tabChars = std::min(numBytes, length); 4424 do { 4425 foundTab = fText->FindChar(B_TAB, offset, &tabChars); 4426 if (foundTab) { 4427 do { 4428 numTabs++; 4429 if (ByteAt(offset + tabChars + numTabs) != B_TAB) 4430 break; 4431 } while ((tabChars + numTabs) < numBytes); 4432 } 4433 4434 drawing_mode textRenderingMode = defaultTextRenderingMode; 4435 4436 if (inputRegion.CountRects() > 0 4437 && ((offset <= fInline->Offset() 4438 && fInline->Offset() < offset + tabChars) 4439 || (fInline->Offset() <= offset 4440 && offset < fInline->Offset() + fInline->Length()))) { 4441 4442 textRenderingMode = B_OP_OVER; 4443 4444 BRegion textRegion; 4445 GetTextRegion(offset, offset + length, &textRegion); 4446 4447 textRegion.IntersectWith(&inputRegion); 4448 view->PushState(); 4449 4450 // Highlight in blue the inputted text 4451 view->SetHighColor(kBlueInputColor); 4452 view->FillRect(textRegion.Frame()); 4453 4454 // Highlight in red the selected part 4455 if (fInline->SelectionLength() > 0) { 4456 BRegion selectedRegion; 4457 GetTextRegion(fInline->Offset() 4458 + fInline->SelectionOffset(), fInline->Offset() 4459 + fInline->SelectionOffset() 4460 + fInline->SelectionLength(), &selectedRegion); 4461 4462 textRegion.IntersectWith(&selectedRegion); 4463 4464 view->SetHighColor(kRedInputColor); 4465 view->FillRect(textRegion.Frame()); 4466 } 4467 4468 view->PopState(); 4469 } 4470 4471 int32 size = tabChars; 4472 const char* stringToDraw = fText->GetString(offset, &size); 4473 view->SetDrawingMode(textRenderingMode); 4474 view->DrawString(stringToDraw, size); 4475 4476 if (foundTab) { 4477 float penPos = PenLocation().x - fTextRect.left; 4478 switch (fAlignment) { 4479 default: 4480 case B_ALIGN_LEFT: 4481 // nothing more to do 4482 break; 4483 4484 case B_ALIGN_RIGHT: 4485 // subtract distance from left to line 4486 penPos -= fTextRect.Width() - LineWidth(lineNum); 4487 break; 4488 4489 case B_ALIGN_CENTER: 4490 // subtract half distance from left to line 4491 penPos -= floorf((fTextRect.Width() 4492 - LineWidth(lineNum)) / 2); 4493 break; 4494 } 4495 float tabWidth = _ActualTabWidth(penPos); 4496 4497 // add in the rest of the tabs (if there are any) 4498 tabWidth += ((numTabs - 1) * fTabWidth); 4499 4500 // move pen by tab(s) width 4501 view->MovePenBy(tabWidth, 0.0); 4502 tabChars += numTabs; 4503 } 4504 4505 offset += tabChars; 4506 length -= tabChars; 4507 numBytes -= tabChars; 4508 tabChars = std::min(numBytes, length); 4509 numTabs = 0; 4510 } while (foundTab && tabChars > 0); 4511 } 4512 } 4513 4514 4515 void 4516 BTextView::_DrawLines(int32 startLine, int32 endLine, int32 startOffset, 4517 bool erase) 4518 { 4519 if (!Window()) 4520 return; 4521 4522 const BRect bounds(Bounds()); 4523 4524 // clip the text extending to end of selection 4525 BRect clipRect(fTextRect); 4526 clipRect.left = std::min(fTextRect.left, 4527 bounds.left + fLayoutData->leftInset); 4528 clipRect.right = std::max(fTextRect.right, 4529 bounds.right - fLayoutData->rightInset); 4530 clipRect = bounds & clipRect; 4531 4532 BRegion newClip; 4533 newClip.Set(clipRect); 4534 ConstrainClippingRegion(&newClip); 4535 4536 // set the low color to the view color so that 4537 // drawing to a non-white background will work 4538 SetLowColor(ViewColor()); 4539 4540 BView* view = NULL; 4541 if (fOffscreen == NULL) 4542 view = this; 4543 else { 4544 fOffscreen->Lock(); 4545 view = fOffscreen->ChildAt(0); 4546 view->SetLowColor(ViewColor()); 4547 view->FillRect(view->Bounds(), B_SOLID_LOW); 4548 } 4549 4550 long maxLine = fLines->NumLines() - 1; 4551 if (startLine < 0) 4552 startLine = 0; 4553 if (endLine > maxLine) 4554 endLine = maxLine; 4555 4556 // TODO: See if we can avoid this 4557 if (fAlignment != B_ALIGN_LEFT) 4558 erase = true; 4559 4560 BRect eraseRect = clipRect; 4561 int32 startEraseLine = startLine; 4562 STELine* line = (*fLines)[startLine]; 4563 4564 if (erase && startOffset != -1 && fAlignment == B_ALIGN_LEFT) { 4565 // erase only to the right of startOffset 4566 startEraseLine++; 4567 int32 startErase = startOffset; 4568 4569 BPoint erasePoint = PointAt(startErase); 4570 eraseRect.left = erasePoint.x; 4571 eraseRect.top = erasePoint.y; 4572 eraseRect.bottom = (line + 1)->origin + fTextRect.top; 4573 4574 view->FillRect(eraseRect, B_SOLID_LOW); 4575 4576 eraseRect = clipRect; 4577 } 4578 4579 BRegion inputRegion; 4580 if (fInline != NULL && fInline->IsActive()) { 4581 GetTextRegion(fInline->Offset(), fInline->Offset() + fInline->Length(), 4582 &inputRegion); 4583 } 4584 4585 //BPoint leftTop(startLeft, line->origin); 4586 for (int32 lineNum = startLine; lineNum <= endLine; lineNum++) { 4587 const bool eraseThisLine = erase && lineNum >= startEraseLine; 4588 _DrawLine(view, lineNum, startOffset, eraseThisLine, eraseRect, 4589 inputRegion); 4590 startOffset = -1; 4591 // Set this to -1 so the next iteration will use the line offset 4592 } 4593 4594 // draw the caret/hilite the selection 4595 if (fActive) { 4596 if (fSelStart != fSelEnd) { 4597 if (fSelectable) 4598 Highlight(fSelStart, fSelEnd); 4599 } else { 4600 if (fCaretVisible) 4601 _DrawCaret(fSelStart, true); 4602 } 4603 } 4604 4605 if (fOffscreen != NULL) { 4606 view->Sync(); 4607 /*BPoint penLocation = view->PenLocation(); 4608 BRect drawRect(leftTop.x, leftTop.y, penLocation.x, penLocation.y); 4609 DrawBitmap(fOffscreen, drawRect, drawRect);*/ 4610 fOffscreen->Unlock(); 4611 } 4612 4613 ConstrainClippingRegion(NULL); 4614 } 4615 4616 4617 void 4618 BTextView::_RequestDrawLines(int32 startLine, int32 endLine) 4619 { 4620 if (!Window()) 4621 return; 4622 4623 long maxLine = fLines->NumLines() - 1; 4624 4625 STELine* from = (*fLines)[startLine]; 4626 STELine* to = endLine == maxLine ? NULL : (*fLines)[endLine + 1]; 4627 BRect invalidRect(Bounds().left, from->origin + fTextRect.top, 4628 Bounds().right, 4629 to != NULL ? to->origin + fTextRect.top : fTextRect.bottom); 4630 Invalidate(invalidRect); 4631 } 4632 4633 4634 void 4635 BTextView::_DrawCaret(int32 offset, bool visible) 4636 { 4637 float lineHeight; 4638 BPoint caretPoint = PointAt(offset, &lineHeight); 4639 caretPoint.x = std::min(caretPoint.x, fTextRect.right); 4640 4641 BRect caretRect; 4642 caretRect.left = caretRect.right = caretPoint.x; 4643 caretRect.top = caretPoint.y; 4644 caretRect.bottom = caretPoint.y + lineHeight - 1; 4645 4646 if (visible) 4647 InvertRect(caretRect); 4648 else 4649 Invalidate(caretRect); 4650 } 4651 4652 4653 inline void 4654 BTextView::_ShowCaret() 4655 { 4656 if (fActive && !fCaretVisible && fEditable && fSelStart == fSelEnd) 4657 _InvertCaret(); 4658 } 4659 4660 4661 inline void 4662 BTextView::_HideCaret() 4663 { 4664 if (fCaretVisible && fSelStart == fSelEnd) 4665 _InvertCaret(); 4666 } 4667 4668 4669 //! Hides the caret if it is being shown, and if it's hidden, shows it. 4670 void 4671 BTextView::_InvertCaret() 4672 { 4673 fCaretVisible = !fCaretVisible; 4674 _DrawCaret(fSelStart, fCaretVisible); 4675 fCaretTime = system_time(); 4676 } 4677 4678 4679 /*! Place the dragging caret at the given offset. 4680 4681 \param offset The offset (zero based within the object's text) where to 4682 place the dragging caret. If it's -1, hide the caret. 4683 */ 4684 void 4685 BTextView::_DragCaret(int32 offset) 4686 { 4687 // does the caret need to move? 4688 if (offset == fDragOffset) 4689 return; 4690 4691 // hide the previous drag caret 4692 if (fDragOffset != -1) 4693 _DrawCaret(fDragOffset, false); 4694 4695 // do we have a new location? 4696 if (offset != -1) { 4697 if (fActive) { 4698 // ignore if offset is within active selection 4699 if (offset >= fSelStart && offset <= fSelEnd) { 4700 fDragOffset = -1; 4701 return; 4702 } 4703 } 4704 4705 _DrawCaret(offset, true); 4706 } 4707 4708 fDragOffset = offset; 4709 } 4710 4711 4712 void 4713 BTextView::_StopMouseTracking() 4714 { 4715 delete fTrackingMouse; 4716 fTrackingMouse = NULL; 4717 } 4718 4719 4720 bool 4721 BTextView::_PerformMouseUp(BPoint where) 4722 { 4723 if (fTrackingMouse == NULL) 4724 return false; 4725 4726 if (fTrackingMouse->selectionRect.IsValid()) 4727 Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset); 4728 4729 _StopMouseTracking(); 4730 // adjust cursor if necessary 4731 _TrackMouse(where, NULL, true); 4732 4733 return true; 4734 } 4735 4736 4737 bool 4738 BTextView::_PerformMouseMoved(BPoint where, uint32 code) 4739 { 4740 fWhere = where; 4741 4742 if (fTrackingMouse == NULL) 4743 return false; 4744 4745 int32 currentOffset = OffsetAt(where); 4746 if (fTrackingMouse->selectionRect.IsValid()) { 4747 // we are tracking the mouse for drag action, if the mouse has moved 4748 // to another index or more than three pixels from where it was clicked, 4749 // we initiate a drag now: 4750 if (currentOffset != fTrackingMouse->clickOffset 4751 || fabs(fTrackingMouse->where.x - where.x) > 3 4752 || fabs(fTrackingMouse->where.y - where.y) > 3) { 4753 _StopMouseTracking(); 4754 _InitiateDrag(); 4755 return true; 4756 } 4757 return false; 4758 } 4759 4760 switch (fClickCount) { 4761 case 3: 4762 // triple click, extend selection linewise 4763 if (currentOffset <= fTrackingMouse->anchor) { 4764 fTrackingMouse->selStart 4765 = (*fLines)[_LineAt(currentOffset)]->offset; 4766 fTrackingMouse->selEnd = fTrackingMouse->shiftDown 4767 ? fSelEnd 4768 : (*fLines)[_LineAt(fTrackingMouse->anchor) + 1]->offset; 4769 } else { 4770 fTrackingMouse->selStart 4771 = fTrackingMouse->shiftDown 4772 ? fSelStart 4773 : (*fLines)[_LineAt(fTrackingMouse->anchor)]->offset; 4774 fTrackingMouse->selEnd 4775 = (*fLines)[_LineAt(currentOffset) + 1]->offset; 4776 } 4777 break; 4778 4779 case 2: 4780 // double click, extend selection wordwise 4781 if (currentOffset <= fTrackingMouse->anchor) { 4782 fTrackingMouse->selStart = _PreviousWordBoundary(currentOffset); 4783 fTrackingMouse->selEnd 4784 = fTrackingMouse->shiftDown 4785 ? fSelEnd 4786 : _NextWordBoundary(fTrackingMouse->anchor); 4787 } else { 4788 fTrackingMouse->selStart 4789 = fTrackingMouse->shiftDown 4790 ? fSelStart 4791 : _PreviousWordBoundary(fTrackingMouse->anchor); 4792 fTrackingMouse->selEnd = _NextWordBoundary(currentOffset); 4793 } 4794 break; 4795 4796 default: 4797 // new click, extend selection char by char 4798 if (currentOffset <= fTrackingMouse->anchor) { 4799 fTrackingMouse->selStart = currentOffset; 4800 fTrackingMouse->selEnd 4801 = fTrackingMouse->shiftDown 4802 ? fSelEnd : fTrackingMouse->anchor; 4803 } else { 4804 fTrackingMouse->selStart 4805 = fTrackingMouse->shiftDown 4806 ? fSelStart : fTrackingMouse->anchor; 4807 fTrackingMouse->selEnd = currentOffset; 4808 } 4809 break; 4810 } 4811 4812 // position caret to follow the direction of the selection 4813 if (fTrackingMouse->selEnd != fSelEnd) 4814 fCaretOffset = fTrackingMouse->selEnd; 4815 else if (fTrackingMouse->selStart != fSelStart) 4816 fCaretOffset = fTrackingMouse->selStart; 4817 4818 Select(fTrackingMouse->selStart, fTrackingMouse->selEnd); 4819 _TrackMouse(where, NULL); 4820 4821 return true; 4822 } 4823 4824 4825 /*! Tracks the mouse position, doing special actions like changing the 4826 view cursor. 4827 4828 \param where The point where the mouse has moved. 4829 \param message The dragging message, if there is any. 4830 \param force Passed as second parameter of SetViewCursor() 4831 */ 4832 void 4833 BTextView::_TrackMouse(BPoint where, const BMessage* message, bool force) 4834 { 4835 BRegion textRegion; 4836 GetTextRegion(fSelStart, fSelEnd, &textRegion); 4837 4838 if (message && AcceptsDrop(message)) 4839 _TrackDrag(where); 4840 else if ((fSelectable || fEditable) 4841 && (fTrackingMouse != NULL || !textRegion.Contains(where))) { 4842 SetViewCursor(B_CURSOR_I_BEAM, force); 4843 } else 4844 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, force); 4845 } 4846 4847 4848 //! Tracks the mouse position when the user is dragging some data. 4849 void 4850 BTextView::_TrackDrag(BPoint where) 4851 { 4852 CALLED(); 4853 if (Bounds().Contains(where)) 4854 _DragCaret(OffsetAt(where)); 4855 } 4856 4857 4858 //! Initiates a drag operation. 4859 void 4860 BTextView::_InitiateDrag() 4861 { 4862 BMessage dragMessage(B_MIME_DATA); 4863 BBitmap* dragBitmap = NULL; 4864 BPoint bitmapPoint; 4865 BHandler* dragHandler = NULL; 4866 4867 GetDragParameters(&dragMessage, &dragBitmap, &bitmapPoint, &dragHandler); 4868 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 4869 4870 if (dragBitmap != NULL) 4871 DragMessage(&dragMessage, dragBitmap, bitmapPoint, dragHandler); 4872 else { 4873 BRegion region; 4874 GetTextRegion(fSelStart, fSelEnd, ®ion); 4875 BRect bounds = Bounds(); 4876 BRect dragRect = region.Frame(); 4877 if (!bounds.Contains(dragRect)) 4878 dragRect = bounds & dragRect; 4879 4880 DragMessage(&dragMessage, dragRect, dragHandler); 4881 } 4882 4883 BMessenger messenger(this); 4884 BMessage message(_DISPOSE_DRAG_); 4885 fDragRunner = new (nothrow) BMessageRunner(messenger, &message, 100000); 4886 } 4887 4888 4889 //! Handles when some data is dropped on the view. 4890 bool 4891 BTextView::_MessageDropped(BMessage* message, BPoint where, BPoint offset) 4892 { 4893 ASSERT(message); 4894 4895 void* from = NULL; 4896 bool internalDrop = false; 4897 if (message->FindPointer("be:originator", &from) == B_OK 4898 && from == this && fSelEnd != fSelStart) 4899 internalDrop = true; 4900 4901 _DragCaret(-1); 4902 4903 delete fDragRunner; 4904 fDragRunner = NULL; 4905 4906 _TrackMouse(where, NULL); 4907 4908 // are we sure we like this message? 4909 if (!AcceptsDrop(message)) 4910 return false; 4911 4912 int32 dropOffset = OffsetAt(where); 4913 if (dropOffset > fText->Length()) 4914 dropOffset = fText->Length(); 4915 4916 // if this view initiated the drag, move instead of copy 4917 if (internalDrop) { 4918 // dropping onto itself? 4919 if (dropOffset >= fSelStart && dropOffset <= fSelEnd) 4920 return true; 4921 } 4922 4923 ssize_t dataLength = 0; 4924 const char* text = NULL; 4925 entry_ref ref; 4926 if (message->FindData("text/plain", B_MIME_TYPE, (const void**)&text, 4927 &dataLength) == B_OK) { 4928 text_run_array* runArray = NULL; 4929 ssize_t runLength = 0; 4930 if (fStylable) { 4931 message->FindData("application/x-vnd.Be-text_run_array", 4932 B_MIME_TYPE, (const void**)&runArray, &runLength); 4933 } 4934 4935 _FilterDisallowedChars((char*)text, dataLength, runArray); 4936 4937 if (dataLength < 1) { 4938 beep(); 4939 return true; 4940 } 4941 4942 if (fUndo) { 4943 delete fUndo; 4944 fUndo = new DropUndoBuffer(this, text, dataLength, runArray, 4945 runLength, dropOffset, internalDrop); 4946 } 4947 4948 if (internalDrop) { 4949 if (dropOffset > fSelEnd) 4950 dropOffset -= dataLength; 4951 Delete(); 4952 } 4953 4954 Insert(dropOffset, text, dataLength, runArray); 4955 if (IsFocus()) 4956 Select(dropOffset, dropOffset + dataLength); 4957 } 4958 4959 return true; 4960 } 4961 4962 4963 void 4964 BTextView::_PerformAutoScrolling() 4965 { 4966 // Scroll the view a bit if mouse is outside the view bounds 4967 BRect bounds = Bounds(); 4968 BPoint scrollBy(B_ORIGIN); 4969 4970 // R5 does a pretty soft auto-scroll, we try to do the same by 4971 // simply scrolling the distance between cursor and border 4972 if (fWhere.x > bounds.right) 4973 scrollBy.x = fWhere.x - bounds.right; 4974 else if (fWhere.x < bounds.left) 4975 scrollBy.x = fWhere.x - bounds.left; // negative value 4976 4977 // prevent from scrolling out of view 4978 if (scrollBy.x != 0.0) { 4979 float rightMax = fTextRect.right + fLayoutData->rightInset; 4980 if (bounds.right + scrollBy.x > rightMax) 4981 scrollBy.x = rightMax - bounds.right; 4982 float leftMin = fTextRect.left - fLayoutData->leftInset; 4983 if (bounds.left + scrollBy.x < leftMin) 4984 scrollBy.x = leftMin - bounds.left; 4985 } 4986 4987 if (CountLines() > 1) { 4988 // scroll in Y only if multiple lines! 4989 if (fWhere.y > bounds.bottom) 4990 scrollBy.y = fWhere.y - bounds.bottom; 4991 else if (fWhere.y < bounds.top) 4992 scrollBy.y = fWhere.y - bounds.top; // negative value 4993 4994 // prevent from scrolling out of view 4995 if (scrollBy.y != 0.0) { 4996 float bottomMax = fTextRect.bottom + fLayoutData->bottomInset; 4997 if (bounds.bottom + scrollBy.y > bottomMax) 4998 scrollBy.y = bottomMax - bounds.bottom; 4999 float topMin = fTextRect.top - fLayoutData->topInset; 5000 if (bounds.top + scrollBy.y < topMin) 5001 scrollBy.y = topMin - bounds.top; 5002 } 5003 } 5004 5005 if (scrollBy != B_ORIGIN) 5006 ScrollBy(scrollBy.x, scrollBy.y); 5007 } 5008 5009 5010 //! Updates the scrollbars associated with the object (if any). 5011 void 5012 BTextView::_UpdateScrollbars() 5013 { 5014 BRect bounds(Bounds()); 5015 BScrollBar* horizontalScrollBar = ScrollBar(B_HORIZONTAL); 5016 BScrollBar* verticalScrollBar = ScrollBar(B_VERTICAL); 5017 5018 // do we have a horizontal scroll bar? 5019 if (horizontalScrollBar != NULL) { 5020 long viewWidth = bounds.IntegerWidth(); 5021 long dataWidth = (long)ceilf(fTextRect.IntegerWidth() 5022 + fLayoutData->leftInset + fLayoutData->rightInset); 5023 5024 long maxRange = dataWidth - viewWidth; 5025 maxRange = std::max(maxRange, 0l); 5026 5027 horizontalScrollBar->SetRange(0, (float)maxRange); 5028 horizontalScrollBar->SetProportion((float)viewWidth 5029 / (float)dataWidth); 5030 horizontalScrollBar->SetSteps(kHorizontalScrollBarStep, 5031 dataWidth / 10); 5032 } 5033 5034 // how about a vertical scroll bar? 5035 if (verticalScrollBar != NULL) { 5036 long viewHeight = bounds.IntegerHeight(); 5037 long dataHeight = (long)ceilf(fLayoutData->topInset 5038 + fTextRect.IntegerHeight() + fLayoutData->bottomInset); 5039 5040 long maxRange = dataHeight - viewHeight; 5041 maxRange = std::max(maxRange, 0l); 5042 5043 verticalScrollBar->SetRange(0, maxRange); 5044 verticalScrollBar->SetProportion((float)viewHeight 5045 / (float)dataHeight); 5046 verticalScrollBar->SetSteps(kVerticalScrollBarStep, 5047 viewHeight); 5048 } 5049 } 5050 5051 5052 //! Scrolls by the given offsets 5053 void 5054 BTextView::_ScrollBy(float horizontal, float vertical) 5055 { 5056 BRect bounds = Bounds(); 5057 _ScrollTo(bounds.left + horizontal, bounds.top + vertical); 5058 } 5059 5060 5061 //! Scrolls to the given position, making sure not to scroll out of bounds. 5062 void 5063 BTextView::_ScrollTo(float x, float y) 5064 { 5065 BRect bounds = Bounds(); 5066 long viewWidth = bounds.IntegerWidth(); 5067 long viewHeight = bounds.IntegerHeight(); 5068 5069 if (x > fTextRect.right - viewWidth) 5070 x = fTextRect.right - viewWidth; 5071 if (x < 0.0) 5072 x = 0.0; 5073 5074 if (y > fTextRect.bottom + fLayoutData->bottomInset - viewHeight) 5075 y = fTextRect.bottom + fLayoutData->bottomInset - viewHeight; 5076 if (y < 0.0) 5077 y = 0.0; 5078 5079 ScrollTo(x, y); 5080 } 5081 5082 5083 //! Autoresizes the view to fit the contained text. 5084 void 5085 BTextView::_AutoResize(bool redraw) 5086 { 5087 if (!fResizable || fContainerView == NULL) 5088 return; 5089 5090 // NOTE: This container view thing is only used by Tracker. 5091 // move container view if not left aligned 5092 float oldWidth = Bounds().Width(); 5093 float newWidth = fLayoutData->leftInset + fTextRect.Width() 5094 + fLayoutData->rightInset; 5095 float right = oldWidth - newWidth; 5096 5097 if (fAlignment == B_ALIGN_CENTER) 5098 fContainerView->MoveBy(roundf(right / 2), 0); 5099 else if (fAlignment == B_ALIGN_RIGHT) 5100 fContainerView->MoveBy(right, 0); 5101 5102 // resize container view 5103 float grow = newWidth - oldWidth; 5104 fContainerView->ResizeBy(grow, 0); 5105 5106 // reposition text view 5107 fTextRect.OffsetTo(fLayoutData->leftInset, fLayoutData->topInset); 5108 5109 // scroll rect to start, there is room for full text 5110 ScrollToOffset(0); 5111 5112 if (redraw) 5113 _RequestDrawLines(0, 0); 5114 } 5115 5116 5117 //! Creates a new offscreen BBitmap with an associated BView. 5118 void 5119 BTextView::_NewOffscreen(float padding) 5120 { 5121 if (fOffscreen != NULL) 5122 _DeleteOffscreen(); 5123 5124 #if USE_DOUBLEBUFFERING 5125 BRect bitmapRect(0, 0, fTextRect.Width() + padding, fTextRect.Height()); 5126 fOffscreen = new BBitmap(bitmapRect, fColorSpace, true, false); 5127 if (fOffscreen != NULL && fOffscreen->Lock()) { 5128 BView* bufferView = new BView(bitmapRect, "drawing view", 0, 0); 5129 fOffscreen->AddChild(bufferView); 5130 fOffscreen->Unlock(); 5131 } 5132 #endif 5133 } 5134 5135 5136 //! Deletes the textview's offscreen bitmap, if any. 5137 void 5138 BTextView::_DeleteOffscreen() 5139 { 5140 if (fOffscreen != NULL && fOffscreen->Lock()) { 5141 delete fOffscreen; 5142 fOffscreen = NULL; 5143 } 5144 } 5145 5146 5147 /*! Creates a new offscreen bitmap, highlight the selection, and set the 5148 cursor to \c B_CURSOR_I_BEAM. 5149 */ 5150 void 5151 BTextView::_Activate() 5152 { 5153 fActive = true; 5154 5155 // Create a new offscreen BBitmap 5156 _NewOffscreen(); 5157 5158 if (fSelStart != fSelEnd) { 5159 if (fSelectable) 5160 Highlight(fSelStart, fSelEnd); 5161 } else 5162 _ShowCaret(); 5163 5164 BPoint where; 5165 uint32 buttons; 5166 GetMouse(&where, &buttons, false); 5167 if (Bounds().Contains(where)) 5168 _TrackMouse(where, NULL); 5169 5170 if (Window() != NULL) { 5171 BMessage* message; 5172 5173 if (!Window()->HasShortcut(B_LEFT_ARROW, B_COMMAND_KEY) 5174 && !Window()->HasShortcut(B_RIGHT_ARROW, B_COMMAND_KEY)) { 5175 message = new BMessage(kMsgNavigateArrow); 5176 message->AddInt32("key", B_LEFT_ARROW); 5177 message->AddInt32("modifiers", B_COMMAND_KEY); 5178 Window()->AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY, message, this); 5179 5180 message = new BMessage(kMsgNavigateArrow); 5181 message->AddInt32("key", B_RIGHT_ARROW); 5182 message->AddInt32("modifiers", B_COMMAND_KEY); 5183 Window()->AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY, message, this); 5184 5185 fInstalledNavigateCommandWordwiseShortcuts = true; 5186 } 5187 if (!Window()->HasShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY) 5188 && !Window()->HasShortcut(B_RIGHT_ARROW, 5189 B_COMMAND_KEY | B_SHIFT_KEY)) { 5190 message = new BMessage(kMsgNavigateArrow); 5191 message->AddInt32("key", B_LEFT_ARROW); 5192 message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY); 5193 Window()->AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY, 5194 message, this); 5195 5196 message = new BMessage(kMsgNavigateArrow); 5197 message->AddInt32("key", B_RIGHT_ARROW); 5198 message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY); 5199 Window()->AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY, 5200 message, this); 5201 5202 fInstalledSelectCommandWordwiseShortcuts = true; 5203 } 5204 if (!Window()->HasShortcut(B_DELETE, B_COMMAND_KEY) 5205 && !Window()->HasShortcut(B_BACKSPACE, B_COMMAND_KEY)) { 5206 message = new BMessage(kMsgRemoveWord); 5207 message->AddInt32("key", B_DELETE); 5208 message->AddInt32("modifiers", B_COMMAND_KEY); 5209 Window()->AddShortcut(B_DELETE, B_COMMAND_KEY, message, this); 5210 5211 message = new BMessage(kMsgRemoveWord); 5212 message->AddInt32("key", B_BACKSPACE); 5213 message->AddInt32("modifiers", B_COMMAND_KEY); 5214 Window()->AddShortcut(B_BACKSPACE, B_COMMAND_KEY, message, this); 5215 5216 fInstalledRemoveCommandWordwiseShortcuts = true; 5217 } 5218 5219 if (!Window()->HasShortcut(B_LEFT_ARROW, B_OPTION_KEY) 5220 && !Window()->HasShortcut(B_RIGHT_ARROW, B_OPTION_KEY)) { 5221 message = new BMessage(kMsgNavigateArrow); 5222 message->AddInt32("key", B_LEFT_ARROW); 5223 message->AddInt32("modifiers", B_OPTION_KEY); 5224 Window()->AddShortcut(B_LEFT_ARROW, B_OPTION_KEY, message, this); 5225 5226 message = new BMessage(kMsgNavigateArrow); 5227 message->AddInt32("key", B_RIGHT_ARROW); 5228 message->AddInt32("modifiers", B_OPTION_KEY); 5229 Window()->AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY, message, this); 5230 5231 fInstalledNavigateOptionWordwiseShortcuts = true; 5232 } 5233 if (!Window()->HasShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_SHIFT_KEY) 5234 && !Window()->HasShortcut(B_RIGHT_ARROW, 5235 B_OPTION_KEY | B_SHIFT_KEY)) { 5236 message = new BMessage(kMsgNavigateArrow); 5237 message->AddInt32("key", B_LEFT_ARROW); 5238 message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY); 5239 Window()->AddShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_SHIFT_KEY, 5240 message, this); 5241 5242 message = new BMessage(kMsgNavigateArrow); 5243 message->AddInt32("key", B_RIGHT_ARROW); 5244 message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY); 5245 Window()->AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_SHIFT_KEY, 5246 message, this); 5247 5248 fInstalledSelectOptionWordwiseShortcuts = true; 5249 } 5250 if (!Window()->HasShortcut(B_DELETE, B_OPTION_KEY) 5251 && !Window()->HasShortcut(B_BACKSPACE, B_OPTION_KEY)) { 5252 message = new BMessage(kMsgRemoveWord); 5253 message->AddInt32("key", B_DELETE); 5254 message->AddInt32("modifiers", B_OPTION_KEY); 5255 Window()->AddShortcut(B_DELETE, B_OPTION_KEY, message, this); 5256 5257 message = new BMessage(kMsgRemoveWord); 5258 message->AddInt32("key", B_BACKSPACE); 5259 message->AddInt32("modifiers", B_OPTION_KEY); 5260 Window()->AddShortcut(B_BACKSPACE, B_OPTION_KEY, message, this); 5261 5262 fInstalledRemoveOptionWordwiseShortcuts = true; 5263 } 5264 5265 if (!Window()->HasShortcut(B_UP_ARROW, B_OPTION_KEY) 5266 && !Window()->HasShortcut(B_DOWN_ARROW, B_OPTION_KEY)) { 5267 message = new BMessage(kMsgNavigateArrow); 5268 message->AddInt32("key", B_UP_ARROW); 5269 message->AddInt32("modifiers", B_OPTION_KEY); 5270 Window()->AddShortcut(B_UP_ARROW, B_OPTION_KEY, message, this); 5271 5272 message = new BMessage(kMsgNavigateArrow); 5273 message->AddInt32("key", B_DOWN_ARROW); 5274 message->AddInt32("modifiers", B_OPTION_KEY); 5275 Window()->AddShortcut(B_DOWN_ARROW, B_OPTION_KEY, message, this); 5276 5277 fInstalledNavigateOptionLinewiseShortcuts = true; 5278 } 5279 if (!Window()->HasShortcut(B_UP_ARROW, B_OPTION_KEY | B_SHIFT_KEY) 5280 && !Window()->HasShortcut(B_DOWN_ARROW, 5281 B_OPTION_KEY | B_SHIFT_KEY)) { 5282 message = new BMessage(kMsgNavigateArrow); 5283 message->AddInt32("key", B_UP_ARROW); 5284 message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY); 5285 Window()->AddShortcut(B_UP_ARROW, B_OPTION_KEY | B_SHIFT_KEY, 5286 message, this); 5287 5288 message = new BMessage(kMsgNavigateArrow); 5289 message->AddInt32("key", B_DOWN_ARROW); 5290 message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY); 5291 Window()->AddShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_SHIFT_KEY, 5292 message, this); 5293 5294 fInstalledSelectOptionLinewiseShortcuts = true; 5295 } 5296 5297 if (!Window()->HasShortcut(B_HOME, B_COMMAND_KEY) 5298 && !Window()->HasShortcut(B_END, B_COMMAND_KEY)) { 5299 message = new BMessage(kMsgNavigatePage); 5300 message->AddInt32("key", B_HOME); 5301 message->AddInt32("modifiers", B_COMMAND_KEY); 5302 Window()->AddShortcut(B_HOME, B_COMMAND_KEY, message, this); 5303 5304 message = new BMessage(kMsgNavigatePage); 5305 message->AddInt32("key", B_END); 5306 message->AddInt32("modifiers", B_COMMAND_KEY); 5307 Window()->AddShortcut(B_END, B_COMMAND_KEY, message, this); 5308 5309 fInstalledNavigateHomeEndDocwiseShortcuts = true; 5310 } 5311 if (!Window()->HasShortcut(B_HOME, B_COMMAND_KEY | B_SHIFT_KEY) 5312 && !Window()->HasShortcut(B_END, B_COMMAND_KEY | B_SHIFT_KEY)) { 5313 message = new BMessage(kMsgNavigatePage); 5314 message->AddInt32("key", B_HOME); 5315 message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY); 5316 Window()->AddShortcut(B_HOME, B_COMMAND_KEY | B_SHIFT_KEY, 5317 message, this); 5318 5319 message = new BMessage(kMsgNavigatePage); 5320 message->AddInt32("key", B_END); 5321 message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY); 5322 Window()->AddShortcut(B_END, B_COMMAND_KEY | B_SHIFT_KEY, 5323 message, this); 5324 5325 fInstalledSelectHomeEndDocwiseShortcuts = true; 5326 } 5327 } 5328 } 5329 5330 5331 //! Unhilights the selection, set the cursor to \c B_CURSOR_SYSTEM_DEFAULT. 5332 void 5333 BTextView::_Deactivate() 5334 { 5335 fActive = false; 5336 5337 _CancelInputMethod(); 5338 _DeleteOffscreen(); 5339 5340 if (fSelStart != fSelEnd) { 5341 if (fSelectable) 5342 Highlight(fSelStart, fSelEnd); 5343 } else 5344 _HideCaret(); 5345 5346 if (Window() != NULL) { 5347 if (fInstalledNavigateCommandWordwiseShortcuts) { 5348 Window()->RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY); 5349 Window()->RemoveShortcut(B_RIGHT_ARROW, B_COMMAND_KEY); 5350 fInstalledNavigateCommandWordwiseShortcuts = false; 5351 } 5352 if (fInstalledSelectCommandWordwiseShortcuts) { 5353 Window()->RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY); 5354 Window()->RemoveShortcut(B_RIGHT_ARROW, 5355 B_COMMAND_KEY | B_SHIFT_KEY); 5356 fInstalledSelectCommandWordwiseShortcuts = false; 5357 } 5358 if (fInstalledRemoveCommandWordwiseShortcuts) { 5359 Window()->RemoveShortcut(B_DELETE, B_COMMAND_KEY); 5360 Window()->RemoveShortcut(B_BACKSPACE, B_COMMAND_KEY); 5361 fInstalledRemoveCommandWordwiseShortcuts = false; 5362 } 5363 5364 if (fInstalledNavigateOptionWordwiseShortcuts) { 5365 Window()->RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY); 5366 Window()->RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY); 5367 fInstalledNavigateOptionWordwiseShortcuts = false; 5368 } 5369 if (fInstalledSelectOptionWordwiseShortcuts) { 5370 Window()->RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_SHIFT_KEY); 5371 Window()->RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_SHIFT_KEY); 5372 fInstalledSelectOptionWordwiseShortcuts = false; 5373 } 5374 if (fInstalledRemoveOptionWordwiseShortcuts) { 5375 Window()->RemoveShortcut(B_DELETE, B_OPTION_KEY); 5376 Window()->RemoveShortcut(B_BACKSPACE, B_OPTION_KEY); 5377 fInstalledRemoveOptionWordwiseShortcuts = false; 5378 } 5379 5380 if (fInstalledNavigateOptionLinewiseShortcuts) { 5381 Window()->RemoveShortcut(B_UP_ARROW, B_OPTION_KEY); 5382 Window()->RemoveShortcut(B_DOWN_ARROW, B_OPTION_KEY); 5383 fInstalledNavigateOptionLinewiseShortcuts = false; 5384 } 5385 if (fInstalledSelectOptionLinewiseShortcuts) { 5386 Window()->RemoveShortcut(B_UP_ARROW, B_OPTION_KEY | B_SHIFT_KEY); 5387 Window()->RemoveShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_SHIFT_KEY); 5388 fInstalledSelectOptionLinewiseShortcuts = false; 5389 } 5390 5391 if (fInstalledNavigateHomeEndDocwiseShortcuts) { 5392 Window()->RemoveShortcut(B_HOME, B_COMMAND_KEY); 5393 Window()->RemoveShortcut(B_END, B_COMMAND_KEY); 5394 fInstalledNavigateHomeEndDocwiseShortcuts = false; 5395 } 5396 if (fInstalledSelectHomeEndDocwiseShortcuts) { 5397 Window()->RemoveShortcut(B_HOME, B_COMMAND_KEY | B_SHIFT_KEY); 5398 Window()->RemoveShortcut(B_END, B_COMMAND_KEY | B_SHIFT_KEY); 5399 fInstalledSelectHomeEndDocwiseShortcuts = false; 5400 } 5401 } 5402 } 5403 5404 5405 /*! Changes the passed in font to be displayable by the object. 5406 5407 Set font rotation to 0, removes any font flag, set font spacing 5408 to \c B_BITMAP_SPACING and font encoding to \c B_UNICODE_UTF8. 5409 */ 5410 void 5411 BTextView::_NormalizeFont(BFont* font) 5412 { 5413 if (font) { 5414 font->SetRotation(0.0f); 5415 font->SetFlags(0); 5416 font->SetSpacing(B_BITMAP_SPACING); 5417 font->SetEncoding(B_UNICODE_UTF8); 5418 } 5419 } 5420 5421 5422 void 5423 BTextView::_SetRunArray(int32 startOffset, int32 endOffset, 5424 const text_run_array* runs) 5425 { 5426 const int32 numStyles = runs->count; 5427 if (numStyles > 0) { 5428 const text_run* theRun = &runs->runs[0]; 5429 for (int32 index = 0; index < numStyles; index++) { 5430 int32 fromOffset = theRun->offset + startOffset; 5431 int32 toOffset = endOffset; 5432 if (index + 1 < numStyles) { 5433 toOffset = (theRun + 1)->offset + startOffset; 5434 toOffset = (toOffset > endOffset) ? endOffset : toOffset; 5435 } 5436 5437 _ApplyStyleRange(fromOffset, toOffset, B_FONT_ALL, &theRun->font, 5438 &theRun->color, false); 5439 5440 theRun++; 5441 } 5442 fStyles->InvalidateNullStyle(); 5443 } 5444 } 5445 5446 5447 /*! Returns the character class of the character at the given offset. 5448 5449 \param offset The offset where the wanted character can be found. 5450 5451 \return A value which represents the character's classification. 5452 */ 5453 uint32 5454 BTextView::_CharClassification(int32 offset) const 5455 { 5456 // TODO: Should check against a list of characters containing also 5457 // japanese word breakers. 5458 // And what about other languages ? Isn't there a better way to check 5459 // for separator characters ? 5460 // Andrew suggested to have a look at UnicodeBlockObject.h 5461 switch (fText->RealCharAt(offset)) { 5462 case '\0': 5463 return CHAR_CLASS_END_OF_TEXT; 5464 5465 case B_SPACE: 5466 case B_TAB: 5467 case B_ENTER: 5468 return CHAR_CLASS_WHITESPACE; 5469 5470 case '=': 5471 case '+': 5472 case '@': 5473 case '#': 5474 case '$': 5475 case '%': 5476 case '^': 5477 case '&': 5478 case '*': 5479 case '\\': 5480 case '|': 5481 case '<': 5482 case '>': 5483 case '/': 5484 case '~': 5485 return CHAR_CLASS_GRAPHICAL; 5486 5487 case '\'': 5488 case '"': 5489 return CHAR_CLASS_QUOTE; 5490 5491 case ',': 5492 case '.': 5493 case '?': 5494 case '!': 5495 case ';': 5496 case ':': 5497 case '-': 5498 return CHAR_CLASS_PUNCTUATION; 5499 5500 case '(': 5501 case '[': 5502 case '{': 5503 return CHAR_CLASS_PARENS_OPEN; 5504 5505 case ')': 5506 case ']': 5507 case '}': 5508 return CHAR_CLASS_PARENS_CLOSE; 5509 5510 default: 5511 return CHAR_CLASS_DEFAULT; 5512 } 5513 } 5514 5515 5516 /*! Returns the offset of the next UTF-8 character. 5517 5518 \param offset The offset where to start looking. 5519 5520 \return The offset of the next UTF-8 character. 5521 */ 5522 int32 5523 BTextView::_NextInitialByte(int32 offset) const 5524 { 5525 if (offset >= fText->Length()) 5526 return offset; 5527 5528 for (++offset; (ByteAt(offset) & 0xC0) == 0x80; ++offset) 5529 ; 5530 5531 return offset; 5532 } 5533 5534 5535 /*! Returns the offset of the previous UTF-8 character. 5536 5537 \param offset The offset where to start looking. 5538 5539 \return The offset of the previous UTF-8 character. 5540 */ 5541 int32 5542 BTextView::_PreviousInitialByte(int32 offset) const 5543 { 5544 if (offset <= 0) 5545 return 0; 5546 5547 int32 count = 6; 5548 5549 for (--offset; offset > 0 && count; --offset, --count) { 5550 if ((ByteAt(offset) & 0xC0) != 0x80) 5551 break; 5552 } 5553 5554 return count ? offset : 0; 5555 } 5556 5557 5558 bool 5559 BTextView::_GetProperty(BMessage* message, BMessage* specifier, 5560 const char* property, BMessage* reply) 5561 { 5562 CALLED(); 5563 if (strcmp(property, "selection") == 0) { 5564 reply->what = B_REPLY; 5565 reply->AddInt32("result", fSelStart); 5566 reply->AddInt32("result", fSelEnd); 5567 reply->AddInt32("error", B_OK); 5568 5569 return true; 5570 } else if (strcmp(property, "Text") == 0) { 5571 if (IsTypingHidden()) { 5572 // Do not allow stealing passwords via scripting 5573 beep(); 5574 return false; 5575 } 5576 5577 int32 index, range; 5578 specifier->FindInt32("index", &index); 5579 specifier->FindInt32("range", &range); 5580 5581 char* buffer = new char[range + 1]; 5582 GetText(index, range, buffer); 5583 5584 reply->what = B_REPLY; 5585 reply->AddString("result", buffer); 5586 reply->AddInt32("error", B_OK); 5587 5588 delete[] buffer; 5589 5590 return true; 5591 } else if (strcmp(property, "text_run_array") == 0) 5592 return false; 5593 5594 return false; 5595 } 5596 5597 5598 bool 5599 BTextView::_SetProperty(BMessage* message, BMessage* specifier, 5600 const char* property, BMessage* reply) 5601 { 5602 CALLED(); 5603 if (strcmp(property, "selection") == 0) { 5604 int32 index, range; 5605 5606 specifier->FindInt32("index", &index); 5607 specifier->FindInt32("range", &range); 5608 5609 Select(index, index + range); 5610 5611 reply->what = B_REPLY; 5612 reply->AddInt32("error", B_OK); 5613 5614 return true; 5615 } else if (strcmp(property, "Text") == 0) { 5616 int32 index, range; 5617 specifier->FindInt32("index", &index); 5618 specifier->FindInt32("range", &range); 5619 5620 const char* buffer = NULL; 5621 if (message->FindString("data", &buffer) == B_OK) { 5622 InsertText(buffer, range, index, NULL); 5623 _Refresh(index, index + range); 5624 } else { 5625 DeleteText(index, index + range); 5626 _Refresh(index, index); 5627 } 5628 5629 reply->what = B_REPLY; 5630 reply->AddInt32("error", B_OK); 5631 5632 return true; 5633 } else if (strcmp(property, "text_run_array") == 0) 5634 return false; 5635 5636 return false; 5637 } 5638 5639 5640 bool 5641 BTextView::_CountProperties(BMessage* message, BMessage* specifier, 5642 const char* property, BMessage* reply) 5643 { 5644 CALLED(); 5645 if (strcmp(property, "Text") == 0) { 5646 reply->what = B_REPLY; 5647 reply->AddInt32("result", fText->Length()); 5648 reply->AddInt32("error", B_OK); 5649 return true; 5650 } 5651 5652 return false; 5653 } 5654 5655 5656 //! Called when the object receives a \c B_INPUT_METHOD_CHANGED message. 5657 void 5658 BTextView::_HandleInputMethodChanged(BMessage* message) 5659 { 5660 // TODO: block input if not editable (Andrew) 5661 ASSERT(fInline != NULL); 5662 5663 const char* string = NULL; 5664 if (message->FindString("be:string", &string) < B_OK || string == NULL) 5665 return; 5666 5667 _HideCaret(); 5668 5669 if (IsFocus()) 5670 be_app->ObscureCursor(); 5671 5672 // If we find the "be:confirmed" boolean (and the boolean is true), 5673 // it means it's over for now, so the current InlineInput object 5674 // should become inactive. We will probably receive a 5675 // B_INPUT_METHOD_STOPPED message after this one. 5676 bool confirmed; 5677 if (message->FindBool("be:confirmed", &confirmed) != B_OK) 5678 confirmed = false; 5679 5680 // Delete the previously inserted text (if any) 5681 if (fInline->IsActive()) { 5682 const int32 oldOffset = fInline->Offset(); 5683 DeleteText(oldOffset, oldOffset + fInline->Length()); 5684 if (confirmed) 5685 fInline->SetActive(false); 5686 fCaretOffset = fSelStart = fSelEnd = oldOffset; 5687 } 5688 5689 const int32 stringLen = strlen(string); 5690 5691 fInline->SetOffset(fSelStart); 5692 fInline->SetLength(stringLen); 5693 fInline->ResetClauses(); 5694 5695 if (!confirmed && !fInline->IsActive()) 5696 fInline->SetActive(true); 5697 5698 // Get the clauses, and pass them to the InlineInput object 5699 // TODO: Find out if what we did it's ok, currently we don't consider 5700 // clauses at all, while the bebook says we should; though the visual 5701 // effect we obtained seems correct. Weird. 5702 int32 clauseCount = 0; 5703 int32 clauseStart; 5704 int32 clauseEnd; 5705 while (message->FindInt32("be:clause_start", clauseCount, &clauseStart) 5706 == B_OK 5707 && message->FindInt32("be:clause_end", clauseCount, &clauseEnd) 5708 == B_OK) { 5709 if (!fInline->AddClause(clauseStart, clauseEnd)) 5710 break; 5711 clauseCount++; 5712 } 5713 5714 if (confirmed) { 5715 _Refresh(fSelStart, fSelEnd, fSelEnd); 5716 _ShowCaret(); 5717 5718 // now we need to feed ourselves the individual characters as if the 5719 // user would have pressed them now - this lets KeyDown() pick out all 5720 // the special characters like B_BACKSPACE, cursor keys and the like: 5721 const char* currPos = string; 5722 const char* prevPos = currPos; 5723 while (*currPos != '\0') { 5724 if ((*currPos & 0xC0) == 0xC0) { 5725 // found the start of an UTF-8 char, we collect while it lasts 5726 ++currPos; 5727 while ((*currPos & 0xC0) == 0x80) 5728 ++currPos; 5729 } else if ((*currPos & 0xC0) == 0x80) { 5730 // illegal: character starts with utf-8 intermediate byte, 5731 // skip it 5732 prevPos = ++currPos; 5733 } else { 5734 // single byte character/code, just feed that 5735 ++currPos; 5736 } 5737 KeyDown(prevPos, currPos - prevPos); 5738 prevPos = currPos; 5739 } 5740 5741 _Refresh(fSelStart, fSelEnd, fSelEnd); 5742 } else { 5743 // temporarily show transient state of inline input 5744 int32 selectionStart = 0; 5745 int32 selectionEnd = 0; 5746 message->FindInt32("be:selection", 0, &selectionStart); 5747 message->FindInt32("be:selection", 1, &selectionEnd); 5748 5749 fInline->SetSelectionOffset(selectionStart); 5750 fInline->SetSelectionLength(selectionEnd - selectionStart); 5751 5752 const int32 inlineOffset = fInline->Offset(); 5753 InsertText(string, stringLen, fSelStart, NULL); 5754 5755 _Refresh(inlineOffset, fSelEnd, fSelEnd); 5756 _ShowCaret(); 5757 } 5758 } 5759 5760 5761 /*! Called when the object receives a \c B_INPUT_METHOD_LOCATION_REQUEST 5762 message. 5763 */ 5764 void 5765 BTextView::_HandleInputMethodLocationRequest() 5766 { 5767 ASSERT(fInline != NULL); 5768 5769 int32 offset = fInline->Offset(); 5770 const int32 limit = offset + fInline->Length(); 5771 5772 BMessage message(B_INPUT_METHOD_EVENT); 5773 message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST); 5774 5775 // Add the location of the UTF8 characters 5776 while (offset < limit) { 5777 float height; 5778 BPoint where = PointAt(offset, &height); 5779 ConvertToScreen(&where); 5780 5781 message.AddPoint("be:location_reply", where); 5782 message.AddFloat("be:height_reply", height); 5783 5784 offset = _NextInitialByte(offset); 5785 } 5786 5787 fInline->Method()->SendMessage(&message); 5788 } 5789 5790 5791 //! Tells the Input Server method add-on to stop the current transaction. 5792 void 5793 BTextView::_CancelInputMethod() 5794 { 5795 if (!fInline) 5796 return; 5797 5798 InlineInput* inlineInput = fInline; 5799 fInline = NULL; 5800 5801 if (inlineInput->IsActive() && Window()) { 5802 _Refresh(inlineInput->Offset(), fText->Length() 5803 - inlineInput->Offset()); 5804 5805 BMessage message(B_INPUT_METHOD_EVENT); 5806 message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED); 5807 inlineInput->Method()->SendMessage(&message); 5808 } 5809 5810 delete inlineInput; 5811 } 5812 5813 5814 /*! Returns the line number of the character at the given \a offset. 5815 5816 \note This will never return the last line (use LineAt() if you 5817 need to be correct about that.) N.B. 5818 5819 \param offset The offset of the wanted character. 5820 5821 \return The line number of the character at the given \a offset as an int32. 5822 */ 5823 int32 5824 BTextView::_LineAt(int32 offset) const 5825 { 5826 return fLines->OffsetToLine(offset); 5827 } 5828 5829 5830 /*! Returns the line number that the given \a point is on. 5831 5832 \note This will never return the last line (use LineAt() if you 5833 need to be correct about that.) N.B. 5834 5835 \param point The \a point the get the line number of. 5836 5837 \return The line number of the given \a point as an int32. 5838 */ 5839 int32 5840 BTextView::_LineAt(const BPoint& point) const 5841 { 5842 return fLines->PixelToLine(point.y - fTextRect.top); 5843 } 5844 5845 5846 /*! Returns whether or not the given \a offset is on the empty line at the end 5847 of the buffer. 5848 */ 5849 bool 5850 BTextView::_IsOnEmptyLastLine(int32 offset) const 5851 { 5852 return (offset == fText->Length() && offset > 0 5853 && fText->RealCharAt(offset - 1) == B_ENTER); 5854 } 5855 5856 5857 void 5858 BTextView::_ApplyStyleRange(int32 fromOffset, int32 toOffset, uint32 mode, 5859 const BFont* font, const rgb_color* color, bool syncNullStyle) 5860 { 5861 BFont normalized; 5862 // Declared before the if so it stays allocated until the call to 5863 // SetStyleRange 5864 if (font != NULL) { 5865 // if a font has been given, normalize it 5866 normalized = *font; 5867 _NormalizeFont(&normalized); 5868 font = &normalized; 5869 } 5870 5871 if (!fStylable) { 5872 // always apply font and color to full range for non-stylable textviews 5873 fromOffset = 0; 5874 toOffset = fText->Length(); 5875 } 5876 5877 if (syncNullStyle) 5878 fStyles->SyncNullStyle(fromOffset); 5879 5880 fStyles->SetStyleRange(fromOffset, toOffset, fText->Length(), mode, 5881 font, color); 5882 } 5883 5884 5885 float 5886 BTextView::_NullStyleHeight() const 5887 { 5888 const BFont* font = NULL; 5889 fStyles->GetNullStyle(&font, NULL); 5890 5891 font_height fontHeight; 5892 font->GetHeight(&fontHeight); 5893 return ceilf(fontHeight.ascent + fontHeight.descent + 1); 5894 } 5895 5896 5897 void 5898 BTextView::_ShowContextMenu(BPoint where) 5899 { 5900 bool isRedo; 5901 undo_state state = UndoState(&isRedo); 5902 bool isUndo = state != B_UNDO_UNAVAILABLE && !isRedo; 5903 5904 int32 start; 5905 int32 finish; 5906 GetSelection(&start, &finish); 5907 5908 bool canEdit = IsEditable(); 5909 int32 length = fText->Length(); 5910 5911 BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 5912 5913 BLayoutBuilder::Menu<>(menu) 5914 .AddItem(TRANSLATE("Undo"), B_UNDO/*, 'Z'*/) 5915 .SetEnabled(canEdit && isUndo) 5916 .AddItem(TRANSLATE("Redo"), B_UNDO/*, 'Z', B_SHIFT_KEY*/) 5917 .SetEnabled(canEdit && isRedo) 5918 .AddSeparator() 5919 .AddItem(TRANSLATE("Cut"), B_CUT, 'X') 5920 .SetEnabled(canEdit && start != finish) 5921 .AddItem(TRANSLATE("Copy"), B_COPY, 'C') 5922 .SetEnabled(start != finish) 5923 .AddItem(TRANSLATE("Paste"), B_PASTE, 'V') 5924 .SetEnabled(canEdit && be_clipboard->SystemCount() > 0) 5925 .AddSeparator() 5926 .AddItem(TRANSLATE("Select all"), B_SELECT_ALL, 'A') 5927 .SetEnabled(!(start == 0 && finish == length)) 5928 ; 5929 5930 menu->SetTargetForItems(this); 5931 ConvertToScreen(&where); 5932 menu->Go(where, true, true, true); 5933 } 5934 5935 5936 void 5937 BTextView::_FilterDisallowedChars(char* text, ssize_t& length, 5938 text_run_array* runArray) 5939 { 5940 if (!fDisallowedChars) 5941 return; 5942 5943 if (fDisallowedChars->IsEmpty() || !text) 5944 return; 5945 5946 ssize_t stringIndex = 0; 5947 if (runArray) { 5948 ssize_t remNext = 0; 5949 5950 for (int i = 0; i < runArray->count; i++) { 5951 runArray->runs[i].offset -= remNext; 5952 while (stringIndex < runArray->runs[i].offset 5953 && stringIndex < length) { 5954 if (fDisallowedChars->HasItem( 5955 reinterpret_cast<void*>(text[stringIndex]))) { 5956 memmove(text + stringIndex, text + stringIndex + 1, 5957 length - stringIndex - 1); 5958 length--; 5959 runArray->runs[i].offset--; 5960 remNext++; 5961 } else 5962 stringIndex++; 5963 } 5964 } 5965 } 5966 5967 while (stringIndex < length) { 5968 if (fDisallowedChars->HasItem( 5969 reinterpret_cast<void*>(text[stringIndex]))) { 5970 memmove(text + stringIndex, text + stringIndex + 1, 5971 length - stringIndex - 1); 5972 length--; 5973 } else 5974 stringIndex++; 5975 } 5976 } 5977 5978 5979 void 5980 BTextView::_UpdateInsets(const BRect& rect) 5981 { 5982 // do not update insets if SetInsets() was called 5983 if (fLayoutData->overridden) 5984 return; 5985 5986 const BRect& bounds = Bounds(); 5987 5988 // we disallow negative insets, as they would cause parts of the 5989 // text to be hidden 5990 fLayoutData->leftInset = rect.left >= bounds.left 5991 ? rect.left - bounds.left : 0; 5992 fLayoutData->topInset = rect.top >= bounds.top 5993 ? rect.top - bounds.top : 0; 5994 fLayoutData->rightInset = bounds.right >= rect.right 5995 ? bounds.right - rect.right : 0; 5996 fLayoutData->bottomInset = bounds.bottom >= rect.bottom 5997 ? bounds.bottom - rect.bottom : 0; 5998 5999 // only add default insets if text rect is set to bounds 6000 if (rect == bounds && (fEditable || fSelectable)) { 6001 float hPadding = be_control_look->DefaultLabelSpacing(); 6002 float hInset = floorf(hPadding / 2.0f); 6003 float vInset = 1; 6004 fLayoutData->leftInset += hInset; 6005 fLayoutData->topInset += vInset; 6006 fLayoutData->rightInset += hInset; 6007 fLayoutData->bottomInset += vInset; 6008 } 6009 } 6010 6011 6012 // #pragma mark - BTextView::TextTrackState 6013 6014 6015 BTextView::TextTrackState::TextTrackState(BMessenger messenger) 6016 : 6017 clickOffset(0), 6018 shiftDown(false), 6019 anchor(0), 6020 selStart(0), 6021 selEnd(0), 6022 fRunner(NULL) 6023 { 6024 BMessage message(_PING_); 6025 const bigtime_t scrollSpeed = 25 * 1000; // 40 scroll steps per second 6026 fRunner = new (nothrow) BMessageRunner(messenger, &message, scrollSpeed); 6027 } 6028 6029 6030 BTextView::TextTrackState::~TextTrackState() 6031 { 6032 delete fRunner; 6033 } 6034 6035 6036 void 6037 BTextView::TextTrackState::SimulateMouseMovement(BTextView* textView) 6038 { 6039 BPoint where; 6040 uint32 buttons; 6041 // When the mouse cursor is still and outside the textview, 6042 // no B_MOUSE_MOVED message are sent, obviously. But scrolling 6043 // has to work neverthless, so we "fake" a MouseMoved() call here. 6044 textView->GetMouse(&where, &buttons); 6045 textView->_PerformMouseMoved(where, B_INSIDE_VIEW); 6046 } 6047 6048 6049 // #pragma mark - Binary ABI compat 6050 6051 6052 extern "C" void 6053 B_IF_GCC_2(InvalidateLayout__9BTextViewb, _ZN9BTextView16InvalidateLayoutEb)( 6054 BTextView* view, bool descendants) 6055 { 6056 perform_data_layout_invalidated data; 6057 data.descendants = descendants; 6058 6059 view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data); 6060 } 6061