1 /* 2 * Copyright 2001-2008, Haiku Inc. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Hiroshi Lockheimer (BTextView is based on his STEEngine) 7 * Marc Flerackers (mflerackers@androme.be) 8 * Stefano Ceccherini (stefano.ceccherini@gmail.com) 9 */ 10 11 /*! BTextView displays and manages styled text. */ 12 13 // TODOs: 14 // - Finish documenting this class 15 // - Consider using BObjectList instead of BList 16 // for disallowed characters (it would remove a lot of reinterpret_casts) 17 // - Check for correctness and possible optimizations the calls to _Refresh(), 18 // to refresh only changed parts of text (currently we often redraw the whole text) 19 20 // Known Bugs: 21 // - Double buffering doesn't work well (disabled by default) 22 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <new> 26 27 #include <Application.h> 28 #include <Beep.h> 29 #include <Bitmap.h> 30 #include <Clipboard.h> 31 #include <Debug.h> 32 #include <Input.h> 33 #include <MessageRunner.h> 34 #include <PropertyInfo.h> 35 #include <Region.h> 36 #include <ScrollBar.h> 37 #include <TextView.h> 38 #include <Window.h> 39 40 #include "InlineInput.h" 41 #include "LineBuffer.h" 42 #include "StyleBuffer.h" 43 #include "TextGapBuffer.h" 44 #include "UndoBuffer.h" 45 #include "WidthBuffer.h" 46 47 using namespace std; 48 49 //#define TRACE_TEXTVIEW 50 #ifdef TRACE_TEXTVIEW 51 # define CALLED() printf("%s\n", __PRETTY_FUNCTION__) 52 #else 53 # define CALLED() 54 #endif 55 56 #define USE_WIDTHBUFFER 1 57 #define USE_DOUBLEBUFFERING 0 58 59 60 struct flattened_text_run { 61 int32 offset; 62 font_family family; 63 font_style style; 64 float size; 65 float shear; /* typically 90.0 */ 66 uint16 face; /* typically 0 */ 67 uint8 red; 68 uint8 green; 69 uint8 blue; 70 uint8 alpha; /* 255 == opaque */ 71 uint16 _reserved_; /* 0 */ 72 }; 73 74 struct flattened_text_run_array { 75 uint32 magic; 76 uint32 version; 77 int32 count; 78 flattened_text_run styles[1]; 79 }; 80 81 static const uint32 kFlattenedTextRunArrayMagic = 'Ali!'; 82 static const uint32 kFlattenedTextRunArrayVersion = 0; 83 84 enum { 85 B_SEPARATOR_CHARACTER, 86 B_OTHER_CHARACTER 87 }; 88 89 90 class _BTextTrackState_ { 91 public: 92 _BTextTrackState_(BMessenger messenger); 93 ~_BTextTrackState_(); 94 95 void SimulateMouseMovement(BTextView *view); 96 97 int32 clickOffset; 98 bool shiftDown; 99 BRect selectionRect; 100 101 int32 anchor; 102 int32 selStart; 103 int32 selEnd; 104 105 private: 106 BMessageRunner *fRunner; 107 }; 108 109 110 // Initialized/finalized by init/fini_interface_kit 111 _BWidthBuffer_* BTextView::sWidths = NULL; 112 sem_id BTextView::sWidthSem = B_BAD_SEM_ID; 113 int32 BTextView::sWidthAtom = 0; 114 115 116 const static rgb_color kBlackColor = { 0, 0, 0, 255 }; 117 const static rgb_color kBlueInputColor = { 152, 203, 255, 255 }; 118 const static rgb_color kRedInputColor = { 255, 152, 152, 255 }; 119 120 121 static property_info sPropertyList[] = { 122 { 123 "selection", 124 { B_GET_PROPERTY, 0 }, 125 { B_DIRECT_SPECIFIER, 0 }, 126 "Returns the current selection.", 0, 127 { B_INT32_TYPE, 0 } 128 }, 129 { 130 "selection", 131 { B_SET_PROPERTY, 0 }, 132 { B_DIRECT_SPECIFIER, 0 }, 133 "Sets the current selection.", 0, 134 { B_INT32_TYPE, 0 } 135 }, 136 { 137 "Text", 138 { B_COUNT_PROPERTIES, 0 }, 139 { B_DIRECT_SPECIFIER, 0 }, 140 "Returns the length of the text in bytes.", 0, 141 { B_INT32_TYPE, 0 } 142 }, 143 { 144 "Text", 145 { B_GET_PROPERTY, 0 }, 146 { B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 }, 147 "Returns the text in the specified range in the BTextView.", 0, 148 { B_STRING_TYPE, 0 } 149 }, 150 { 151 "Text", 152 { B_SET_PROPERTY, 0 }, 153 { B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 }, 154 "Removes or inserts text into the specified range in the BTextView.", 0, 155 { B_STRING_TYPE, 0 } 156 }, 157 { 158 "text_run_array", 159 { B_GET_PROPERTY, 0 }, 160 { B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 }, 161 "Returns the style information for the text in the specified range in the BTextView.", 0, 162 { B_RAW_TYPE, 0 }, 163 }, 164 { 165 "text_run_array", 166 { B_SET_PROPERTY, 0 }, 167 { B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 }, 168 "Sets the style information for the text in the specified range in the BTextView.", 0, 169 { B_RAW_TYPE, 0 }, 170 }, 171 { 0 } 172 }; 173 174 175 /*! \brief Creates a BTextView object with the given attributes. 176 \param frame The rect which will enclose the BTextView object. 177 \param name The name of the object. 178 \param textRect Determines the area of the text within the BTextView object. 179 \param resizeMask The resizing mask for the BTextView, passed to the BView constructor. 180 \param flags The flags for the BTextView, passed to the BView constructor. 181 */ 182 BTextView::BTextView(BRect frame, const char *name, BRect textRect, 183 uint32 resizeMask, uint32 flags) 184 : BView(frame, name, resizeMask, 185 flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE) 186 { 187 _InitObject(textRect, NULL, NULL); 188 } 189 190 191 /*! \brief Creates a BTextView object with the given attributes. 192 \param frame The rect which will enclose the BTextView object. 193 \param name The name of the object. 194 \param textRect Determines the area of the text within the BTextView object. 195 \param initialFont The BTextView will display its text using this font, unless otherwise specified. 196 \param initialColor The BTextView will display its text using this color, unless otherwise specified. 197 \param resizeMask The resizing mask for the BTextView, passed to the BView constructor. 198 \param flags The flags for the BTextView, passed to the BView constructor. 199 */ 200 BTextView::BTextView(BRect frame, const char *name, BRect textRect, 201 const BFont *initialFont, const rgb_color *initialColor, 202 uint32 resizeMask, uint32 flags) 203 : BView(frame, name, resizeMask, 204 flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE) 205 { 206 _InitObject(textRect, initialFont, initialColor); 207 } 208 209 210 /*! \brief Creates a BTextView object from the passed BMessage. 211 \param archive The BMessage from which the object shall be created. 212 */ 213 BTextView::BTextView(BMessage *archive) 214 : BView(archive) 215 { 216 CALLED(); 217 BRect rect; 218 219 if (archive->FindRect("_trect", &rect) != B_OK) 220 rect.Set(0, 0, 0, 0); 221 222 _InitObject(rect, NULL, NULL); 223 224 const char *text = NULL; 225 if (archive->FindString("_text", &text) == B_OK) 226 SetText(text); 227 228 int32 flag, flag2; 229 if (archive->FindInt32("_align", &flag) == B_OK) 230 SetAlignment((alignment)flag); 231 232 float value; 233 234 if (archive->FindFloat("_tab", &value) == B_OK) 235 SetTabWidth(value); 236 237 if (archive->FindInt32("_col_sp", &flag) == B_OK) 238 SetColorSpace((color_space)flag); 239 240 if (archive->FindInt32("_max", &flag) == B_OK) 241 SetMaxBytes(flag); 242 243 if (archive->FindInt32("_sel", &flag) == B_OK && 244 archive->FindInt32("_sel", &flag2) == B_OK) 245 Select(flag, flag2); 246 247 bool toggle; 248 249 if (archive->FindBool("_stylable", &toggle) == B_OK) 250 SetStylable(toggle); 251 252 if (archive->FindBool("_auto_in", &toggle) == B_OK) 253 SetAutoindent(toggle); 254 255 if (archive->FindBool("_wrap", &toggle) == B_OK) 256 SetWordWrap(toggle); 257 258 if (archive->FindBool("_nsel", &toggle) == B_OK) 259 MakeSelectable(!toggle); 260 261 if (archive->FindBool("_nedit", &toggle) == B_OK) 262 MakeEditable(!toggle); 263 264 ssize_t disallowedCount = 0; 265 const int32 *disallowedChars = NULL; 266 if (archive->FindData("_dis_ch", B_RAW_TYPE, 267 (const void **)&disallowedChars, &disallowedCount) == B_OK) { 268 269 fDisallowedChars = new BList; 270 disallowedCount /= sizeof(int32); 271 for (int32 x = 0; x < disallowedCount; x++) 272 fDisallowedChars->AddItem(reinterpret_cast<void *>(disallowedChars[x])); 273 } 274 275 ssize_t runSize = 0; 276 const void *flattenedRun = NULL; 277 278 if (archive->FindData("_runs", B_RAW_TYPE, &flattenedRun, &runSize) == B_OK) { 279 text_run_array *runArray = UnflattenRunArray(flattenedRun, (int32 *)&runSize); 280 if (runArray) { 281 SetRunArray(0, TextLength(), runArray); 282 FreeRunArray(runArray); 283 } 284 } 285 286 } 287 288 289 /*! \brief Frees the memory allocated and destroy the object created on 290 construction. 291 */ 292 BTextView::~BTextView() 293 { 294 _CancelInputMethod(); 295 _StopMouseTracking(); 296 _DeleteOffscreen(); 297 298 delete fText; 299 delete fLines; 300 delete fStyles; 301 delete fDisallowedChars; 302 delete fUndo; 303 delete fClickRunner; 304 delete fDragRunner; 305 } 306 307 308 /*! \brief Static function used to istantiate a BTextView object from the given BMessage. 309 \param archive The BMessage from which the object shall be created. 310 \return A constructed BTextView as a BArchivable object. 311 */ 312 BArchivable * 313 BTextView::Instantiate(BMessage *archive) 314 { 315 CALLED(); 316 if (validate_instantiation(archive, "BTextView")) 317 return new BTextView(archive); 318 return NULL; 319 } 320 321 322 /*! \brief Archives the object into the passed message. 323 \param data A pointer to the message where to archive the object. 324 \param deep ? 325 \return \c B_OK if everything went well, an error code if not. 326 */ 327 status_t 328 BTextView::Archive(BMessage *data, bool deep) const 329 { 330 CALLED(); 331 status_t err = BView::Archive(data, deep); 332 if (err == B_OK) 333 err = data->AddString("_text", Text()); 334 if (err == B_OK) 335 err = data->AddInt32("_align", fAlignment); 336 if (err == B_OK) 337 err = data->AddFloat("_tab", fTabWidth); 338 if (err == B_OK) 339 err = data->AddInt32("_col_sp", fColorSpace); 340 if (err == B_OK) 341 err = data->AddRect("_trect", fTextRect); 342 if (err == B_OK) 343 err = data->AddInt32("_max", fMaxBytes); 344 if (err == B_OK) 345 err = data->AddInt32("_sel", fSelStart); 346 if (err == B_OK) 347 err = data->AddInt32("_sel", fSelEnd); 348 if (err == B_OK) 349 err = data->AddBool("_stylable", fStylable); 350 if (err == B_OK) 351 err = data->AddBool("_auto_in", fAutoindent); 352 if (err == B_OK) 353 err = data->AddBool("_wrap", fWrap); 354 if (err == B_OK) 355 err = data->AddBool("_nsel", !fSelectable); 356 if (err == B_OK) 357 err = data->AddBool("_nedit", !fEditable); 358 359 if (err == B_OK && fDisallowedChars != NULL) { 360 err = data->AddData("_dis_ch", B_RAW_TYPE, fDisallowedChars->Items(), 361 fDisallowedChars->CountItems() * sizeof(int32)); 362 } 363 364 if (err == B_OK) { 365 int32 runSize = 0; 366 text_run_array *runArray = RunArray(0, TextLength()); 367 368 void *flattened = FlattenRunArray(runArray, &runSize); 369 if (flattened != NULL) { 370 data->AddData("_runs", B_RAW_TYPE, flattened, runSize); 371 free(flattened); 372 } else 373 err = B_NO_MEMORY; 374 375 FreeRunArray(runArray); 376 } 377 378 return err; 379 } 380 381 382 /*! \brief Hook function called when the BTextView is added to the 383 window's view hierarchy. 384 385 Set the window's pulse rate to 2 per second and adjust scrollbars if needed 386 */ 387 void 388 BTextView::AttachedToWindow() 389 { 390 BView::AttachedToWindow(); 391 392 SetDrawingMode(B_OP_COPY); 393 394 Window()->SetPulseRate(500000); 395 396 fCaretVisible = false; 397 fCaretTime = 0; 398 fClickCount = 0; 399 fClickTime = 0; 400 fDragOffset = -1; 401 fActive = false; 402 403 if (fResizable) 404 _AutoResize(true); 405 406 _UpdateScrollbars(); 407 408 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 409 } 410 411 412 /*! \brief Hook function called when the BTextView is removed from the 413 window's view hierarchy. 414 */ 415 void 416 BTextView::DetachedFromWindow() 417 { 418 BView::DetachedFromWindow(); 419 } 420 421 422 /*! \brief Hook function called whenever 423 the contents of the BTextView need to be (re)drawn. 424 \param updateRect The rect which needs to be redrawn 425 */ 426 void 427 BTextView::Draw(BRect updateRect) 428 { 429 // what lines need to be drawn? 430 int32 startLine = LineAt(BPoint(0.0f, updateRect.top)); 431 int32 endLine = LineAt(BPoint(0.0f, updateRect.bottom)); 432 433 // TODO: _DrawLines draw the text over and over, causing the text to 434 // antialias against itself. In theory we should use an offscreen bitmap 435 // to draw the text which would eliminate the problem. 436 //_DrawLines(startLine, endLine); 437 _Refresh(OffsetAt(startLine), OffsetAt(endLine + 1), false, false); 438 } 439 440 441 /*! \brief Hook function called when a mouse button is clicked while 442 the cursor is in the view. 443 \param where The location where the mouse button has been clicked. 444 */ 445 void 446 BTextView::MouseDown(BPoint where) 447 { 448 // should we even bother? 449 if (!fEditable && !fSelectable) 450 return; 451 452 _CancelInputMethod(); 453 454 if (!IsFocus()) 455 MakeFocus(); 456 457 _HideCaret(); 458 459 _StopMouseTracking(); 460 461 BMessenger messenger(this); 462 fTrackingMouse = new (nothrow) _BTextTrackState_(messenger); 463 if (fTrackingMouse == NULL) 464 return; 465 466 int32 modifiers = 0; 467 //uint32 buttons; 468 BMessage *currentMessage = Window()->CurrentMessage(); 469 if (currentMessage != NULL) { 470 currentMessage->FindInt32("modifiers", &modifiers); 471 //currentMessage->FindInt32("buttons", (int32 *)&buttons); 472 } 473 474 fTrackingMouse->clickOffset = OffsetAt(where); 475 fTrackingMouse->shiftDown = modifiers & B_SHIFT_KEY; 476 477 bigtime_t clickTime = system_time(); 478 bigtime_t clickSpeed = 0; 479 get_click_speed(&clickSpeed); 480 bool multipleClick = false; 481 if (clickTime - fClickTime < clickSpeed && fClickOffset == fTrackingMouse->clickOffset) 482 multipleClick = true; 483 484 fWhere = where; 485 486 SetMouseEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS, B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY); 487 488 if (fSelStart != fSelEnd && !fTrackingMouse->shiftDown && !multipleClick) { 489 BRegion region; 490 GetTextRegion(fSelStart, fSelEnd, ®ion); 491 if (region.Contains(where)) { 492 // Setup things for dragging 493 fTrackingMouse->selectionRect = region.Frame(); 494 return; 495 } 496 } 497 498 if (multipleClick) { 499 if (fClickCount > 1) { 500 fClickCount = 0; 501 fClickTime = 0; 502 } else { 503 fClickCount = 2; 504 fClickTime = clickTime; 505 } 506 } else { 507 fClickOffset = fTrackingMouse->clickOffset; 508 fClickCount = 1; 509 fClickTime = clickTime; 510 511 // Deselect any previously selected text 512 if (!fTrackingMouse->shiftDown) 513 Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset); 514 } 515 516 if (fClickTime == clickTime) { 517 BMessage message(_PING_); 518 message.AddInt64("clickTime", clickTime); 519 delete fClickRunner; 520 521 BMessenger messenger(this); 522 fClickRunner = new (nothrow) BMessageRunner(messenger, &message, clickSpeed, 1); 523 } 524 525 526 if (!fSelectable) { 527 _StopMouseTracking(); 528 return; 529 } 530 531 int32 offset = fSelStart; 532 if (fTrackingMouse->clickOffset > fSelStart) 533 offset = fSelEnd; 534 535 fTrackingMouse->anchor = offset; 536 537 MouseMoved(where, B_INSIDE_VIEW, NULL); 538 } 539 540 541 /*! \brief Hook function called when a mouse button is released while 542 the cursor is in the view. 543 \param where The point where the mouse button has been released. 544 545 Stops asynchronous mouse tracking 546 */ 547 void 548 BTextView::MouseUp(BPoint where) 549 { 550 BView::MouseUp(where); 551 _PerformMouseUp(where); 552 553 delete fDragRunner; 554 fDragRunner = NULL; 555 } 556 557 558 /*! \brief Hook function called whenever the mouse cursor enters, exits 559 or moves inside the view. 560 \param where The point where the mouse cursor has moved to. 561 \param code A code which tells if the mouse entered or exited the view 562 \param message The message containing dragged information, if any. 563 */ 564 void 565 BTextView::MouseMoved(BPoint where, uint32 code, const BMessage *message) 566 { 567 // Check if it's a "click'n'move 568 if (_PerformMouseMoved(where, code)) 569 return; 570 571 bool sync = false; 572 switch (code) { 573 // We force a sync when the mouse enters the view 574 case B_ENTERED_VIEW: 575 sync = true; 576 // supposed to fall through 577 case B_INSIDE_VIEW: 578 _TrackMouse(where, message, sync); 579 break; 580 581 case B_EXITED_VIEW: 582 _DragCaret(-1); 583 if (Window()->IsActive() && message == NULL) 584 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 585 break; 586 587 default: 588 BView::MouseMoved(where, code, message); 589 break; 590 } 591 } 592 593 594 /*! \brief Hook function called when the window becomes the active window 595 or gives up that status. 596 \param state If true, window has just become active, if false, window has 597 just become inactive. 598 */ 599 void 600 BTextView::WindowActivated(bool state) 601 { 602 BView::WindowActivated(state); 603 604 if (state && IsFocus()) { 605 if (!fActive) 606 _Activate(); 607 } else { 608 if (fActive) 609 _Deactivate(); 610 } 611 612 BPoint where; 613 ulong buttons; 614 GetMouse(&where, &buttons, false); 615 616 if (Bounds().Contains(where)) 617 _TrackMouse(where, NULL); 618 } 619 620 621 /*! \brief Hook function called whenever a key is pressed while the view is 622 the focus view of the active window. 623 */ 624 void 625 BTextView::KeyDown(const char *bytes, int32 numBytes) 626 { 627 const char keyPressed = bytes[0]; 628 629 if (!fEditable) { 630 // only arrow and page keys are allowed 631 // (no need to hide the cursor) 632 switch (keyPressed) { 633 case B_LEFT_ARROW: 634 case B_RIGHT_ARROW: 635 case B_UP_ARROW: 636 case B_DOWN_ARROW: 637 _HandleArrowKey(keyPressed); 638 break; 639 640 case B_HOME: 641 case B_END: 642 case B_PAGE_UP: 643 case B_PAGE_DOWN: 644 _HandlePageKey(keyPressed); 645 break; 646 647 default: 648 BView::KeyDown(bytes, numBytes); 649 break; 650 } 651 652 return; 653 } 654 655 // hide the cursor and caret 656 be_app->ObscureCursor(); 657 _HideCaret(); 658 659 switch (keyPressed) { 660 case B_BACKSPACE: 661 _HandleBackspace(); 662 break; 663 664 case B_LEFT_ARROW: 665 case B_RIGHT_ARROW: 666 case B_UP_ARROW: 667 case B_DOWN_ARROW: 668 _HandleArrowKey(keyPressed); 669 break; 670 671 case B_DELETE: 672 _HandleDelete(); 673 break; 674 675 case B_HOME: 676 case B_END: 677 case B_PAGE_UP: 678 case B_PAGE_DOWN: 679 _HandlePageKey(keyPressed); 680 break; 681 682 case B_ESCAPE: 683 case B_INSERT: 684 case B_FUNCTION_KEY: 685 // ignore, pass it up to superclass 686 BView::KeyDown(bytes, numBytes); 687 break; 688 689 default: 690 // if the character is not allowed, bail out. 691 if (fDisallowedChars 692 && fDisallowedChars->HasItem(reinterpret_cast<void *>((uint32)keyPressed))) { 693 beep(); 694 return; 695 } 696 697 _HandleAlphaKey(bytes, numBytes); 698 break; 699 } 700 701 // draw the caret 702 if (fSelStart == fSelEnd) 703 _ShowCaret(); 704 } 705 706 707 /*! \brief Hook function called every x microseconds. 708 It's the function which makes the caret blink. 709 */ 710 void 711 BTextView::Pulse() 712 { 713 if (fActive && fEditable && fSelStart == fSelEnd) { 714 if (system_time() > (fCaretTime + 500000.0)) 715 _InvertCaret(); 716 } 717 } 718 719 720 /*! \brief Hook function called when the view's frame is resized. 721 \param width The new view's width. 722 \param height The new view's height. 723 724 Updates the associated scrollbars. 725 */ 726 void 727 BTextView::FrameResized(float width, float height) 728 { 729 BView::FrameResized(width, height); 730 _UpdateScrollbars(); 731 } 732 733 734 /*! \brief Highlight/unhighlight the selection when the view gets 735 or looses the focus. 736 \param focusState The focus state: true, if the view is getting the focus, 737 false otherwise. 738 */ 739 void 740 BTextView::MakeFocus(bool focusState) 741 { 742 BView::MakeFocus(focusState); 743 744 if (focusState && Window() && Window()->IsActive()) { 745 if (!fActive) 746 _Activate(); 747 } else { 748 if (fActive) 749 _Deactivate(); 750 } 751 } 752 753 754 /*! \brief Hook function executed every time the BTextView gets a message. 755 \param message The received message 756 */ 757 void 758 BTextView::MessageReceived(BMessage *message) 759 { 760 // TODO: block input if not editable (Andrew) 761 762 // was this message dropped? 763 if (message->WasDropped()) { 764 BPoint dropOffset; 765 BPoint dropPoint = message->DropPoint(&dropOffset); 766 ConvertFromScreen(&dropPoint); 767 ConvertFromScreen(&dropOffset); 768 if (!_MessageDropped(message, dropPoint, dropOffset)) 769 BView::MessageReceived(message); 770 771 return; 772 } 773 774 switch (message->what) { 775 case B_CUT: 776 if (!IsTypingHidden()) 777 Cut(be_clipboard); 778 else 779 beep(); 780 break; 781 782 case B_COPY: 783 if (!IsTypingHidden()) 784 Copy(be_clipboard); 785 else 786 beep(); 787 break; 788 789 case B_PASTE: 790 Paste(be_clipboard); 791 break; 792 793 case B_UNDO: 794 Undo(be_clipboard); 795 break; 796 797 case B_SELECT_ALL: 798 SelectAll(); 799 break; 800 801 case B_INPUT_METHOD_EVENT: 802 { 803 int32 opcode; 804 if (message->FindInt32("be:opcode", &opcode) == B_OK) { 805 switch (opcode) { 806 case B_INPUT_METHOD_STARTED: 807 { 808 BMessenger messenger; 809 if (message->FindMessenger("be:reply_to", &messenger) == B_OK) { 810 ASSERT(fInline == NULL); 811 fInline = new _BInlineInput_(messenger); 812 } 813 break; 814 } 815 816 case B_INPUT_METHOD_STOPPED: 817 delete fInline; 818 fInline = NULL; 819 break; 820 821 case B_INPUT_METHOD_CHANGED: 822 if (fInline != NULL) 823 _HandleInputMethodChanged(message); 824 break; 825 826 case B_INPUT_METHOD_LOCATION_REQUEST: 827 if (fInline != NULL) 828 _HandleInputMethodLocationRequest(); 829 break; 830 831 default: 832 break; 833 } 834 } 835 break; 836 } 837 838 case B_SET_PROPERTY: 839 case B_GET_PROPERTY: 840 case B_COUNT_PROPERTIES: 841 { 842 BPropertyInfo propInfo(sPropertyList); 843 BMessage specifier; 844 const char *property; 845 846 if (message->GetCurrentSpecifier(NULL, &specifier) < B_OK 847 || specifier.FindString("property", &property) < B_OK) 848 return; 849 850 if (propInfo.FindMatch(message, 0, &specifier, specifier.what, 851 property) < B_OK) { 852 BView::MessageReceived(message); 853 break; 854 } 855 856 BMessage reply; 857 bool handled = false; 858 switch(message->what) { 859 case B_GET_PROPERTY: 860 handled = _GetProperty(&specifier, specifier.what, property, 861 &reply); 862 break; 863 864 case B_SET_PROPERTY: 865 handled = _SetProperty(&specifier, specifier.what, property, 866 &reply); 867 break; 868 869 case B_COUNT_PROPERTIES: 870 handled = _CountProperties(&specifier, specifier.what, 871 property, &reply); 872 break; 873 874 default: 875 break; 876 } 877 if (handled) 878 message->SendReply(&reply); 879 else 880 BView::MessageReceived(message); 881 break; 882 } 883 884 case _PING_: 885 { 886 if (message->HasInt64("clickTime")) { 887 bigtime_t clickTime; 888 message->FindInt64("clickTime", &clickTime); 889 if (clickTime == fClickTime) { 890 if (fSelStart != fSelEnd && fSelectable) { 891 BRegion region; 892 GetTextRegion(fSelStart, fSelEnd, ®ion); 893 if (region.Contains(fWhere)) 894 _TrackMouse(fWhere, NULL); 895 } 896 delete fClickRunner; 897 fClickRunner = NULL; 898 } 899 } else if (fTrackingMouse) { 900 fTrackingMouse->SimulateMouseMovement(this); 901 _PerformAutoScrolling(); 902 } 903 break; 904 } 905 906 case _DISPOSE_DRAG_: 907 if (fEditable) 908 _TrackDrag(fWhere); 909 break; 910 911 default: 912 BView::MessageReceived(message); 913 break; 914 } 915 } 916 917 918 /*! \brief Returns the proper handler for the given scripting message. 919 \param message The scripting message which needs to be examined. 920 \param index The index of the specifier 921 \param specifier The message which contains the specifier 922 \param what The 'what' field of the specifier message. 923 \param property The name of the targetted property 924 \return The proper BHandler for the given scripting message. 925 */ 926 BHandler * 927 BTextView::ResolveSpecifier(BMessage *message, int32 index, BMessage *specifier, 928 int32 what, const char *property) 929 { 930 BPropertyInfo propInfo(sPropertyList); 931 BHandler *target = this; 932 933 if (propInfo.FindMatch(message, index, specifier, what, property) < B_OK) { 934 target = BView::ResolveSpecifier(message, index, specifier, what, 935 property); 936 } 937 return target; 938 } 939 940 941 status_t 942 BTextView::GetSupportedSuites(BMessage *data) 943 { 944 if (data == NULL) 945 return B_BAD_VALUE; 946 947 status_t err = data->AddString("suites", "suite/vnd.Be-text-view"); 948 if (err != B_OK) 949 return err; 950 951 BPropertyInfo prop_info(sPropertyList); 952 err = data->AddFlat("messages", &prop_info); 953 954 if (err != B_OK) 955 return err; 956 return BView::GetSupportedSuites(data); 957 } 958 959 960 status_t 961 BTextView::Perform(perform_code d, void *arg) 962 { 963 return BView::Perform(d, arg); 964 } 965 966 967 void 968 BTextView::SetText(const char *inText, const text_run_array *inRuns) 969 { 970 SetText(inText, inText ? strlen(inText) : 0, inRuns); 971 } 972 973 974 void 975 BTextView::SetText(const char *inText, int32 inLength, const text_run_array *inRuns) 976 { 977 _CancelInputMethod(); 978 979 // hide the caret/unhilite the selection 980 if (fActive) { 981 if (fSelStart != fSelEnd) 982 Highlight(fSelStart, fSelEnd); 983 else { 984 _HideCaret(); 985 } 986 } 987 988 // remove data from buffer 989 if (fText->Length() > 0) 990 DeleteText(0, fText->Length()); // TODO: was fText->Length() - 1 991 992 if (inText != NULL && inLength > 0) 993 InsertText(inText, inLength, 0, inRuns); 994 995 // recalc line breaks and draw the text 996 _Refresh(0, inLength, true, false); 997 fClickOffset = fSelStart = fSelEnd = 0; 998 ScrollToOffset(fSelStart); 999 1000 // draw the caret 1001 if (fActive) 1002 _ShowCaret(); 1003 } 1004 1005 1006 void 1007 BTextView::SetText(BFile *inFile, int32 inOffset, int32 inLength, 1008 const text_run_array *inRuns) 1009 { 1010 CALLED(); 1011 1012 _CancelInputMethod(); 1013 1014 if (!inFile) 1015 return; 1016 1017 if (fText->Length() > 0) 1018 DeleteText(0, fText->Length()); 1019 1020 fText->InsertText(inFile, inOffset, inLength, 0); 1021 1022 // update the start offsets of each line below offset 1023 fLines->BumpOffset(inLength, LineAt(inOffset) + 1); 1024 1025 // update the style runs 1026 fStyles->BumpOffset(inLength, fStyles->OffsetToRun(inOffset - 1) + 1); 1027 1028 if (inRuns != NULL) 1029 SetRunArray(inOffset, inOffset + inLength, inRuns); 1030 else { 1031 // apply nullStyle to inserted text 1032 fStyles->SyncNullStyle(inOffset); 1033 fStyles->SetStyleRange(inOffset, inOffset + inLength, 1034 fText->Length(), B_FONT_ALL, NULL, NULL); 1035 } 1036 1037 // recalc line breaks and draw the text 1038 _Refresh(0, inLength, true, false); 1039 fClickOffset = fSelStart = fSelEnd = 0; 1040 ScrollToOffset(fSelStart); 1041 1042 // draw the caret 1043 if (fActive) 1044 _ShowCaret(); 1045 } 1046 1047 1048 void 1049 BTextView::Insert(const char *inText, const text_run_array *inRuns) 1050 { 1051 if (inText != NULL) 1052 _DoInsertText(inText, strlen(inText), fSelStart, inRuns, NULL); 1053 } 1054 1055 1056 void 1057 BTextView::Insert(const char *inText, int32 inLength, 1058 const text_run_array *inRuns) 1059 { 1060 if (inText != NULL && inLength > 0) { 1061 int32 realLength = strlen(inText); 1062 _DoInsertText(inText, min_c(inLength, realLength), fSelStart, inRuns, 1063 NULL); 1064 } 1065 } 1066 1067 1068 void 1069 BTextView::Insert(int32 startOffset, const char *inText, int32 inLength, 1070 const text_run_array *inRuns) 1071 { 1072 CALLED(); 1073 1074 // do we really need to do anything? 1075 if (inText != NULL && inLength > 0) { 1076 int32 realLength = strlen(inText); 1077 _DoInsertText(inText, min_c(inLength, realLength), startOffset, inRuns, NULL); 1078 } 1079 } 1080 1081 1082 /*! \brief Deletes the text within the current selection. 1083 */ 1084 void 1085 BTextView::Delete() 1086 { 1087 Delete(fSelStart, fSelEnd); 1088 } 1089 1090 1091 /*! \brief Delets the text comprised within the given offsets. 1092 \param startOffset The offset of the text to delete. 1093 \param endOffset The offset where the text to delete ends. 1094 */ 1095 void 1096 BTextView::Delete(int32 startOffset, int32 endOffset) 1097 { 1098 CALLED(); 1099 // anything to delete? 1100 if (startOffset == endOffset) 1101 return; 1102 1103 // hide the caret/unhilite the selection 1104 if (fActive) { 1105 if (fSelStart != fSelEnd) 1106 Highlight(fSelStart, fSelEnd); 1107 else 1108 _HideCaret(); 1109 } 1110 // remove data from buffer 1111 DeleteText(startOffset, endOffset); 1112 1113 // Check if the caret needs to be moved 1114 if (fClickOffset >= endOffset) 1115 fClickOffset -= (endOffset - startOffset); 1116 else if (fClickOffset >= startOffset && fClickOffset < endOffset) 1117 fClickOffset = startOffset; 1118 1119 fSelEnd = fSelStart = fClickOffset; 1120 1121 // recalc line breaks and draw what's left 1122 _Refresh(startOffset, endOffset, true, true); 1123 1124 // draw the caret 1125 if (fActive) 1126 _ShowCaret(); 1127 } 1128 1129 1130 /*! \brief Returns the BTextView text as a C string. 1131 \return A pointer to the text. 1132 1133 It is possible that the BTextView object had to do some operations 1134 on the text, to be able to return it as a C string. 1135 If you need to call Text() repeatedly, you'd better use GetText(). 1136 */ 1137 const char * 1138 BTextView::Text() const 1139 { 1140 return fText->RealText(); 1141 } 1142 1143 1144 /*! \brief Returns the length of the BTextView's text. 1145 \return The length of the text. 1146 */ 1147 int32 1148 BTextView::TextLength() const 1149 { 1150 return fText->Length(); 1151 } 1152 1153 1154 void 1155 BTextView::GetText(int32 offset, int32 length, char *buffer) const 1156 { 1157 if (buffer != NULL) 1158 fText->GetString(offset, length, buffer); 1159 } 1160 1161 1162 /*! \brief Returns the character at the given offset. 1163 \param offset The offset of the wanted character. 1164 \return The character at the given offset. 1165 */ 1166 uchar 1167 BTextView::ByteAt(int32 offset) const 1168 { 1169 if (offset < 0 || offset >= fText->Length()) 1170 return '\0'; 1171 1172 return fText->RealCharAt(offset); 1173 } 1174 1175 1176 /*! \brief Returns the number of lines that the object contains. 1177 \return The number of lines contained in the BTextView object. 1178 */ 1179 int32 1180 BTextView::CountLines() const 1181 { 1182 return fLines->NumLines(); 1183 } 1184 1185 1186 /*! \brief Returns the index of the current line. 1187 \return The index of the current line. 1188 */ 1189 int32 1190 BTextView::CurrentLine() const 1191 { 1192 return LineAt(fSelStart); 1193 } 1194 1195 1196 /*! \brief Move the caret to the specified line. 1197 \param index The index of the line. 1198 */ 1199 void 1200 BTextView::GoToLine(int32 index) 1201 { 1202 _CancelInputMethod(); 1203 fSelStart = fSelEnd = fClickOffset = OffsetAt(index); 1204 } 1205 1206 1207 /*! \brief Cuts the current selection to the clipboard. 1208 \param clipboard The clipboard where to copy the cutted text. 1209 */ 1210 void 1211 BTextView::Cut(BClipboard *clipboard) 1212 { 1213 _CancelInputMethod(); 1214 if (fUndo) { 1215 delete fUndo; 1216 fUndo = new _BCutUndoBuffer_(this); 1217 } 1218 Copy(clipboard); 1219 Delete(); 1220 } 1221 1222 1223 /*! \brief Copies the current selection to the clipboard. 1224 \param clipboard The clipboard where to copy the selected text. 1225 */ 1226 void 1227 BTextView::Copy(BClipboard *clipboard) 1228 { 1229 _CancelInputMethod(); 1230 1231 if (clipboard->Lock()) { 1232 clipboard->Clear(); 1233 1234 BMessage *clip = clipboard->Data(); 1235 if (clip != NULL) { 1236 int32 numBytes = fSelEnd - fSelStart; 1237 const char* text = fText->GetString(fSelStart, &numBytes); 1238 clip->AddData("text/plain", B_MIME_TYPE, text, numBytes); 1239 1240 int32 size; 1241 if (fStylable) { 1242 text_run_array *runArray = RunArray(fSelStart, fSelEnd, &size); 1243 clip->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE, 1244 runArray, size); 1245 FreeRunArray(runArray); 1246 } 1247 clipboard->Commit(); 1248 } 1249 clipboard->Unlock(); 1250 } 1251 } 1252 1253 1254 /*! \brief Paste the text contained in the clipboard to the BTextView. 1255 \param clipboard A pointer to the clipboard. 1256 */ 1257 void 1258 BTextView::Paste(BClipboard *clipboard) 1259 { 1260 CALLED(); 1261 _CancelInputMethod(); 1262 1263 if (clipboard->Lock()) { 1264 BMessage *clip = clipboard->Data(); 1265 if (clip != NULL) { 1266 const char *text = NULL; 1267 ssize_t len = 0; 1268 1269 if (clip->FindData("text/plain", B_MIME_TYPE, 1270 (const void **)&text, &len) == B_OK) { 1271 text_run_array *runArray = NULL; 1272 ssize_t runLen = 0; 1273 1274 if (fStylable) 1275 clip->FindData("application/x-vnd.Be-text_run_array", B_MIME_TYPE, 1276 (const void **)&runArray, &runLen); 1277 1278 if (fUndo) { 1279 delete fUndo; 1280 fUndo = new _BPasteUndoBuffer_(this, text, len, runArray, runLen); 1281 } 1282 1283 if (fSelStart != fSelEnd) 1284 Delete(); 1285 1286 Insert(text, len, runArray); 1287 } 1288 } 1289 1290 clipboard->Unlock(); 1291 } 1292 } 1293 1294 1295 /*! \brief Deletes the currently selected text. 1296 */ 1297 void 1298 BTextView::Clear() 1299 { 1300 // We always check for fUndo != NULL (not only here), 1301 // because when fUndo is NULL, undo is deactivated. 1302 if (fUndo) { 1303 delete fUndo; 1304 fUndo = new _BClearUndoBuffer_(this); 1305 } 1306 1307 Delete(); 1308 } 1309 1310 1311 bool 1312 BTextView::AcceptsPaste(BClipboard *clipboard) 1313 { 1314 bool result = false; 1315 1316 if (fEditable && clipboard && clipboard->Lock()) { 1317 BMessage *data = clipboard->Data(); 1318 result = data && data->HasData("text/plain", B_MIME_TYPE); 1319 clipboard->Unlock(); 1320 } 1321 1322 return result; 1323 } 1324 1325 1326 bool 1327 BTextView::AcceptsDrop(const BMessage *inMessage) 1328 { 1329 if (fEditable && inMessage && inMessage->HasData("text/plain", B_MIME_TYPE)) 1330 return true; 1331 1332 return false; 1333 } 1334 1335 1336 /*! \brief Selects the text within the given offsets. 1337 \param startOffset The offset of the text to select. 1338 \param endOffset The offset where the text ends. 1339 */ 1340 void 1341 BTextView::Select(int32 startOffset, int32 endOffset) 1342 { 1343 CALLED(); 1344 if (!fSelectable) 1345 return; 1346 1347 _CancelInputMethod(); 1348 1349 // a negative selection? 1350 if (startOffset > endOffset) 1351 return; 1352 1353 // pin offsets at reasonable values 1354 if (startOffset < 0) 1355 startOffset = 0; 1356 if (endOffset < 0) 1357 endOffset = 0; 1358 else if (endOffset > fText->Length()) 1359 endOffset = fText->Length(); 1360 1361 // is the new selection any different from the current selection? 1362 if (startOffset == fSelStart && endOffset == fSelEnd) 1363 return; 1364 1365 fStyles->InvalidateNullStyle(); 1366 1367 _HideCaret(); 1368 1369 if (startOffset == endOffset) { 1370 if (fSelStart != fSelEnd) { 1371 // unhilite the selection 1372 if (fActive) 1373 Highlight(fSelStart, fSelEnd); 1374 } 1375 fSelStart = fSelEnd = fClickOffset = startOffset; 1376 } else { 1377 if (fActive) { 1378 // draw only those ranges that are different 1379 long start, end; 1380 if (startOffset != fSelStart) { 1381 // start of selection has changed 1382 if (startOffset > fSelStart) { 1383 start = fSelStart; 1384 end = startOffset; 1385 } else { 1386 start = startOffset; 1387 end = fSelStart; 1388 } 1389 Highlight(start, end); 1390 } 1391 1392 if (endOffset != fSelEnd) { 1393 // end of selection has changed 1394 if (endOffset > fSelEnd) { 1395 start = fSelEnd; 1396 end = endOffset; 1397 } else { 1398 start = endOffset; 1399 end = fSelEnd; 1400 } 1401 Highlight(start, end); 1402 } 1403 } 1404 fSelStart = startOffset; 1405 fSelEnd = fClickOffset = endOffset; 1406 } 1407 } 1408 1409 1410 /*! \brief Selects all the text within the BTextView. 1411 */ 1412 void 1413 BTextView::SelectAll() 1414 { 1415 Select(0, fText->Length()); 1416 } 1417 1418 1419 /*! \brief Gets the current selection. 1420 \param outStart A pointer to an int32 which will contain the selection start's offset. 1421 \param outEnd A pointer to an int32 which will contain the selection end's offset. 1422 */ 1423 void 1424 BTextView::GetSelection(int32 *outStart, int32 *outEnd) const 1425 { 1426 int32 start = 0, end = 0; 1427 1428 if (fSelectable) { 1429 start = fSelStart; 1430 end = fSelEnd; 1431 } 1432 1433 if (outStart) 1434 *outStart = start; 1435 if (outEnd) 1436 *outEnd = end; 1437 } 1438 1439 1440 void 1441 BTextView::SetFontAndColor(const BFont *inFont, uint32 inMode, const rgb_color *inColor) 1442 { 1443 SetFontAndColor(fSelStart, fSelEnd, inFont, inMode, inColor); 1444 } 1445 1446 1447 void 1448 BTextView::SetFontAndColor(int32 startOffset, int32 endOffset, const BFont* font, 1449 uint32 fontMode, const rgb_color* color) 1450 { 1451 CALLED(); 1452 1453 if (startOffset > endOffset) 1454 return; 1455 1456 BFont newFont; 1457 if (font != NULL) { 1458 newFont = font; 1459 _NormalizeFont(&newFont); 1460 } 1461 1462 const int32 textLength = fText->Length(); 1463 1464 if (!fStylable) { 1465 // When the text view is not stylable, we always set the whole text's 1466 // style and ignore the offsets 1467 startOffset = 0; 1468 endOffset = textLength; 1469 } 1470 1471 // pin offsets at reasonable values 1472 if (startOffset < 0) 1473 startOffset = 0; 1474 else if (startOffset > textLength) 1475 startOffset = textLength; 1476 1477 if (endOffset < 0) 1478 endOffset = 0; 1479 else if (endOffset > textLength) 1480 endOffset = textLength; 1481 1482 // add the style to the style buffer 1483 fStyles->SetStyleRange(startOffset, endOffset, fText->Length(), 1484 fontMode, &newFont, color); 1485 1486 if (fontMode & B_FONT_FAMILY_AND_STYLE || fontMode & B_FONT_SIZE) { 1487 // recalc the line breaks and redraw with new style 1488 _Refresh(startOffset, endOffset, startOffset != endOffset, false); 1489 } else { 1490 // the line breaks wont change, simply redraw 1491 _DrawLines(LineAt(startOffset), LineAt(endOffset), startOffset, true); 1492 } 1493 } 1494 1495 1496 void 1497 BTextView::GetFontAndColor(int32 inOffset, BFont *outFont, rgb_color *outColor) const 1498 { 1499 fStyles->GetStyle(inOffset, outFont, outColor); 1500 } 1501 1502 1503 void 1504 BTextView::GetFontAndColor(BFont *outFont, uint32 *outMode, rgb_color *outColor, bool *outEqColor) const 1505 { 1506 fStyles->ContinuousGetStyle(outFont, outMode, outColor, outEqColor, fSelStart, fSelEnd); 1507 } 1508 1509 1510 void 1511 BTextView::SetRunArray(int32 startOffset, int32 endOffset, const text_run_array *inRuns) 1512 { 1513 CALLED(); 1514 1515 _CancelInputMethod(); 1516 1517 _SetRunArray(startOffset, endOffset, inRuns); 1518 1519 _Refresh(startOffset, endOffset, true, false); 1520 } 1521 1522 1523 /*! \brief Returns a RunArray for the text within the given offsets. 1524 \param startOffset The offset where to start. 1525 \param endOffset The offset where the wanted text ends. 1526 \param outSize A pointer to an int32 which will contain the size 1527 of the run array. 1528 \return A text_run_array for the text in the given offsets. 1529 1530 The returned text_run_array belongs to the caller, so you better 1531 free it as soon as you don't need it. 1532 */ 1533 text_run_array * 1534 BTextView::RunArray(int32 startOffset, int32 endOffset, int32 *outSize) const 1535 { 1536 STEStyleRange* styleRange = fStyles->GetStyleRange(startOffset, endOffset - 1); 1537 if (styleRange == NULL) 1538 return NULL; 1539 1540 text_run_array *runArray = AllocRunArray(styleRange->count, outSize); 1541 if (runArray != NULL) { 1542 for (int32 i = 0; i < runArray->count; i++) { 1543 runArray->runs[i].offset = styleRange->runs[i].offset; 1544 runArray->runs[i].font = styleRange->runs[i].style.font; 1545 runArray->runs[i].color = styleRange->runs[i].style.color; 1546 } 1547 } 1548 1549 free(styleRange); 1550 1551 return runArray; 1552 } 1553 1554 1555 /*! \brief Returns the line number for the character at the given offset. 1556 \param offset The offset of the wanted character. 1557 \return A line number. 1558 */ 1559 int32 1560 BTextView::LineAt(int32 offset) const 1561 { 1562 return fLines->OffsetToLine(offset); 1563 } 1564 1565 1566 /*! \brief Returns the line number for the given point. 1567 \param point A point. 1568 \return A line number. 1569 */ 1570 int32 1571 BTextView::LineAt(BPoint point) const 1572 { 1573 return fLines->PixelToLine(point.y - fTextRect.top); 1574 } 1575 1576 1577 /*! \brief Returns the location of the character at the given offset. 1578 \param inOffset The offset of the character. 1579 \param outHeight Here the function will put the height of the character at the 1580 given offset. 1581 \return A BPoint which is the location of the character. 1582 */ 1583 BPoint 1584 BTextView::PointAt(int32 inOffset, float *outHeight) const 1585 { 1586 // TODO: Cleanup. 1587 const int32 textLength = fText->Length(); 1588 int32 lineNum = LineAt(inOffset); 1589 STELine* line = (*fLines)[lineNum]; 1590 float height = 0; 1591 1592 BPoint result; 1593 result.x = 0.0; 1594 result.y = line->origin + fTextRect.top; 1595 1596 // Handle the case where there is only one line 1597 // (no text inserted) 1598 // TODO: See if we can do this better 1599 if (fStyles->NumRuns() == 0) { 1600 const rgb_color *color = NULL; 1601 const BFont *font = NULL; 1602 fStyles->GetNullStyle(&font, &color); 1603 1604 font_height fontHeight; 1605 font->GetHeight(&fontHeight); 1606 height = fontHeight.ascent + fontHeight.descent; 1607 1608 } else { 1609 height = (line + 1)->origin - line->origin; 1610 1611 // special case: go down one line if inOffset is a newline 1612 if (inOffset == textLength && fText->RealCharAt(inOffset - 1) == B_ENTER) { 1613 result.y += height; 1614 height = LineHeight(CountLines() - 1); 1615 1616 } else { 1617 int32 offset = line->offset; 1618 int32 length = inOffset - line->offset; 1619 int32 numBytes = length; 1620 bool foundTab = false; 1621 do { 1622 foundTab = fText->FindChar(B_TAB, offset, &numBytes); 1623 float width = _StyledWidth(offset, numBytes); 1624 result.x += width; 1625 1626 if (foundTab) { 1627 result.x += _ActualTabWidth(result.x); 1628 numBytes++; 1629 } 1630 1631 offset += numBytes; 1632 length -= numBytes; 1633 numBytes = length; 1634 } while (foundTab && length > 0); 1635 } 1636 } 1637 1638 if (fAlignment != B_ALIGN_LEFT) { 1639 float modifier = fTextRect.right - LineWidth(lineNum); 1640 if (fAlignment == B_ALIGN_CENTER) 1641 modifier /= 2; 1642 result.x += modifier; 1643 } 1644 // convert from text rect coordinates 1645 // NOTE: I didn't understand why "- 1.0" 1646 // and it works only correct without it on Haiku app_server. 1647 // Feel free to enlighten me though! 1648 result.x += fTextRect.left;// - 1.0; 1649 1650 // round up 1651 result.x = ceilf(result.x); 1652 result.y = ceilf(result.y); 1653 if (outHeight != NULL) 1654 *outHeight = height; 1655 1656 return result; 1657 } 1658 1659 1660 /*! \brief Returns the offset for the given location. 1661 \param point A BPoint which specify the wanted location. 1662 \return The offset for the given point. 1663 */ 1664 int32 1665 BTextView::OffsetAt(BPoint point) const 1666 { 1667 const int32 textLength = fText->Length(); 1668 1669 // should we even bother? 1670 if (point.y >= fTextRect.bottom) 1671 return textLength; 1672 else if (point.y < fTextRect.top) 1673 return 0; 1674 1675 int32 lineNum = LineAt(point); 1676 STELine* line = (*fLines)[lineNum]; 1677 1678 // special case: if point is within the text rect and PixelToLine() 1679 // tells us that it's on the last line, but if point is actually 1680 // lower than the bottom of the last line, return the last offset 1681 // (can happen for newlines) 1682 if (lineNum == (fLines->NumLines() - 1)) { 1683 if (point.y >= ((line + 1)->origin + fTextRect.top)) 1684 return textLength; 1685 } 1686 1687 // convert to text rect coordinates 1688 if (fAlignment != B_ALIGN_LEFT) { 1689 float lineWidth = fTextRect.right - LineWidth(lineNum); 1690 if (fAlignment == B_ALIGN_CENTER) 1691 lineWidth /= 2; 1692 point.x -= lineWidth; 1693 } 1694 1695 point.x -= fTextRect.left; 1696 point.x = max_c(point.x, 0.0); 1697 1698 // TODO: The following code isn't very efficient because it always starts from the left end, 1699 // so when the point is near the right end it's very slow. 1700 int32 offset = line->offset; 1701 const int32 limit = (line + 1)->offset; 1702 float location = 0; 1703 do { 1704 const int32 nextInitial = _NextInitialByte(offset); 1705 const int32 saveOffset = offset; 1706 float width = 0; 1707 if (ByteAt(offset) == B_TAB) 1708 width = _ActualTabWidth(location); 1709 else 1710 width = _StyledWidth(saveOffset, nextInitial - saveOffset); 1711 if (location + width > point.x) { 1712 if (fabs(location + width - point.x) < fabs(location - point.x)) 1713 offset = nextInitial; 1714 break; 1715 } 1716 1717 location += width; 1718 offset = nextInitial; 1719 } while (offset < limit); 1720 1721 if (offset == (line + 1)->offset) { 1722 // special case: newlines aren't visible 1723 // return the offset of the character preceding the newline 1724 if (ByteAt(offset - 1) == B_ENTER) 1725 return --offset; 1726 1727 // special case: return the offset preceding any spaces that 1728 // aren't at the end of the buffer 1729 if (offset != textLength && ByteAt(offset - 1) == B_SPACE) 1730 return --offset; 1731 } 1732 1733 return offset; 1734 } 1735 1736 1737 /*! \brief Returns the offset of the given line. 1738 \param line A line number. 1739 \return The offset of the passed line. 1740 */ 1741 int32 1742 BTextView::OffsetAt(int32 line) const 1743 { 1744 if (line > fLines->NumLines()) 1745 return fText->Length(); 1746 1747 return (*fLines)[line]->offset; 1748 } 1749 1750 1751 /*! \brief Looks for a sequence of character that qualifies as a word. 1752 \param inOffset The offset where to start looking. 1753 \param outFromOffset A pointer to an integer which will contain the starting 1754 offset of the word. 1755 \param outToOffset A pointer to an integer which will contain the ending 1756 offset of the word. 1757 */ 1758 void 1759 BTextView::FindWord(int32 inOffset, int32 *outFromOffset, int32 *outToOffset) 1760 { 1761 int32 offset; 1762 uint32 charType = _CharClassification(inOffset); 1763 1764 // check to the left 1765 int32 previous; 1766 for (offset = inOffset, previous = offset; offset > 0; 1767 previous = _PreviousInitialByte(offset)) { 1768 if (_CharClassification(previous) != charType) 1769 break; 1770 offset = previous; 1771 } 1772 1773 if (outFromOffset) 1774 *outFromOffset = offset; 1775 1776 // check to the right 1777 int32 textLen = TextLength(); 1778 for (offset = inOffset; offset < textLen; offset = _NextInitialByte(offset)) { 1779 if (_CharClassification(offset) != charType) 1780 break; 1781 } 1782 1783 if (outToOffset) 1784 *outToOffset = offset; 1785 } 1786 1787 1788 /*! \brief Returns true if the character at the given offset can be the last character in a line. 1789 \param offset The offset of the character. 1790 \return true if the character can be the last of a line, false if not. 1791 */ 1792 bool 1793 BTextView::CanEndLine(int32 offset) 1794 { 1795 // TODO: Could be improved, the bebook says there are other checks to do 1796 return (_CharClassification(offset) == B_SEPARATOR_CHARACTER); 1797 } 1798 1799 1800 /*! \brief Returns the width of the line at the given index. 1801 \param lineNum A line index. 1802 */ 1803 float 1804 BTextView::LineWidth(int32 lineNum) const 1805 { 1806 if (lineNum < 0 || lineNum >= fLines->NumLines()) 1807 return 0; 1808 1809 STELine* line = (*fLines)[lineNum]; 1810 return _StyledWidth(line->offset, (line + 1)->offset - line->offset); 1811 } 1812 1813 1814 /*! \brief Returns the height of the line at the given index. 1815 \param lineNum A line index. 1816 */ 1817 float 1818 BTextView::LineHeight(int32 lineNum) const 1819 { 1820 return TextHeight(lineNum, lineNum); 1821 } 1822 1823 1824 /*! \brief Returns the height of the text comprised between the two given lines. 1825 \param startLine The index of the starting line. 1826 \param endLine The index of the ending line. 1827 */ 1828 float 1829 BTextView::TextHeight(int32 startLine, int32 endLine) const 1830 { 1831 const int32 numLines = fLines->NumLines(); 1832 if (startLine < 0) 1833 startLine = 0; 1834 if (endLine > numLines - 1) 1835 endLine = numLines - 1; 1836 1837 float height = (*fLines)[endLine + 1]->origin 1838 - (*fLines)[startLine]->origin; 1839 1840 if (startLine != endLine && endLine == numLines - 1 1841 && fText->RealCharAt(fText->Length() - 1) == B_ENTER) 1842 height += (*fLines)[endLine + 1]->origin - (*fLines)[endLine]->origin; 1843 1844 return ceilf(height); 1845 } 1846 1847 1848 void 1849 BTextView::GetTextRegion(int32 startOffset, int32 endOffset, BRegion *outRegion) const 1850 { 1851 if (!outRegion) 1852 return; 1853 1854 outRegion->MakeEmpty(); 1855 1856 // return an empty region if the range is invalid 1857 if (startOffset >= endOffset) 1858 return; 1859 1860 float startLineHeight = 0.0; 1861 float endLineHeight = 0.0; 1862 BPoint startPt = PointAt(startOffset, &startLineHeight); 1863 BPoint endPt = PointAt(endOffset, &endLineHeight); 1864 1865 startLineHeight = ceilf(startLineHeight); 1866 endLineHeight = ceilf(endLineHeight); 1867 1868 BRect selRect; 1869 1870 if (startPt.y == endPt.y) { 1871 // this is a one-line region 1872 selRect.left = max_c(startPt.x, fTextRect.left); 1873 selRect.top = startPt.y; 1874 selRect.right = endPt.x - 1.0; 1875 selRect.bottom = endPt.y + endLineHeight - 1.0; 1876 outRegion->Include(selRect); 1877 } else { 1878 // more than one line in the specified offset range 1879 selRect.left = max_c(startPt.x, fTextRect.left); 1880 selRect.top = startPt.y; 1881 selRect.right = fTextRect.right; 1882 selRect.bottom = startPt.y + startLineHeight - 1.0; 1883 outRegion->Include(selRect); 1884 1885 if (startPt.y + startLineHeight < endPt.y) { 1886 // more than two lines in the range 1887 selRect.left = fTextRect.left; 1888 selRect.top = startPt.y + startLineHeight; 1889 selRect.right = fTextRect.right; 1890 selRect.bottom = endPt.y - 1.0; 1891 outRegion->Include(selRect); 1892 } 1893 1894 selRect.left = fTextRect.left; 1895 selRect.top = endPt.y; 1896 selRect.right = endPt.x - 1.0; 1897 selRect.bottom = endPt.y + endLineHeight - 1.0; 1898 outRegion->Include(selRect); 1899 } 1900 } 1901 1902 1903 /*! \brief Scrolls the text so that the character at "inOffset" is within the visible range. 1904 \param inOffset The offset of the character. 1905 */ 1906 void 1907 BTextView::ScrollToOffset(int32 inOffset) 1908 { 1909 _ScrollToOffset(inOffset, ScrollBar(B_HORIZONTAL) != NULL, ScrollBar(B_VERTICAL) != NULL); 1910 } 1911 1912 1913 void 1914 BTextView::_ScrollToOffset(int32 inOffset, bool useHorizontal, bool useVertical) 1915 { 1916 BRect bounds = Bounds(); 1917 float lineHeight = 0.0; 1918 float xDiff = 0.0; 1919 float yDiff = 0.0; 1920 BPoint point = PointAt(inOffset, &lineHeight); 1921 1922 if (useHorizontal) { 1923 if (point.x < bounds.left) 1924 xDiff = point.x - bounds.left - bounds.IntegerWidth() / 2; 1925 else if (point.x >= bounds.right) 1926 xDiff = point.x - bounds.right + bounds.IntegerWidth() / 2; 1927 } 1928 1929 if (useVertical) { 1930 if (point.y < bounds.top) 1931 yDiff = point.y - bounds.top - bounds.IntegerHeight() / 2; 1932 else if (point.y >= bounds.bottom) 1933 yDiff = point.y - bounds.bottom + bounds.IntegerHeight() / 2; 1934 } 1935 1936 ScrollBy(xDiff, yDiff); 1937 } 1938 1939 1940 /*! \brief Scrolls the text so that the character which begins the current selection 1941 is within the visible range. 1942 \param inOffset The offset of the character. 1943 */ 1944 void 1945 BTextView::ScrollToSelection() 1946 { 1947 ScrollToOffset(fSelStart); 1948 } 1949 1950 1951 /*! \brief Highlight the text comprised between the given offset. 1952 \param startOffset The offset of the text to highlight. 1953 \param endOffset The offset where the text to highlight ends. 1954 */ 1955 void 1956 BTextView::Highlight(int32 startOffset, int32 endOffset) 1957 { 1958 // get real 1959 if (startOffset >= endOffset) 1960 return; 1961 1962 BRegion selRegion; 1963 GetTextRegion(startOffset, endOffset, &selRegion); 1964 1965 SetDrawingMode(B_OP_INVERT); 1966 FillRegion(&selRegion, B_SOLID_HIGH); 1967 SetDrawingMode(B_OP_COPY); 1968 } 1969 1970 1971 /*! \brief Sets the BTextView's text rectangle to be the same as the passed rect. 1972 \param rect A BRect. 1973 */ 1974 void 1975 BTextView::SetTextRect(BRect rect) 1976 { 1977 if (rect == fTextRect) 1978 return; 1979 1980 fTextRect = rect; 1981 1982 if (Window() != NULL) { 1983 Invalidate(); 1984 Window()->UpdateIfNeeded(); 1985 } 1986 } 1987 1988 1989 /*! \brief Returns the current BTextView's text rectangle. 1990 \return The current text rectangle. 1991 */ 1992 BRect 1993 BTextView::TextRect() const 1994 { 1995 return fTextRect; 1996 } 1997 1998 1999 /*! \brief Sets whether the BTextView accepts multiple character styles. 2000 */ 2001 void 2002 BTextView::SetStylable(bool stylable) 2003 { 2004 fStylable = stylable; 2005 } 2006 2007 2008 /*! \brief Tells if the object is stylable. 2009 \return true if the object is stylable, false otherwise. 2010 If the object is stylable, it can show multiple fonts at the same time. 2011 */ 2012 bool 2013 BTextView::IsStylable() const 2014 { 2015 return fStylable; 2016 } 2017 2018 2019 /*! \brief Sets the distance between tab stops (in pixel). 2020 \param width The distance (in pixel) between tab stops. 2021 */ 2022 void 2023 BTextView::SetTabWidth(float width) 2024 { 2025 if (width == fTabWidth) 2026 return; 2027 2028 fTabWidth = width; 2029 2030 if (Window() != NULL) 2031 _Refresh(0, fText->Length(), true, false); 2032 } 2033 2034 2035 /*! \brief Returns the BTextView's tab width. 2036 \return The BTextView's tab width. 2037 */ 2038 float 2039 BTextView::TabWidth() const 2040 { 2041 return fTabWidth; 2042 } 2043 2044 2045 /*! \brief Makes the object selectable, or not selectable. 2046 \param selectable If true, the object will be selectable from now on. 2047 if false, it won't be selectable anymore. 2048 */ 2049 void 2050 BTextView::MakeSelectable(bool selectable) 2051 { 2052 if (selectable == fSelectable) 2053 return; 2054 2055 fSelectable = selectable; 2056 2057 if (Window() != NULL) { 2058 if (fActive) { 2059 // show/hide the caret, hilite/unhilite the selection 2060 if (fSelStart != fSelEnd) 2061 Highlight(fSelStart, fSelEnd); 2062 else 2063 _InvertCaret(); 2064 } 2065 } 2066 } 2067 2068 2069 /*! \brief Tells if the object is selectable 2070 \return \c true if the object is selectable, 2071 \c false if not. 2072 */ 2073 bool 2074 BTextView::IsSelectable() const 2075 { 2076 return fSelectable; 2077 } 2078 2079 2080 /*! \brief Set (or remove) the editable property for the object. 2081 \param editable If true, will make the object editable, 2082 if false, will make it not editable. 2083 */ 2084 void 2085 BTextView::MakeEditable(bool editable) 2086 { 2087 if (editable == fEditable) 2088 return; 2089 2090 fEditable = editable; 2091 // TextControls change the color of the text when 2092 // they are made editable, so we need to invalidate 2093 // the NULL style here 2094 // TODO: it works well, but it could be caused by a bug somewhere else 2095 if (fEditable) 2096 fStyles->InvalidateNullStyle(); 2097 if (Window() != NULL && fActive) { 2098 if (!fEditable) { 2099 _HideCaret(); 2100 _CancelInputMethod(); 2101 } 2102 } 2103 } 2104 2105 2106 /*! \brief Tells if the object is editable. 2107 \return \c true if the object is editable, 2108 \c false if not. 2109 */ 2110 bool 2111 BTextView::IsEditable() const 2112 { 2113 return fEditable; 2114 } 2115 2116 2117 /*! \brief Set (or unset) word wrapping mode. 2118 \param wrap Specifies if you want word wrapping active or not. 2119 */ 2120 void 2121 BTextView::SetWordWrap(bool wrap) 2122 { 2123 if (wrap == fWrap) 2124 return; 2125 2126 if (Window() != NULL) { 2127 if (fActive) { 2128 // hide the caret, unhilite the selection 2129 if (fSelStart != fSelEnd) 2130 Highlight(fSelStart, fSelEnd); 2131 else { 2132 _HideCaret(); 2133 } 2134 } 2135 2136 fWrap = wrap; 2137 _Refresh(0, fText->Length(), true, true); 2138 2139 if (fActive) { 2140 // show the caret, hilite the selection 2141 if (fSelStart != fSelEnd && fSelectable) 2142 Highlight(fSelStart, fSelEnd); 2143 else 2144 _ShowCaret(); 2145 } 2146 } 2147 } 2148 2149 2150 /*! \brief Tells if word wrapping is activated. 2151 \return true if word wrapping is active, false otherwise. 2152 */ 2153 bool 2154 BTextView::DoesWordWrap() const 2155 { 2156 return fWrap; 2157 } 2158 2159 2160 /*! \brief Sets the maximun number of bytes that the BTextView can contain. 2161 \param max The new max number of bytes. 2162 */ 2163 void 2164 BTextView::SetMaxBytes(int32 max) 2165 { 2166 const int32 textLength = fText->Length(); 2167 fMaxBytes = max; 2168 2169 if (fMaxBytes < textLength) { 2170 int32 offset = fMaxBytes; 2171 // Delete the text after fMaxBytes, but 2172 // respect multibyte characters boundaries. 2173 const int32 previousInitial = _PreviousInitialByte(offset); 2174 if (_NextInitialByte(previousInitial) != offset) 2175 offset = previousInitial; 2176 2177 Delete(offset, textLength); 2178 } 2179 } 2180 2181 2182 /*! \brief Returns the maximum number of bytes that the BTextView can contain. 2183 \return the maximum number of bytes that the BTextView can contain. 2184 */ 2185 int32 2186 BTextView::MaxBytes() const 2187 { 2188 return fMaxBytes; 2189 } 2190 2191 2192 /*! \brief Adds the given char to the disallowed chars list. 2193 \param aChar The character to add to the list. 2194 2195 After this function returns, the given character won't be accepted 2196 by the textview anymore. 2197 */ 2198 void 2199 BTextView::DisallowChar(uint32 aChar) 2200 { 2201 if (fDisallowedChars == NULL) 2202 fDisallowedChars = new BList; 2203 if (!fDisallowedChars->HasItem(reinterpret_cast<void *>(aChar))) 2204 fDisallowedChars->AddItem(reinterpret_cast<void *>(aChar)); 2205 } 2206 2207 2208 /*! \brief Removes the given character from the disallowed list. 2209 \param aChar The character to remove from the list. 2210 */ 2211 void 2212 BTextView::AllowChar(uint32 aChar) 2213 { 2214 if (fDisallowedChars != NULL) 2215 fDisallowedChars->RemoveItem(reinterpret_cast<void *>(aChar)); 2216 } 2217 2218 2219 /*! \brief Sets the way text is aligned within the text rectangle. 2220 \param flag The new alignment. 2221 */ 2222 void 2223 BTextView::SetAlignment(alignment flag) 2224 { 2225 // Do a reality check 2226 if (fAlignment != flag && 2227 (flag == B_ALIGN_LEFT || 2228 flag == B_ALIGN_RIGHT || 2229 flag == B_ALIGN_CENTER)) { 2230 fAlignment = flag; 2231 2232 // After setting new alignment, update the view/window 2233 BWindow *window = Window(); 2234 if (window) { 2235 Invalidate(); 2236 window->UpdateIfNeeded(); 2237 } 2238 } 2239 } 2240 2241 2242 /*! \brief Returns the current alignment of the text. 2243 \return The current alignment. 2244 */ 2245 alignment 2246 BTextView::Alignment() const 2247 { 2248 return fAlignment; 2249 } 2250 2251 2252 /*! \brief Sets wheter a new line of text is automatically indented. 2253 \param state The new autoindent state 2254 */ 2255 void 2256 BTextView::SetAutoindent(bool state) 2257 { 2258 fAutoindent = state; 2259 } 2260 2261 2262 /*! \brief Returns the current autoindent state. 2263 \return The current autoindent state. 2264 */ 2265 bool 2266 BTextView::DoesAutoindent() const 2267 { 2268 return fAutoindent; 2269 } 2270 2271 2272 /*! \brief Set the color space for the offscreen BBitmap. 2273 \param colors The new colorspace for the offscreen BBitmap. 2274 */ 2275 void 2276 BTextView::SetColorSpace(color_space colors) 2277 { 2278 if (colors != fColorSpace && fOffscreen) { 2279 fColorSpace = colors; 2280 _DeleteOffscreen(); 2281 _NewOffscreen(); 2282 } 2283 } 2284 2285 2286 /*! \brief Returns the colorspace of the offscreen BBitmap, if any. 2287 \return The colorspace of the BTextView's offscreen BBitmap. 2288 */ 2289 color_space 2290 BTextView::ColorSpace() const 2291 { 2292 return fColorSpace; 2293 } 2294 2295 2296 /*! \brief Gives to the BTextView the ability to automatically resize itself when needed. 2297 \param resize If true, the BTextView will automatically resize itself. 2298 \param resizeView The BTextView's parent view, it's the view which resizes itself. 2299 The resizing mechanism is alternative to the BView resizing. The container view 2300 (the one passed to this function) should not automatically resize itself when the parent is 2301 resized. 2302 */ 2303 void 2304 BTextView::MakeResizable(bool resize, BView *resizeView) 2305 { 2306 if (resize) { 2307 fResizable = true; 2308 fContainerView = resizeView; 2309 2310 // Wrapping mode and resizable mode can't live together 2311 if (fWrap) { 2312 fWrap = false; 2313 2314 if (fActive && Window() != NULL) { 2315 if (fSelStart != fSelEnd && fSelectable) 2316 Highlight(fSelStart, fSelEnd); 2317 else 2318 _HideCaret(); 2319 } 2320 } 2321 } else { 2322 fResizable = false; 2323 fContainerView = NULL; 2324 if (fOffscreen) 2325 _DeleteOffscreen(); 2326 _NewOffscreen(); 2327 } 2328 2329 _Refresh(0, fText->Length(), true, false); 2330 } 2331 2332 2333 /*! \brief Returns whether the BTextView is currently resizable. 2334 \returns whether the BTextView is currently resizable. 2335 */ 2336 bool 2337 BTextView::IsResizable() const 2338 { 2339 return fResizable; 2340 } 2341 2342 2343 /*! \brief Enables or disables the undo mechanism. 2344 \param undo If true enables the undo mechanism, if false, disables it. 2345 */ 2346 void 2347 BTextView::SetDoesUndo(bool undo) 2348 { 2349 if (undo && fUndo == NULL) 2350 fUndo = new _BUndoBuffer_(this, B_UNDO_UNAVAILABLE); 2351 else if (!undo && fUndo != NULL) { 2352 delete fUndo; 2353 fUndo = NULL; 2354 } 2355 } 2356 2357 2358 /*! \brief Tells if the object is undoable. 2359 \return Whether the object is undoable. 2360 */ 2361 bool 2362 BTextView::DoesUndo() const 2363 { 2364 return fUndo != NULL; 2365 } 2366 2367 2368 void 2369 BTextView::HideTyping(bool enabled) 2370 { 2371 if (enabled) 2372 Delete(0, fText->Length()); 2373 2374 fText->SetPasswordMode(enabled); 2375 } 2376 2377 2378 bool 2379 BTextView::IsTypingHidden() const 2380 { 2381 return fText->PasswordMode(); 2382 } 2383 2384 2385 void 2386 BTextView::ResizeToPreferred() 2387 { 2388 float widht, height; 2389 GetPreferredSize(&widht, &height); 2390 BView::ResizeTo(widht, height); 2391 } 2392 2393 2394 void 2395 BTextView::GetPreferredSize(float *width, float *height) 2396 { 2397 BView::GetPreferredSize(width, height); 2398 } 2399 2400 2401 void 2402 BTextView::AllAttached() 2403 { 2404 BView::AllAttached(); 2405 } 2406 2407 2408 void 2409 BTextView::AllDetached() 2410 { 2411 BView::AllDetached(); 2412 } 2413 2414 2415 /* static */ 2416 text_run_array * 2417 BTextView::AllocRunArray(int32 entryCount, int32 *outSize) 2418 { 2419 int32 size = sizeof(text_run_array) + (entryCount - 1) * sizeof(text_run); 2420 2421 text_run_array *runArray = (text_run_array *)malloc(size); 2422 if (runArray == NULL) { 2423 if (outSize != NULL) 2424 *outSize = 0; 2425 return NULL; 2426 } 2427 2428 memset(runArray, 0, sizeof(size)); 2429 2430 runArray->count = entryCount; 2431 2432 // Call constructors explicitly as the text_run_array 2433 // was allocated with malloc (and has to, for backwards 2434 // compatibility) 2435 for (int32 i = 0; i < runArray->count; i++) { 2436 new (&runArray->runs[i].font) BFont; 2437 } 2438 2439 if (outSize != NULL) 2440 *outSize = size; 2441 2442 return runArray; 2443 } 2444 2445 2446 /* static */ 2447 text_run_array * 2448 BTextView::CopyRunArray(const text_run_array *orig, int32 countDelta) 2449 { 2450 text_run_array *copy = AllocRunArray(countDelta, NULL); 2451 if (copy != NULL) { 2452 for (int32 i = 0; i < countDelta; i++) { 2453 copy->runs[i].offset = orig->runs[i].offset; 2454 copy->runs[i].font = orig->runs[i].font; 2455 copy->runs[i].color = orig->runs[i].color; 2456 } 2457 } 2458 return copy; 2459 } 2460 2461 2462 /* static */ 2463 void 2464 BTextView::FreeRunArray(text_run_array *array) 2465 { 2466 if (array == NULL) 2467 return; 2468 2469 // Call destructors explicitly 2470 for (int32 i = 0; i < array->count; i++) 2471 array->runs[i].font.~BFont(); 2472 2473 free(array); 2474 } 2475 2476 2477 /* static */ 2478 void * 2479 BTextView::FlattenRunArray(const text_run_array* runArray, int32* _size) 2480 { 2481 CALLED(); 2482 int32 size = sizeof(flattened_text_run_array) + (runArray->count - 1) 2483 * sizeof(flattened_text_run); 2484 2485 flattened_text_run_array *array = (flattened_text_run_array *)malloc(size); 2486 if (array == NULL) { 2487 if (_size) 2488 *_size = 0; 2489 return NULL; 2490 } 2491 2492 array->magic = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayMagic); 2493 array->version = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayVersion); 2494 array->count = B_HOST_TO_BENDIAN_INT32(runArray->count); 2495 2496 for (int32 i = 0; i < runArray->count; i++) { 2497 array->styles[i].offset = B_HOST_TO_BENDIAN_INT32(runArray->runs[i].offset); 2498 runArray->runs[i].font.GetFamilyAndStyle(&array->styles[i].family, 2499 &array->styles[i].style); 2500 array->styles[i].size = B_HOST_TO_BENDIAN_FLOAT(runArray->runs[i].font.Size()); 2501 array->styles[i].shear = B_HOST_TO_BENDIAN_FLOAT(runArray->runs[i].font.Shear()); 2502 array->styles[i].face = B_HOST_TO_BENDIAN_INT16(runArray->runs[i].font.Face()); 2503 array->styles[i].red = runArray->runs[i].color.red; 2504 array->styles[i].green = runArray->runs[i].color.green; 2505 array->styles[i].blue = runArray->runs[i].color.blue; 2506 array->styles[i].alpha = 255; 2507 array->styles[i]._reserved_ = 0; 2508 } 2509 2510 if (_size) 2511 *_size = size; 2512 2513 return array; 2514 } 2515 2516 2517 /* static */ 2518 text_run_array * 2519 BTextView::UnflattenRunArray(const void* data, int32* _size) 2520 { 2521 CALLED(); 2522 flattened_text_run_array *array = (flattened_text_run_array *)data; 2523 2524 if (B_BENDIAN_TO_HOST_INT32(array->magic) != kFlattenedTextRunArrayMagic 2525 || B_BENDIAN_TO_HOST_INT32(array->version) != kFlattenedTextRunArrayVersion) { 2526 if (_size) 2527 *_size = 0; 2528 2529 return NULL; 2530 } 2531 2532 int32 count = B_BENDIAN_TO_HOST_INT32(array->count); 2533 2534 text_run_array *runArray = AllocRunArray(count, _size); 2535 if (runArray == NULL) 2536 return NULL; 2537 2538 for (int32 i = 0; i < count; i++) { 2539 runArray->runs[i].offset = B_BENDIAN_TO_HOST_INT32(array->styles[i].offset); 2540 2541 // Set family and style independently from each other, so that 2542 // even if the family doesn't exist, we try to preserve the style 2543 runArray->runs[i].font.SetFamilyAndStyle(array->styles[i].family, NULL); 2544 runArray->runs[i].font.SetFamilyAndStyle(NULL, array->styles[i].style); 2545 2546 runArray->runs[i].font.SetSize(B_BENDIAN_TO_HOST_FLOAT(array->styles[i].size)); 2547 runArray->runs[i].font.SetShear(B_BENDIAN_TO_HOST_FLOAT(array->styles[i].shear)); 2548 2549 uint16 face = B_BENDIAN_TO_HOST_INT16(array->styles[i].face); 2550 if (face != B_REGULAR_FACE) { 2551 // Be's version doesn't seem to set this correctly 2552 runArray->runs[i].font.SetFace(face); 2553 } 2554 2555 runArray->runs[i].color.red = array->styles[i].red; 2556 runArray->runs[i].color.green = array->styles[i].green; 2557 runArray->runs[i].color.blue = array->styles[i].blue; 2558 runArray->runs[i].color.alpha = array->styles[i].alpha; 2559 } 2560 2561 return runArray; 2562 } 2563 2564 2565 void 2566 BTextView::InsertText(const char *inText, int32 inLength, int32 inOffset, 2567 const text_run_array *inRuns) 2568 { 2569 CALLED(); 2570 // why add nothing? 2571 if (inLength < 1) 2572 return; 2573 2574 // TODO: Pin offset/lenght 2575 // add the text to the buffer 2576 fText->InsertText(inText, inLength, inOffset); 2577 2578 // update the start offsets of each line below offset 2579 fLines->BumpOffset(inLength, LineAt(inOffset) + 1); 2580 2581 // update the style runs 2582 fStyles->BumpOffset(inLength, fStyles->OffsetToRun(inOffset - 1) + 1); 2583 2584 if (inRuns != NULL) { 2585 _SetRunArray(inOffset, inOffset + inLength, inRuns); 2586 } else { 2587 // apply nullStyle to inserted text 2588 fStyles->SyncNullStyle(inOffset); 2589 fStyles->SetStyleRange(inOffset, inOffset + inLength, 2590 fText->Length(), B_FONT_ALL, NULL, NULL); 2591 } 2592 } 2593 2594 2595 void 2596 BTextView::DeleteText(int32 fromOffset, int32 toOffset) 2597 { 2598 CALLED(); 2599 // sanity checking 2600 if (fromOffset >= toOffset || fromOffset < 0 || toOffset > fText->Length()) 2601 return; 2602 2603 // set nullStyle to style at beginning of range 2604 fStyles->InvalidateNullStyle(); 2605 fStyles->SyncNullStyle(fromOffset); 2606 2607 // remove from the text buffer 2608 fText->RemoveRange(fromOffset, toOffset); 2609 2610 // remove any lines that have been obliterated 2611 fLines->RemoveLineRange(fromOffset, toOffset); 2612 2613 // remove any style runs that have been obliterated 2614 fStyles->RemoveStyleRange(fromOffset, toOffset); 2615 } 2616 2617 2618 /*! \brief Undoes the last changes. 2619 \param clipboard A clipboard to use for the undo operation. 2620 */ 2621 void 2622 BTextView::Undo(BClipboard *clipboard) 2623 { 2624 if (fUndo) 2625 fUndo->Undo(clipboard); 2626 } 2627 2628 2629 undo_state 2630 BTextView::UndoState(bool *isRedo) const 2631 { 2632 return fUndo == NULL ? B_UNDO_UNAVAILABLE : fUndo->State(isRedo); 2633 } 2634 2635 2636 void 2637 BTextView::GetDragParameters(BMessage *drag, BBitmap **bitmap, BPoint *point, 2638 BHandler **handler) 2639 { 2640 CALLED(); 2641 if (drag == NULL) 2642 return; 2643 2644 // Add originator and action 2645 drag->AddPointer("be:originator", this); 2646 drag->AddInt32("be_actions", B_TRASH_TARGET); 2647 2648 // add the text 2649 int32 numBytes = fSelEnd - fSelStart; 2650 const char* text = fText->GetString(fSelStart, &numBytes); 2651 drag->AddData("text/plain", B_MIME_TYPE, text, numBytes); 2652 2653 // add the corresponding styles 2654 int32 size = 0; 2655 text_run_array *styles = RunArray(fSelStart, fSelEnd, &size); 2656 2657 if (styles != NULL) { 2658 drag->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE, 2659 styles, size); 2660 2661 FreeRunArray(styles); 2662 } 2663 2664 if (bitmap != NULL) 2665 *bitmap = NULL; 2666 if (handler != NULL) 2667 *handler = NULL; 2668 } 2669 2670 2671 void BTextView::_ReservedTextView3() {} 2672 void BTextView::_ReservedTextView4() {} 2673 void BTextView::_ReservedTextView5() {} 2674 void BTextView::_ReservedTextView6() {} 2675 void BTextView::_ReservedTextView7() {} 2676 void BTextView::_ReservedTextView8() {} 2677 void BTextView::_ReservedTextView9() {} 2678 void BTextView::_ReservedTextView10() {} 2679 void BTextView::_ReservedTextView11() {} 2680 void BTextView::_ReservedTextView12() {} 2681 2682 2683 /*! \brief Inits the BTextView object. 2684 \param textRect The BTextView's text rect. 2685 \param initialFont The font which the BTextView will use. 2686 \param initialColor The initial color of the text. 2687 */ 2688 void 2689 BTextView::_InitObject(BRect textRect, const BFont *initialFont, 2690 const rgb_color *initialColor) 2691 { 2692 BFont font; 2693 if (initialFont == NULL) 2694 GetFont(&font); 2695 else 2696 font = *initialFont; 2697 2698 _NormalizeFont(&font); 2699 2700 if (initialColor == NULL) 2701 initialColor = &kBlackColor; 2702 2703 fText = new _BTextGapBuffer_; 2704 fLines = new _BLineBuffer_; 2705 fStyles = new _BStyleBuffer_(&font, initialColor); 2706 2707 // We put these here instead of in the constructor initializer list 2708 // to have less code duplication, and a single place where to do changes 2709 // if needed., 2710 fTextRect = textRect; 2711 fSelStart = fSelEnd = 0; 2712 fCaretVisible = false; 2713 fCaretTime = 0; 2714 fClickOffset = 0; 2715 fClickCount = 0; 2716 fClickTime = 0; 2717 fDragOffset = -1; 2718 fCursor = 0; 2719 fActive = false; 2720 fStylable = false; 2721 fTabWidth = 28.0; 2722 fSelectable = true; 2723 fEditable = true; 2724 fWrap = true; 2725 fMaxBytes = LONG_MAX; 2726 fDisallowedChars = NULL; 2727 fAlignment = B_ALIGN_LEFT; 2728 fAutoindent = false; 2729 fOffscreen = NULL; 2730 fColorSpace = B_CMAP8; 2731 fResizable = false; 2732 fContainerView = NULL; 2733 fUndo = NULL; 2734 fInline = NULL; 2735 fDragRunner = NULL; 2736 fClickRunner = NULL; 2737 fTrackingMouse = NULL; 2738 fTextChange = NULL; 2739 } 2740 2741 2742 /*! \brief Called when Backspace key is pressed. 2743 */ 2744 void 2745 BTextView::_HandleBackspace() 2746 { 2747 if (fUndo) { 2748 _BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>( 2749 fUndo); 2750 if (!undoBuffer) { 2751 delete fUndo; 2752 fUndo = undoBuffer = new _BTypingUndoBuffer_(this); 2753 } 2754 undoBuffer->BackwardErase(); 2755 } 2756 2757 if (fSelStart == fSelEnd) { 2758 if (fSelStart == 0) 2759 return; 2760 else 2761 fSelStart = _PreviousInitialByte(fSelStart); 2762 } else 2763 Highlight(fSelStart, fSelEnd); 2764 2765 DeleteText(fSelStart, fSelEnd); 2766 fClickOffset = fSelEnd = fSelStart; 2767 2768 _Refresh(fSelStart, fSelEnd, true, true); 2769 } 2770 2771 2772 /*! \brief Called when any arrow key is pressed. 2773 \param inArrowKey The code for the pressed key. 2774 */ 2775 void 2776 BTextView::_HandleArrowKey(uint32 inArrowKey) 2777 { 2778 // return if there's nowhere to go 2779 if (fText->Length() == 0) 2780 return; 2781 2782 int32 selStart = fSelStart; 2783 int32 selEnd = fSelEnd; 2784 2785 int32 modifiers = 0; 2786 BMessage *message = Window()->CurrentMessage(); 2787 if (message != NULL) 2788 message->FindInt32("modifiers", &modifiers); 2789 2790 bool shiftDown = modifiers & B_SHIFT_KEY; 2791 2792 int32 currentOffset = fClickOffset; 2793 switch (inArrowKey) { 2794 case B_LEFT_ARROW: 2795 if (shiftDown) { 2796 fClickOffset = _PreviousInitialByte(fClickOffset); 2797 if (fClickOffset != currentOffset) { 2798 if (fClickOffset >= fSelStart) 2799 selEnd = fClickOffset; 2800 else 2801 selStart = fClickOffset; 2802 } 2803 } else if (fSelStart != fSelEnd) 2804 fClickOffset = fSelStart; 2805 else 2806 fClickOffset = _PreviousInitialByte(fSelStart); 2807 2808 break; 2809 2810 case B_RIGHT_ARROW: 2811 if (shiftDown) { 2812 fClickOffset = _NextInitialByte(fClickOffset); 2813 if (fClickOffset != currentOffset) { 2814 if (fClickOffset <= fSelEnd) 2815 selStart = fClickOffset; 2816 else 2817 selEnd = fClickOffset; 2818 } 2819 } else if (fSelStart != fSelEnd) 2820 fClickOffset = fSelEnd; 2821 else 2822 fClickOffset = _NextInitialByte(fSelEnd); 2823 break; 2824 2825 case B_UP_ARROW: 2826 { 2827 float height; 2828 BPoint point = PointAt(fClickOffset, &height); 2829 point.y -= height; 2830 fClickOffset = OffsetAt(point); 2831 if (shiftDown) { 2832 if (fClickOffset != currentOffset) { 2833 if (fClickOffset >= fSelStart) 2834 selEnd = fClickOffset; 2835 else 2836 selStart = fClickOffset; 2837 } 2838 } 2839 break; 2840 } 2841 2842 case B_DOWN_ARROW: 2843 { 2844 float height; 2845 BPoint point = PointAt(fClickOffset, &height); 2846 point.y += height; 2847 fClickOffset = OffsetAt(point); 2848 if (shiftDown) { 2849 if (fClickOffset != currentOffset) { 2850 if (fClickOffset <= fSelEnd) 2851 selStart = fClickOffset; 2852 else 2853 selEnd = fClickOffset; 2854 } 2855 } 2856 break; 2857 } 2858 } 2859 2860 // invalidate the null style 2861 fStyles->InvalidateNullStyle(); 2862 2863 currentOffset = fClickOffset; 2864 if (shiftDown) 2865 Select(selStart, selEnd); 2866 else 2867 Select(fClickOffset, fClickOffset); 2868 2869 fClickOffset = currentOffset; 2870 // Select sets fClickOffset = fSelEnd 2871 2872 // scroll if needed 2873 ScrollToOffset(fClickOffset); 2874 } 2875 2876 2877 /*! \brief Called when the Delete key is pressed. 2878 */ 2879 void 2880 BTextView::_HandleDelete() 2881 { 2882 if (fUndo) { 2883 _BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>( 2884 fUndo); 2885 if (!undoBuffer) { 2886 delete fUndo; 2887 fUndo = undoBuffer = new _BTypingUndoBuffer_(this); 2888 } 2889 undoBuffer->ForwardErase(); 2890 } 2891 2892 if (fSelStart == fSelEnd) { 2893 if (fSelEnd == fText->Length()) 2894 return; 2895 else 2896 fSelEnd = _NextInitialByte(fSelEnd); 2897 } else 2898 Highlight(fSelStart, fSelEnd); 2899 2900 DeleteText(fSelStart, fSelEnd); 2901 2902 fClickOffset = fSelEnd = fSelStart; 2903 2904 _Refresh(fSelStart, fSelEnd, true, true); 2905 } 2906 2907 2908 /*! \brief Called when a "Page key" is pressed. 2909 \param inPageKey The page key which has been pressed. 2910 */ 2911 void 2912 BTextView::_HandlePageKey(uint32 inPageKey) 2913 { 2914 int32 mods = 0; 2915 BMessage *currentMessage = Window()->CurrentMessage(); 2916 if (currentMessage) 2917 currentMessage->FindInt32("modifiers", &mods); 2918 2919 bool shiftDown = mods & B_SHIFT_KEY; 2920 STELine* line = NULL; 2921 int32 start = fSelStart, end = fSelEnd; 2922 2923 switch (inPageKey) { 2924 case B_HOME: 2925 line = (*fLines)[CurrentLine()]; 2926 fClickOffset = line->offset; 2927 if (shiftDown) { 2928 if (fClickOffset <= fSelStart) { 2929 start = fClickOffset; 2930 end = fSelEnd; 2931 } else { 2932 start = fSelStart; 2933 end = fClickOffset; 2934 } 2935 } else 2936 start = end = fClickOffset; 2937 2938 break; 2939 2940 case B_END: 2941 // If we are on the last line, just go to the last 2942 // character in the buffer, otherwise get the starting 2943 // offset of the next line, and go to the previous character 2944 if (CurrentLine() + 1 < fLines->NumLines()) { 2945 line = (*fLines)[CurrentLine() + 1]; 2946 fClickOffset = _PreviousInitialByte(line->offset); 2947 } else { 2948 // This check if needed to avoid moving the cursor 2949 // when the cursor is on the last line, and that line 2950 // is empty 2951 if (fClickOffset != fText->Length()) { 2952 fClickOffset = fText->Length(); 2953 if (ByteAt(fClickOffset - 1) == B_ENTER) 2954 fClickOffset--; 2955 } 2956 } 2957 2958 if (shiftDown) { 2959 if (fClickOffset >= fSelEnd) { 2960 start = fSelStart; 2961 end = fClickOffset; 2962 } else { 2963 start = fClickOffset; 2964 end = fSelEnd; 2965 } 2966 } else 2967 start = end = fClickOffset; 2968 2969 break; 2970 2971 case B_PAGE_UP: 2972 { 2973 BPoint currentPos = PointAt(fClickOffset); 2974 2975 currentPos.y -= Bounds().Height(); 2976 fClickOffset = OffsetAt(LineAt(currentPos)); 2977 2978 if (shiftDown) { 2979 if (fClickOffset <= fSelStart) { 2980 start = fClickOffset; 2981 end = fSelEnd; 2982 } else { 2983 start = fSelStart; 2984 end = fClickOffset; 2985 } 2986 } else 2987 start = end = fClickOffset; 2988 break; 2989 } 2990 2991 case B_PAGE_DOWN: 2992 { 2993 BPoint currentPos = PointAt(fClickOffset); 2994 2995 currentPos.y += Bounds().Height(); 2996 fClickOffset = OffsetAt(LineAt(currentPos) + 1); 2997 2998 if (shiftDown) { 2999 if (fClickOffset >= fSelEnd) { 3000 start = fSelStart; 3001 end = fClickOffset; 3002 } else { 3003 start = fClickOffset; 3004 end = fSelEnd; 3005 } 3006 } else 3007 start = end = fClickOffset; 3008 3009 break; 3010 } 3011 } 3012 3013 ScrollToOffset(fClickOffset); 3014 Select(start, end); 3015 } 3016 3017 3018 /*! \brief Called when an alphanumeric key is pressed. 3019 \param bytes The string or character associated with the key. 3020 \param numBytes The amount of bytes containes in "bytes". 3021 */ 3022 void 3023 BTextView::_HandleAlphaKey(const char *bytes, int32 numBytes) 3024 { 3025 // TODO: block input if not editable (Andrew) 3026 if (fUndo) { 3027 _BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>(fUndo); 3028 if (!undoBuffer) { 3029 delete fUndo; 3030 fUndo = undoBuffer = new _BTypingUndoBuffer_(this); 3031 } 3032 undoBuffer->InputCharacter(numBytes); 3033 } 3034 3035 bool erase = fSelStart != fText->Length(); 3036 3037 if (fSelStart != fSelEnd) { 3038 Highlight(fSelStart, fSelEnd); 3039 DeleteText(fSelStart, fSelEnd); 3040 erase = true; 3041 } 3042 3043 if (fAutoindent && numBytes == 1 && *bytes == B_ENTER) { 3044 int32 start, offset; 3045 start = offset = OffsetAt(LineAt(fSelStart)); 3046 3047 while (ByteAt(offset) != '\0' && 3048 (ByteAt(offset) == B_TAB || ByteAt(offset) == B_SPACE)) 3049 offset++; 3050 3051 if (start != offset) 3052 _DoInsertText(Text() + start, offset - start, fSelStart, NULL, NULL); 3053 3054 _DoInsertText(bytes, numBytes, fSelStart, NULL, NULL); 3055 3056 } else 3057 _DoInsertText(bytes, numBytes, fSelStart, NULL, NULL); 3058 3059 fClickOffset = fSelEnd; 3060 3061 ScrollToOffset(fClickOffset); 3062 } 3063 3064 3065 /*! \brief Redraw the text comprised between the two given offsets, 3066 recalculating linebreaks if needed. 3067 \param fromOffset The offset from where to refresh. 3068 \param toOffset The offset where to refresh to. 3069 \param erase If true, the function will also erase the textview content 3070 in the parts where text isn't present. 3071 \param scroll If true, function will scroll the view to the end offset. 3072 */ 3073 void 3074 BTextView::_Refresh(int32 fromOffset, int32 toOffset, bool erase, bool scroll) 3075 { 3076 // TODO: Cleanup 3077 float saveHeight = fTextRect.Height(); 3078 int32 fromLine = LineAt(fromOffset); 3079 int32 toLine = LineAt(toOffset); 3080 int32 saveFromLine = fromLine; 3081 int32 saveToLine = toLine; 3082 float saveLineHeight = LineHeight(fromLine); 3083 3084 _RecalculateLineBreaks(&fromLine, &toLine); 3085 3086 // TODO: Maybe there is still something we can do without a window... 3087 if (!Window()) 3088 return; 3089 3090 BRect bounds = Bounds(); 3091 float newHeight = fTextRect.Height(); 3092 3093 // if the line breaks have changed, force an erase 3094 if (fromLine != saveFromLine || toLine != saveToLine 3095 || newHeight != saveHeight ) { 3096 erase = true; 3097 fromOffset = -1; 3098 } 3099 3100 if (newHeight != saveHeight) { 3101 // the text area has changed 3102 if (newHeight < saveHeight) 3103 toLine = LineAt(BPoint(0.0f, saveHeight + fTextRect.top)); 3104 else 3105 toLine = LineAt(BPoint(0.0f, newHeight + fTextRect.top)); 3106 } 3107 3108 // draw only those lines that are visible 3109 int32 fromVisible = LineAt(BPoint(0.0f, bounds.top)); 3110 int32 toVisible = LineAt(BPoint(0.0f, bounds.bottom)); 3111 fromLine = max_c(fromVisible, fromLine); 3112 toLine = min_c(toLine, toVisible); 3113 3114 int32 drawOffset = fromOffset; 3115 if (LineHeight(fromLine) != saveLineHeight 3116 || newHeight < saveHeight || fromLine < saveFromLine 3117 || fAlignment != B_ALIGN_LEFT) 3118 drawOffset = (*fLines)[fromLine]->offset; 3119 3120 if (fResizable) 3121 _AutoResize(false); 3122 3123 _DrawLines(fromLine, toLine, drawOffset, erase); 3124 3125 // erase the area below the text 3126 BRect eraseRect = bounds; 3127 eraseRect.top = fTextRect.top + (*fLines)[fLines->NumLines()]->origin; 3128 eraseRect.bottom = fTextRect.top + saveHeight; 3129 if (eraseRect.bottom > eraseRect.top && eraseRect.Intersects(bounds)) { 3130 SetLowColor(ViewColor()); 3131 FillRect(eraseRect, B_SOLID_LOW); 3132 } 3133 3134 // update the scroll bars if the text area has changed 3135 if (newHeight != saveHeight) 3136 _UpdateScrollbars(); 3137 3138 if (scroll) 3139 ScrollToSelection(); 3140 3141 Flush(); 3142 } 3143 3144 3145 void 3146 BTextView::_RecalculateLineBreaks(int32 *startLine, int32 *endLine) 3147 { 3148 // are we insane? 3149 *startLine = (*startLine < 0) ? 0 : *startLine; 3150 *endLine = (*endLine > fLines->NumLines() - 1) ? fLines->NumLines() - 1 : *endLine; 3151 3152 int32 textLength = fText->Length(); 3153 int32 lineIndex = (*startLine > 0) ? *startLine - 1 : 0; 3154 int32 recalThreshold = (*fLines)[*endLine + 1]->offset; 3155 float width = fTextRect.Width(); 3156 STELine* curLine = (*fLines)[lineIndex]; 3157 STELine* nextLine = curLine + 1; 3158 3159 do { 3160 float ascent, descent; 3161 int32 fromOffset = curLine->offset; 3162 int32 toOffset = _FindLineBreak(fromOffset, &ascent, &descent, &width); 3163 3164 // we want to advance at least by one character 3165 int32 nextOffset = _NextInitialByte(fromOffset); 3166 if (toOffset < nextOffset && fromOffset < textLength) 3167 toOffset = nextOffset; 3168 3169 // set the ascent of this line 3170 curLine->ascent = ascent; 3171 3172 lineIndex++; 3173 STELine saveLine = *nextLine; 3174 if (lineIndex > fLines->NumLines() || toOffset < nextLine->offset) { 3175 // the new line comes before the old line start, add a line 3176 STELine newLine; 3177 newLine.offset = toOffset; 3178 newLine.origin = ceilf(curLine->origin + ascent + descent) + 1; 3179 newLine.ascent = 0; 3180 fLines->InsertLine(&newLine, lineIndex); 3181 } else { 3182 // update the exising line 3183 nextLine->offset = toOffset; 3184 nextLine->origin = ceilf(curLine->origin + ascent + descent) + 1; 3185 3186 // remove any lines that start before the current line 3187 while (lineIndex < fLines->NumLines() 3188 && toOffset >= ((*fLines)[lineIndex] + 1)->offset) { 3189 fLines->RemoveLines(lineIndex + 1); 3190 } 3191 3192 nextLine = (*fLines)[lineIndex]; 3193 if (nextLine->offset == saveLine.offset) { 3194 if (nextLine->offset >= recalThreshold) { 3195 if (nextLine->origin != saveLine.origin) 3196 fLines->BumpOrigin(nextLine->origin - saveLine.origin, 3197 lineIndex + 1); 3198 break; 3199 } 3200 } else { 3201 if (lineIndex > 0 && lineIndex == *startLine) 3202 *startLine = lineIndex - 1; 3203 } 3204 } 3205 3206 curLine = (*fLines)[lineIndex]; 3207 nextLine = curLine + 1; 3208 } while (curLine->offset < textLength); 3209 3210 // update the text rect 3211 float newHeight = TextHeight(0, fLines->NumLines() - 1); 3212 fTextRect.bottom = fTextRect.top + newHeight; 3213 3214 *endLine = lineIndex - 1; 3215 *startLine = min_c(*startLine, *endLine); 3216 } 3217 3218 3219 int32 3220 BTextView::_FindLineBreak(int32 fromOffset, float *outAscent, float *outDescent, 3221 float *ioWidth) 3222 { 3223 *outAscent = 0.0; 3224 *outDescent = 0.0; 3225 3226 const int32 limit = fText->Length(); 3227 3228 // is fromOffset at the end? 3229 if (fromOffset >= limit) { 3230 // try to return valid height info anyway 3231 if (fStyles->NumRuns() > 0) { 3232 fStyles->Iterate(fromOffset, 1, fInline, NULL, NULL, outAscent, 3233 outDescent); 3234 } else { 3235 if (fStyles->IsValidNullStyle()) { 3236 const BFont *font = NULL; 3237 fStyles->GetNullStyle(&font, NULL); 3238 3239 font_height fh; 3240 font->GetHeight(&fh); 3241 *outAscent = fh.ascent; 3242 *outDescent = fh.descent + fh.leading; 3243 } 3244 } 3245 3246 return limit; 3247 } 3248 3249 int32 offset = fromOffset; 3250 3251 // Text wrapping is turned off. 3252 // Just find the offset of the first \n character 3253 if (!fWrap) { 3254 offset = limit - fromOffset; 3255 fText->FindChar(B_ENTER, fromOffset, &offset); 3256 offset += fromOffset; 3257 offset = (offset < limit) ? offset + 1 : limit; 3258 3259 *ioWidth = _StyledWidth(fromOffset, offset - fromOffset, outAscent, outDescent); 3260 3261 return offset; 3262 } 3263 3264 bool done = false; 3265 float ascent = 0.0; 3266 float descent = 0.0; 3267 int32 delta = 0; 3268 float deltaWidth = 0.0; 3269 float tabWidth = 0.0; 3270 float strWidth = 0.0; 3271 3272 // wrap the text 3273 do { 3274 bool foundTab = false; 3275 3276 // find the next line break candidate 3277 for ( ; (offset + delta) < limit ; delta++) { 3278 if (CanEndLine(offset + delta)) 3279 break; 3280 } 3281 for ( ; (offset + delta) < limit; delta++) { 3282 uchar theChar = fText->RealCharAt(offset + delta); 3283 if (!CanEndLine(offset + delta)) 3284 break; 3285 3286 if (theChar == B_ENTER) { 3287 // found a newline, we're done! 3288 done = true; 3289 delta++; 3290 break; 3291 } else { 3292 // include all trailing spaces and tabs, 3293 // but not spaces after tabs 3294 if (theChar != B_SPACE && theChar != B_TAB) 3295 break; 3296 else { 3297 if (theChar == B_SPACE && foundTab) 3298 break; 3299 else { 3300 if (theChar == B_TAB) 3301 foundTab = true; 3302 } 3303 } 3304 } 3305 } 3306 delta = max_c(delta, 1); 3307 3308 deltaWidth = _StyledWidth(offset, delta, &ascent, &descent); 3309 strWidth += deltaWidth; 3310 3311 if (!foundTab) 3312 tabWidth = 0.0; 3313 else { 3314 int32 tabCount = 0; 3315 for (int32 i = delta - 1; fText->RealCharAt(offset + i) == B_TAB; 3316 i--) { 3317 tabCount++; 3318 } 3319 3320 tabWidth = _ActualTabWidth(strWidth); 3321 if (tabCount > 1) 3322 tabWidth += ((tabCount - 1) * fTabWidth); 3323 strWidth += tabWidth; 3324 } 3325 3326 if (strWidth >= *ioWidth) { 3327 // we've found where the line will wrap 3328 bool foundNewline = done; 3329 done = true; 3330 int32 pos = delta - 1; 3331 if (!CanEndLine(offset + pos)) 3332 break; 3333 3334 strWidth -= (deltaWidth + tabWidth); 3335 3336 while (offset + pos > offset) { 3337 if (!CanEndLine(offset + pos)) 3338 break; 3339 3340 pos--; 3341 } 3342 3343 strWidth += _StyledWidth(offset, pos + 1, &ascent, &descent); 3344 if (strWidth >= *ioWidth) 3345 break; 3346 3347 if (!foundNewline) { 3348 while (offset + delta < limit) { 3349 const char realChar = fText->RealCharAt(offset + delta); 3350 if (realChar != B_SPACE && realChar != B_TAB) 3351 break; 3352 3353 delta++; 3354 } 3355 if (offset + delta < limit 3356 && fText->RealCharAt(offset + delta) == B_ENTER) 3357 delta++; 3358 } 3359 // get the ascent and descent of the spaces/tabs 3360 _StyledWidth(offset, delta, &ascent, &descent); 3361 } 3362 3363 *outAscent = max_c(ascent, *outAscent); 3364 *outDescent = max_c(descent, *outDescent); 3365 3366 offset += delta; 3367 delta = 0; 3368 } while (offset < limit && !done); 3369 3370 if (offset - fromOffset < 1) { 3371 // there weren't any words that fit entirely in this line 3372 // force a break in the middle of a word 3373 *outAscent = 0.0; 3374 *outDescent = 0.0; 3375 strWidth = 0.0; 3376 3377 int32 current = fromOffset; 3378 for (offset = fromOffset; offset <= limit; current = offset, 3379 offset = _NextInitialByte(offset)) { 3380 strWidth += _StyledWidth(current, offset - current, &ascent, 3381 &descent); 3382 if (strWidth >= *ioWidth) { 3383 offset = _PreviousInitialByte(offset); 3384 break; 3385 } 3386 3387 *outAscent = max_c(ascent, *outAscent); 3388 *outDescent = max_c(descent, *outDescent); 3389 } 3390 } 3391 3392 return min_c(offset, limit); 3393 } 3394 3395 3396 /*! \brief Calculate the width of the text within the given limits. 3397 \param fromOffset The offset where to start. 3398 \param length The length of the text to examine. 3399 \param outAscent A pointer to a float which will contain the maximum ascent. 3400 \param outDescent A pointer to a float which will contain the maximum descent. 3401 \return The width for the text within the given limits. 3402 */ 3403 float 3404 BTextView::_StyledWidth(int32 fromOffset, int32 length, float *outAscent, 3405 float *outDescent) const 3406 { 3407 float result = 0.0; 3408 float ascent = 0.0; 3409 float descent = 0.0; 3410 float maxAscent = 0.0; 3411 float maxDescent = 0.0; 3412 3413 // iterate through the style runs 3414 const BFont *font = NULL; 3415 int32 numChars; 3416 while ((numChars = fStyles->Iterate(fromOffset, length, fInline, &font, 3417 NULL, &ascent, &descent)) != 0) { 3418 maxAscent = max_c(ascent, maxAscent); 3419 maxDescent = max_c(descent, maxDescent); 3420 3421 #if USE_WIDTHBUFFER 3422 // Use _BWidthBuffer_ if possible 3423 if (sWidths != NULL) { 3424 LockWidthBuffer(); 3425 result += sWidths->StringWidth(*fText, fromOffset, numChars, font); 3426 UnlockWidthBuffer(); 3427 } else { 3428 #endif 3429 const char* text = fText->GetString(fromOffset, &numChars); 3430 result += font->StringWidth(text, numChars); 3431 3432 #if USE_WIDTHBUFFER 3433 } 3434 #endif 3435 3436 fromOffset += numChars; 3437 length -= numChars; 3438 } 3439 3440 if (outAscent != NULL) 3441 *outAscent = maxAscent; 3442 if (outDescent != NULL) 3443 *outDescent = maxDescent; 3444 3445 return result; 3446 } 3447 3448 3449 // Unlike the _StyledWidth method, this one takes as parameter 3450 // the number of chars, not the number of bytes. 3451 float 3452 BTextView::_StyledWidthUTF8Safe(int32 fromOffset, int32 numChars, 3453 float *outAscent, float *outDescent) const 3454 { 3455 int32 toOffset = fromOffset; 3456 while (numChars--) 3457 toOffset = _NextInitialByte(toOffset); 3458 3459 const int32 length = toOffset - fromOffset; 3460 return _StyledWidth(fromOffset, length, outAscent, outDescent); 3461 } 3462 3463 3464 /*! \brief Calculate the actual tab width for the given location. 3465 \param location The location to calculate the tab width of. 3466 \return The actual tab width for the given location 3467 */ 3468 float 3469 BTextView::_ActualTabWidth(float location) const 3470 { 3471 return fTabWidth - fmod(location, fTabWidth); 3472 } 3473 3474 3475 void 3476 BTextView::_DoInsertText(const char *inText, int32 inLength, int32 inOffset, 3477 const text_run_array *inRuns, _BTextChangeResult_ *outResult) 3478 { 3479 _CancelInputMethod(); 3480 3481 if (TextLength() + inLength > MaxBytes()) 3482 return; 3483 3484 if (fSelStart != fSelEnd) 3485 Select(fSelStart, fSelStart); 3486 3487 // Don't do any check, the public methods will have adjusted 3488 // eventual bogus values... 3489 3490 const int32 textLength = TextLength(); 3491 if (inOffset > textLength) 3492 inOffset = textLength; 3493 3494 // copy data into buffer 3495 InsertText(inText, inLength, inOffset, inRuns); 3496 3497 // offset the caret/selection 3498 //int32 saveStart = fSelStart; 3499 fSelStart += inLength; 3500 fSelEnd += inLength; 3501 3502 // recalc line breaks and draw the text 3503 _Refresh(inOffset, textLength, true, false); 3504 } 3505 3506 3507 void 3508 BTextView::_DoDeleteText(int32 fromOffset, int32 toOffset, _BTextChangeResult_ *outResult) 3509 { 3510 CALLED(); 3511 } 3512 3513 3514 void 3515 BTextView::_DrawLine(BView *view, const int32 &lineNum, const int32 &startOffset, 3516 const bool &erase, BRect &eraseRect, BRegion &inputRegion) 3517 { 3518 STELine *line = (*fLines)[lineNum]; 3519 float startLeft = fTextRect.left; 3520 if (startOffset != -1) { 3521 if (ByteAt(startOffset) == B_ENTER) { 3522 // StartOffset is a newline 3523 startLeft = PointAt(line->offset).x; 3524 } else 3525 startLeft = PointAt(startOffset).x; 3526 } 3527 3528 int32 length = (line + 1)->offset; 3529 if (startOffset != -1) 3530 length -= startOffset; 3531 else 3532 length -= line->offset; 3533 3534 // DrawString() chokes if you draw a newline 3535 if (ByteAt((line + 1)->offset - 1) == B_ENTER) 3536 length--; 3537 if (fAlignment != B_ALIGN_LEFT) { 3538 // B_ALIGN_RIGHT 3539 startLeft = (fTextRect.right - LineWidth(lineNum)); 3540 if (fAlignment == B_ALIGN_CENTER) 3541 startLeft /= 2; 3542 startLeft += fTextRect.left; 3543 } 3544 3545 view->MovePenTo(startLeft, line->origin + line->ascent + fTextRect.top + 1); 3546 3547 if (erase) { 3548 eraseRect.top = line->origin + fTextRect.top; 3549 eraseRect.bottom = (line + 1)->origin + fTextRect.top; 3550 3551 view->FillRect(eraseRect, B_SOLID_LOW); 3552 } 3553 3554 // do we have any text to draw? 3555 if (length <= 0) 3556 return; 3557 3558 bool foundTab = false; 3559 int32 tabChars = 0; 3560 int32 numTabs = 0; 3561 int32 offset = startOffset != -1 ? startOffset : line->offset; 3562 const BFont *font = NULL; 3563 const rgb_color *color = NULL; 3564 int32 numBytes; 3565 // iterate through each style on this line 3566 while ((numBytes = fStyles->Iterate(offset, length, fInline, &font, 3567 &color)) != 0) { 3568 view->SetFont(font); 3569 view->SetHighColor(*color); 3570 3571 tabChars = numBytes; 3572 do { 3573 foundTab = fText->FindChar(B_TAB, offset, &tabChars); 3574 if (foundTab) { 3575 do { 3576 numTabs++; 3577 if (ByteAt(offset + tabChars + numTabs) != B_TAB) 3578 break; 3579 } while ((tabChars + numTabs) < numBytes); 3580 } 3581 3582 if (inputRegion.CountRects() > 0) { 3583 BRegion textRegion; 3584 GetTextRegion(offset, offset + length, &textRegion); 3585 3586 textRegion.IntersectWith(&inputRegion); 3587 view->PushState(); 3588 3589 // Highlight in blue the inputted text 3590 view->SetHighColor(kBlueInputColor); 3591 view->FillRect(textRegion.Frame()); 3592 3593 // Highlight in red the selected part 3594 if (fInline->SelectionLength() > 0) { 3595 BRegion selectedRegion; 3596 GetTextRegion(fInline->Offset() 3597 + fInline->SelectionOffset(), fInline->Offset() 3598 + fInline->SelectionOffset() 3599 + fInline->SelectionLength(), &selectedRegion); 3600 3601 textRegion.IntersectWith(&selectedRegion); 3602 3603 view->SetHighColor(kRedInputColor); 3604 view->FillRect(textRegion.Frame()); 3605 } 3606 3607 view->PopState(); 3608 } 3609 3610 int32 returnedBytes = tabChars; 3611 const char *stringToDraw = fText->GetString(offset, 3612 &returnedBytes); 3613 3614 view->DrawString(stringToDraw, returnedBytes); 3615 if (foundTab) { 3616 float penPos = PenLocation().x - fTextRect.left; 3617 float tabWidth = _ActualTabWidth(penPos); 3618 if (numTabs > 1) 3619 tabWidth += ((numTabs - 1) * fTabWidth); 3620 3621 view->MovePenBy(tabWidth, 0.0); 3622 tabChars += numTabs; 3623 } 3624 3625 offset += tabChars; 3626 length -= tabChars; 3627 numBytes -= tabChars; 3628 tabChars = numBytes; 3629 numTabs = 0; 3630 } while (foundTab && tabChars > 0); 3631 } 3632 } 3633 3634 3635 void 3636 BTextView::_DrawLines(int32 startLine, int32 endLine, int32 startOffset, 3637 bool erase) 3638 { 3639 if (!Window()) 3640 return; 3641 3642 // clip the text 3643 BRect clipRect = Bounds() & fTextRect; 3644 clipRect.InsetBy(-1, -1); 3645 3646 BRegion newClip; 3647 newClip.Set(clipRect); 3648 ConstrainClippingRegion(&newClip); 3649 3650 // set the low color to the view color so that 3651 // drawing to a non-white background will work 3652 SetLowColor(ViewColor()); 3653 3654 BView *view = NULL; 3655 if (fOffscreen == NULL) 3656 view = this; 3657 else { 3658 fOffscreen->Lock(); 3659 view = fOffscreen->ChildAt(0); 3660 view->SetLowColor(ViewColor()); 3661 view->FillRect(view->Bounds(), B_SOLID_LOW); 3662 } 3663 3664 long maxLine = fLines->NumLines() - 1; 3665 if (startLine < 0) 3666 startLine = 0; 3667 if (endLine > maxLine) 3668 endLine = maxLine; 3669 3670 // TODO: See if we can avoid this 3671 if (fAlignment != B_ALIGN_LEFT) 3672 erase = true; 3673 3674 // Actually hide the caret 3675 if (fCaretVisible) 3676 _DrawCaret(fSelStart); 3677 3678 BRect eraseRect = clipRect; 3679 int32 startEraseLine = startLine; 3680 STELine* line = (*fLines)[startLine]; 3681 3682 if (erase && startOffset != -1 && fAlignment == B_ALIGN_LEFT) { 3683 // erase only to the right of startOffset 3684 startEraseLine++; 3685 int32 startErase = startOffset; 3686 3687 BPoint erasePoint = PointAt(startErase); 3688 eraseRect.left = erasePoint.x; 3689 eraseRect.top = erasePoint.y; 3690 eraseRect.bottom = (line + 1)->origin + fTextRect.top; 3691 3692 view->FillRect(eraseRect, B_SOLID_LOW); 3693 3694 eraseRect = clipRect; 3695 } 3696 3697 BRegion inputRegion; 3698 if (fInline != NULL && fInline->IsActive()) 3699 GetTextRegion(fInline->Offset(), fInline->Offset() + fInline->Length(), &inputRegion); 3700 3701 //BPoint leftTop(startLeft, line->origin); 3702 for (int32 lineNum = startLine; lineNum <= endLine; lineNum++) { 3703 const bool eraseThisLine = erase && lineNum >= startEraseLine; 3704 _DrawLine(view, lineNum, startOffset, eraseThisLine, eraseRect, inputRegion); 3705 startOffset = -1; 3706 // Set this to -1 so the next iteration will use the line offset 3707 } 3708 3709 // draw the caret/hilite the selection 3710 if (fActive) { 3711 if (fSelStart != fSelEnd && fSelectable) 3712 Highlight(fSelStart, fSelEnd); 3713 else { 3714 if (fCaretVisible) 3715 _DrawCaret(fSelStart); 3716 } 3717 } 3718 3719 if (fOffscreen != NULL) { 3720 view->Sync(); 3721 /*BPoint penLocation = view->PenLocation(); 3722 BRect drawRect(leftTop.x, leftTop.y, penLocation.x, penLocation.y); 3723 DrawBitmap(fOffscreen, drawRect, drawRect);*/ 3724 fOffscreen->Unlock(); 3725 } 3726 3727 ConstrainClippingRegion(NULL); 3728 } 3729 3730 3731 void 3732 BTextView::_DrawCaret(int32 offset) 3733 { 3734 float lineHeight; 3735 BPoint caretPoint = PointAt(offset, &lineHeight); 3736 caretPoint.x = min_c(caretPoint.x, fTextRect.right); 3737 3738 BRect caretRect; 3739 caretRect.left = caretRect.right = caretPoint.x; 3740 caretRect.top = caretPoint.y; 3741 caretRect.bottom = caretPoint.y + lineHeight - 1; 3742 3743 InvertRect(caretRect); 3744 } 3745 3746 3747 inline void 3748 BTextView::_ShowCaret() 3749 { 3750 if (!fCaretVisible) 3751 _InvertCaret(); 3752 } 3753 3754 3755 inline void 3756 BTextView::_HideCaret() 3757 { 3758 if (fCaretVisible) 3759 _InvertCaret(); 3760 } 3761 3762 3763 /*! \brief Inverts the blinking caret status. 3764 Hides the caret if it is being shown, and if it's hidden, shows it. 3765 */ 3766 void 3767 BTextView::_InvertCaret() 3768 { 3769 _DrawCaret(fSelStart); 3770 fCaretVisible = !fCaretVisible; 3771 fCaretTime = system_time(); 3772 } 3773 3774 3775 /*! \brief Place the dragging caret at the given offset. 3776 \param offset The offset (zero based within the object's text) where to place 3777 the dragging caret. If it's -1, hide the caret. 3778 */ 3779 void 3780 BTextView::_DragCaret(int32 offset) 3781 { 3782 // does the caret need to move? 3783 if (offset == fDragOffset) 3784 return; 3785 3786 // hide the previous drag caret 3787 if (fDragOffset != -1) 3788 _DrawCaret(fDragOffset); 3789 3790 // do we have a new location? 3791 if (offset != -1) { 3792 if (fActive) { 3793 // ignore if offset is within active selection 3794 if (offset >= fSelStart && offset <= fSelEnd) { 3795 fDragOffset = -1; 3796 return; 3797 } 3798 } 3799 3800 _DrawCaret(offset); 3801 } 3802 3803 fDragOffset = offset; 3804 } 3805 3806 3807 void 3808 BTextView::_StopMouseTracking() 3809 { 3810 delete fTrackingMouse; 3811 fTrackingMouse = NULL; 3812 } 3813 3814 3815 bool 3816 BTextView::_PerformMouseUp(BPoint where) 3817 { 3818 if (fTrackingMouse == NULL) 3819 return false; 3820 3821 if (fTrackingMouse->selectionRect.IsValid() 3822 && fTrackingMouse->selectionRect.Contains(where)) 3823 Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset); 3824 3825 _StopMouseTracking(); 3826 3827 return true; 3828 } 3829 3830 3831 bool 3832 BTextView::_PerformMouseMoved(BPoint where, uint32 code) 3833 { 3834 fWhere = where; 3835 3836 if (fTrackingMouse == NULL) 3837 return false; 3838 3839 if (fTrackingMouse->selectionRect.IsValid() 3840 && fTrackingMouse->selectionRect.Contains(where)) { 3841 _StopMouseTracking(); 3842 _InitiateDrag(); 3843 return true; 3844 } 3845 3846 int32 oldOffset = fTrackingMouse->anchor; 3847 int32 currentOffset = OffsetAt(where); 3848 3849 switch (fClickCount) { 3850 case 0: 3851 // triple click, select line by line 3852 fTrackingMouse->selStart = (*fLines)[LineAt(fTrackingMouse->selStart)]->offset; 3853 fTrackingMouse->selEnd = (*fLines)[LineAt(fTrackingMouse->selEnd) + 1]->offset; 3854 break; 3855 3856 case 2: 3857 // double click, select word by word 3858 FindWord(currentOffset, &fTrackingMouse->selStart, &fTrackingMouse->selEnd); 3859 break; 3860 3861 default: 3862 // new click, select char by char 3863 if (oldOffset < currentOffset) { 3864 fTrackingMouse->selStart = oldOffset; 3865 fTrackingMouse->selEnd = currentOffset; 3866 } else { 3867 fTrackingMouse->selStart = currentOffset; 3868 fTrackingMouse->selEnd = oldOffset; 3869 } 3870 break; 3871 } 3872 3873 Select(fTrackingMouse->selStart, fTrackingMouse->selEnd); 3874 _TrackMouse(where, NULL); 3875 3876 return true; 3877 } 3878 3879 3880 /*! \brief Tracks the mouse position, doing special actions like changing the 3881 view cursor. 3882 \param where The point where the mouse has moved. 3883 \param message The dragging message, if there is any. 3884 \param force Passed as second parameter of SetViewCursor() 3885 */ 3886 void 3887 BTextView::_TrackMouse(BPoint where, const BMessage *message, bool force) 3888 { 3889 BRegion textRegion; 3890 GetTextRegion(fSelStart, fSelEnd, &textRegion); 3891 3892 if (message && AcceptsDrop(message)) 3893 _TrackDrag(where); 3894 else if ((fSelectable || fEditable) && !textRegion.Contains(where)) 3895 SetViewCursor(B_CURSOR_I_BEAM, force); 3896 else 3897 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, force); 3898 } 3899 3900 3901 /*! \brief Tracks the mouse position when the user is dragging some data. 3902 \param where The point where the mouse has moved. 3903 */ 3904 void 3905 BTextView::_TrackDrag(BPoint where) 3906 { 3907 CALLED(); 3908 if (Bounds().Contains(where)) 3909 _DragCaret(OffsetAt(where)); 3910 } 3911 3912 3913 /*! \brief Function called to initiate a drag operation. 3914 */ 3915 void 3916 BTextView::_InitiateDrag() 3917 { 3918 BMessage dragMessage(B_MIME_DATA); 3919 BBitmap *dragBitmap = NULL; 3920 BPoint bitmapPoint; 3921 BHandler *dragHandler = NULL; 3922 3923 GetDragParameters(&dragMessage, &dragBitmap, &bitmapPoint, &dragHandler); 3924 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 3925 3926 if (dragBitmap != NULL) 3927 DragMessage(&dragMessage, dragBitmap, bitmapPoint, dragHandler); 3928 else { 3929 BRegion region; 3930 GetTextRegion(fSelStart, fSelEnd, ®ion); 3931 BRect bounds = Bounds(); 3932 BRect dragRect = region.Frame(); 3933 if (!bounds.Contains(dragRect)) 3934 dragRect = bounds & dragRect; 3935 3936 DragMessage(&dragMessage, dragRect, dragHandler); 3937 } 3938 3939 BMessenger messenger(this); 3940 BMessage message(_DISPOSE_DRAG_); 3941 fDragRunner = new (nothrow) BMessageRunner(messenger, &message, 100000); 3942 } 3943 3944 3945 /*! \brief Called when some data is dropped on the view. 3946 \param inMessage The message which has been dropped. 3947 \param where The location where the message has been dropped. 3948 \param offset ? 3949 \return \c true if the message was handled, \c false if not. 3950 */ 3951 bool 3952 BTextView::_MessageDropped(BMessage *inMessage, BPoint where, BPoint offset) 3953 { 3954 ASSERT(inMessage); 3955 3956 void *from = NULL; 3957 bool internalDrop = false; 3958 if (inMessage->FindPointer("be:originator", &from) == B_OK 3959 && from == this && fSelEnd != fSelStart) 3960 internalDrop = true; 3961 3962 _DragCaret(-1); 3963 3964 delete fDragRunner; 3965 fDragRunner = NULL; 3966 3967 _TrackMouse(where, NULL); 3968 3969 // are we sure we like this message? 3970 if (!AcceptsDrop(inMessage)) 3971 return false; 3972 3973 int32 dropOffset = OffsetAt(where); 3974 if (dropOffset > TextLength()) 3975 dropOffset = TextLength(); 3976 3977 // if this view initiated the drag, move instead of copy 3978 if (internalDrop) { 3979 // dropping onto itself? 3980 if (dropOffset >= fSelStart && dropOffset <= fSelEnd) 3981 return true; 3982 } 3983 3984 ssize_t dataLen = 0; 3985 const char *text = NULL; 3986 if (inMessage->FindData("text/plain", B_MIME_TYPE, (const void **)&text, &dataLen) == B_OK) { 3987 text_run_array *runArray = NULL; 3988 ssize_t runLen = 0; 3989 if (fStylable) 3990 inMessage->FindData("application/x-vnd.Be-text_run_array", B_MIME_TYPE, 3991 (const void **)&runArray, &runLen); 3992 3993 if (fUndo) { 3994 delete fUndo; 3995 fUndo = new _BDropUndoBuffer_(this, text, dataLen, runArray, runLen, dropOffset, internalDrop); 3996 } 3997 3998 if (internalDrop) { 3999 if (dropOffset > fSelEnd) 4000 dropOffset -= dataLen; 4001 Delete(); 4002 } 4003 4004 Insert(dropOffset, text, dataLen, runArray); 4005 } 4006 4007 return true; 4008 } 4009 4010 4011 void 4012 BTextView::_PerformAutoScrolling() 4013 { 4014 // Scroll the view a bit if mouse is outside the view bounds 4015 BRect bounds = Bounds(); 4016 BPoint scrollBy; 4017 4018 BPoint constraint = fWhere; 4019 constraint.ConstrainTo(bounds); 4020 // Scroll char by char horizontally 4021 // TODO: Check how BeOS R5 behaves 4022 float value = _StyledWidthUTF8Safe(OffsetAt(constraint), 1); 4023 if (fWhere.x > bounds.right) { 4024 if (bounds.right + value <= fTextRect.Width()) 4025 scrollBy.x = value; 4026 } else if (fWhere.x < bounds.left) { 4027 if (bounds.left - value >= 0) 4028 scrollBy.x = -value; 4029 } 4030 4031 float lineHeight = 0; 4032 float vertDiff = 0; 4033 if (fWhere.y > bounds.bottom) { 4034 lineHeight = LineHeight(LineAt(bounds.LeftBottom())); 4035 vertDiff = fWhere.y - bounds.bottom; 4036 } else if (fWhere.y < bounds.top) { 4037 lineHeight = LineHeight(LineAt(bounds.LeftTop())); 4038 vertDiff = fWhere.y - bounds.top; // negative value 4039 } 4040 4041 // Always scroll vertically line by line or by multiples of that 4042 // based on the distance of the cursor from the border of the view 4043 // TODO: Refine this, I can't even remember how beos works here 4044 scrollBy.y = lineHeight > 0 ? lineHeight * (int32)(floorf(vertDiff) / lineHeight) : 0; 4045 4046 if (bounds.bottom + scrollBy.y > fTextRect.Height()) 4047 scrollBy.y = fTextRect.Height() - bounds.bottom; 4048 else if (bounds.top + scrollBy.y < 0) 4049 scrollBy.y = -bounds.top; 4050 4051 if (scrollBy != B_ORIGIN) 4052 ScrollBy(scrollBy.x, scrollBy.y); 4053 } 4054 4055 4056 /*! \brief Updates the scrollbars associated with the object (if any). 4057 */ 4058 void 4059 BTextView::_UpdateScrollbars() 4060 { 4061 BRect bounds(Bounds()); 4062 BScrollBar *horizontalScrollBar = ScrollBar(B_HORIZONTAL); 4063 BScrollBar *verticalScrollBar = ScrollBar(B_VERTICAL); 4064 4065 // do we have a horizontal scroll bar? 4066 if (horizontalScrollBar != NULL) { 4067 long viewWidth = bounds.IntegerWidth(); 4068 long dataWidth = fTextRect.IntegerWidth(); 4069 dataWidth += (long)ceilf(fTextRect.left) + 1; 4070 4071 long maxRange = dataWidth - viewWidth; 4072 maxRange = max_c(maxRange, 0); 4073 4074 horizontalScrollBar->SetRange(0, (float)maxRange); 4075 horizontalScrollBar->SetProportion((float)viewWidth / (float)dataWidth); 4076 horizontalScrollBar->SetSteps(10, dataWidth / 10); 4077 } 4078 4079 // how about a vertical scroll bar? 4080 if (verticalScrollBar != NULL) { 4081 long viewHeight = bounds.IntegerHeight(); 4082 long dataHeight = fTextRect.IntegerHeight(); 4083 dataHeight += (long)ceilf(fTextRect.top) + 1; 4084 4085 long maxRange = dataHeight - viewHeight; 4086 maxRange = max_c(maxRange, 0); 4087 4088 verticalScrollBar->SetRange(0, maxRange); 4089 verticalScrollBar->SetProportion((float)viewHeight / (float)dataHeight); 4090 verticalScrollBar->SetSteps(12, viewHeight); 4091 } 4092 } 4093 4094 4095 /*! \brief Autoresizes the view to fit the contained text. 4096 */ 4097 void 4098 BTextView::_AutoResize(bool redraw) 4099 { 4100 if (fResizable) { 4101 float oldWidth = Bounds().Width() + 1; 4102 float newWidth = 3; 4103 for (int32 i = 0; i < CountLines(); i++) 4104 newWidth += LineWidth(i); 4105 4106 BRect newRect(0, 0, ceilf(newWidth), ceilf(LineHeight(0)) + 2); 4107 4108 if (fContainerView != NULL) { 4109 fContainerView->ResizeTo(newRect.Width() + 1, newRect.Height()); 4110 if (fAlignment == B_ALIGN_CENTER) 4111 fContainerView->MoveBy(ceilf((oldWidth - (newRect.Width() + 1)) / 2), 0); 4112 else if (fAlignment == B_ALIGN_RIGHT) 4113 fContainerView->MoveBy(oldWidth - (newRect.Width() + 1), 0); 4114 fContainerView->Invalidate(); 4115 } 4116 4117 fTextRect = newRect.InsetBySelf(0, 1); 4118 4119 if (redraw) 4120 _DrawLines(0, 0); 4121 4122 // Erase the old text (TODO: Might not work for alignments different than B_ALIGN_LEFT) 4123 SetLowColor(ViewColor()); 4124 FillRect(BRect(fTextRect.right, fTextRect.top, Bounds().right, fTextRect.bottom), B_SOLID_LOW); 4125 } 4126 } 4127 4128 4129 /*! \brief Creates a new offscreen BBitmap with an associated BView. 4130 param padding Padding (?) 4131 4132 Creates an offscreen BBitmap which will be used to draw. 4133 */ 4134 void 4135 BTextView::_NewOffscreen(float padding) 4136 { 4137 if (fOffscreen != NULL) 4138 _DeleteOffscreen(); 4139 4140 #if USE_DOUBLEBUFFERING 4141 BRect bitmapRect(0, 0, fTextRect.Width() + padding, fTextRect.Height()); 4142 fOffscreen = new BBitmap(bitmapRect, fColorSpace, true, false); 4143 if (fOffscreen != NULL && fOffscreen->Lock()) { 4144 BView *bufferView = new BView(bitmapRect, "drawing view", 0, 0); 4145 fOffscreen->AddChild(bufferView); 4146 fOffscreen->Unlock(); 4147 } 4148 #endif 4149 } 4150 4151 4152 /*! \brief Deletes the textview's offscreen bitmap, if any. 4153 */ 4154 void 4155 BTextView::_DeleteOffscreen() 4156 { 4157 if (fOffscreen != NULL && fOffscreen->Lock()) { 4158 delete fOffscreen; 4159 fOffscreen = NULL; 4160 } 4161 } 4162 4163 4164 /*! \brief Creates a new offscreen bitmap, highlight the selection, and set the 4165 cursor to B_CURSOR_I_BEAM. 4166 */ 4167 void 4168 BTextView::_Activate() 4169 { 4170 fActive = true; 4171 4172 // Create a new offscreen BBitmap 4173 _NewOffscreen(); 4174 4175 if (fSelStart != fSelEnd) { 4176 if (fSelectable) 4177 Highlight(fSelStart, fSelEnd); 4178 } else { 4179 if (fEditable) 4180 _ShowCaret(); 4181 } 4182 4183 BPoint where; 4184 ulong buttons; 4185 GetMouse(&where, &buttons, false); 4186 if (Bounds().Contains(where)) 4187 _TrackMouse(where, NULL); 4188 } 4189 4190 4191 /*! \brief Unhilights the selection, set the cursor to B_CURSOR_SYSTEM_DEFAULT. 4192 */ 4193 void 4194 BTextView::_Deactivate() 4195 { 4196 fActive = false; 4197 4198 _CancelInputMethod(); 4199 _DeleteOffscreen(); 4200 4201 if (fSelStart != fSelEnd) { 4202 if (fSelectable) 4203 Highlight(fSelStart, fSelEnd); 4204 } else 4205 _HideCaret(); 4206 } 4207 4208 4209 /*! \brief Changes the passed font to be displayable by the object. 4210 \param font A pointer to the font to normalize. 4211 4212 Set font rotation to 0, removes any font flag, set font spacing 4213 to \c B_BITMAP_SPACING and font encoding to \c B_UNICODE_UTF8 4214 */ 4215 void 4216 BTextView::_NormalizeFont(BFont *font) 4217 { 4218 if (font) { 4219 font->SetRotation(0.0f); 4220 font->SetFlags(0); 4221 font->SetSpacing(B_BITMAP_SPACING); 4222 font->SetEncoding(B_UNICODE_UTF8); 4223 } 4224 } 4225 4226 4227 void 4228 BTextView::_SetRunArray(int32 startOffset, int32 endOffset, 4229 const text_run_array *inRuns) 4230 { 4231 if (startOffset > endOffset) 4232 return; 4233 4234 const int32 textLength = fText->Length(); 4235 4236 // pin offsets at reasonable values 4237 if (startOffset < 0) 4238 startOffset = 0; 4239 else if (startOffset > textLength) 4240 startOffset = textLength; 4241 4242 if (endOffset < 0) 4243 endOffset = 0; 4244 else if (endOffset > textLength) 4245 endOffset = textLength; 4246 4247 const int32 numStyles = inRuns->count; 4248 if (numStyles > 0) { 4249 const text_run *theRun = &inRuns->runs[0]; 4250 for (int32 index = 0; index < numStyles; index++) { 4251 int32 fromOffset = theRun->offset + startOffset; 4252 int32 toOffset = endOffset; 4253 if (index + 1 < numStyles) { 4254 toOffset = (theRun + 1)->offset + startOffset; 4255 toOffset = (toOffset > endOffset) ? endOffset : toOffset; 4256 } 4257 4258 BFont font = theRun->font; 4259 _NormalizeFont(&font); 4260 fStyles->SetStyleRange(fromOffset, toOffset, textLength, 4261 B_FONT_ALL, &theRun->font, &theRun->color); 4262 4263 theRun++; 4264 } 4265 fStyles->InvalidateNullStyle(); 4266 } 4267 } 4268 4269 4270 /*! \brief Returns a value which tells if the given character is a separator 4271 character or not. 4272 \param offset The offset where the wanted character can be found. 4273 \return A value which represents the character's classification. 4274 */ 4275 uint32 4276 BTextView::_CharClassification(int32 offset) const 4277 { 4278 // TODO:Should check against a list of characters containing also 4279 // japanese word breakers. 4280 // And what about other languages ? Isn't there a better way to check 4281 // for separator characters ? 4282 // Andrew suggested to have a look at UnicodeBlockObject.h 4283 switch (fText->RealCharAt(offset)) { 4284 case B_SPACE: 4285 case '_': 4286 case '.': 4287 case '\0': 4288 case B_TAB: 4289 case B_ENTER: 4290 case '&': 4291 case '*': 4292 case '+': 4293 case '-': 4294 case '/': 4295 case '<': 4296 case '=': 4297 case '>': 4298 case '\\': 4299 case '^': 4300 case '|': 4301 return B_SEPARATOR_CHARACTER; 4302 default: 4303 return B_OTHER_CHARACTER; 4304 } 4305 } 4306 4307 4308 /*! \brief Returns the offset of the next UTF8 character within the BTextView's text. 4309 \param offset The offset where to start looking. 4310 \return The offset of the next UTF8 character. 4311 */ 4312 int32 4313 BTextView::_NextInitialByte(int32 offset) const 4314 { 4315 int32 textLength = TextLength(); 4316 if (offset >= textLength) 4317 return textLength; 4318 4319 for (++offset; (ByteAt(offset) & 0xC0) == 0x80; ++offset) 4320 ; 4321 4322 return offset; 4323 } 4324 4325 4326 /*! \brief Returns the offset of the previous UTF8 character within the BTextView's text. 4327 \param offset The offset where to start looking. 4328 \return The offset of the previous UTF8 character. 4329 */ 4330 int32 4331 BTextView::_PreviousInitialByte(int32 offset) const 4332 { 4333 if (offset <= 0) 4334 return 0; 4335 4336 int32 count = 6; 4337 4338 for (--offset; offset > 0 && count; --offset, --count) { 4339 if ((ByteAt(offset) & 0xC0) != 0x80) 4340 break; 4341 } 4342 4343 return count ? offset : 0; 4344 } 4345 4346 4347 bool 4348 BTextView::_GetProperty(BMessage *specifier, int32 form, const char *property, BMessage *reply) 4349 { 4350 CALLED(); 4351 if (strcmp(property, "selection") == 0) { 4352 reply->what = B_REPLY; 4353 reply->AddInt32("result", fSelStart); 4354 reply->AddInt32("result", fSelEnd); 4355 reply->AddInt32("error", B_OK); 4356 4357 return true; 4358 } else if (strcmp(property, "Text") == 0) { 4359 if (IsTypingHidden()) { 4360 // Do not allow stealing passwords via scripting 4361 beep(); 4362 return false; 4363 } 4364 4365 int32 index, range; 4366 specifier->FindInt32("index", &index); 4367 specifier->FindInt32("range", &range); 4368 4369 char *buffer = new char[range + 1]; 4370 GetText(index, range, buffer); 4371 4372 reply->what = B_REPLY; 4373 reply->AddString("result", buffer); 4374 delete buffer; 4375 reply->AddInt32("error", B_OK); 4376 4377 return true; 4378 } else if (strcmp(property, "text_run_array") == 0) 4379 return false; 4380 4381 return false; 4382 } 4383 4384 4385 bool 4386 BTextView::_SetProperty(BMessage *specifier, int32 form, const char *property, 4387 BMessage *reply) 4388 { 4389 CALLED(); 4390 if (strcmp(property, "selection") == 0) { 4391 int32 index, range; 4392 4393 specifier->FindInt32("index", &index); 4394 specifier->FindInt32("range", &range); 4395 4396 Select(index, index + range); 4397 4398 reply->what = B_REPLY; 4399 reply->AddInt32("error", B_OK); 4400 4401 return true; 4402 } else if (strcmp(property, "Text") == 0) { 4403 int32 index, range; 4404 specifier->FindInt32("index", &index); 4405 specifier->FindInt32("range", &range); 4406 4407 const char *buffer = NULL; 4408 if (specifier->FindString("data", &buffer) == B_OK) 4409 InsertText(buffer, range, index, NULL); 4410 else 4411 DeleteText(index, range); 4412 4413 reply->what = B_REPLY; 4414 reply->AddInt32("error", B_OK); 4415 4416 return true; 4417 } else if (strcmp(property, "text_run_array") == 0) 4418 return false; 4419 4420 return false; 4421 } 4422 4423 4424 bool 4425 BTextView::_CountProperties(BMessage *specifier, int32 form, 4426 const char *property, BMessage *reply) 4427 { 4428 CALLED(); 4429 if (strcmp(property, "Text") == 0) { 4430 reply->what = B_REPLY; 4431 reply->AddInt32("result", TextLength()); 4432 reply->AddInt32("error", B_OK); 4433 return true; 4434 } 4435 4436 return false; 4437 } 4438 4439 4440 /*! \brief Called when the object receives a B_INPUT_METHOD_CHANGED message. 4441 \param message A B_INPUT_METHOD_CHANGED message. 4442 */ 4443 void 4444 BTextView::_HandleInputMethodChanged(BMessage *message) 4445 { 4446 // TODO: block input if not editable (Andrew) 4447 ASSERT(fInline != NULL); 4448 4449 const char *string = NULL; 4450 if (message->FindString("be:string", &string) < B_OK || string == NULL) 4451 return; 4452 4453 _HideCaret(); 4454 4455 be_app->ObscureCursor(); 4456 4457 // If we find the "be:confirmed" boolean (and the boolean is true), 4458 // it means it's over for now, so the current _BInlineInput_ object 4459 // should become inactive. We will probably receive a B_INPUT_METHOD_STOPPED 4460 // message after this one. 4461 bool confirmed; 4462 if (message->FindBool("be:confirmed", &confirmed) != B_OK) 4463 confirmed = false; 4464 4465 // Delete the previously inserted text (if any) 4466 if (fInline->IsActive()) { 4467 const int32 oldOffset = fInline->Offset(); 4468 DeleteText(oldOffset, oldOffset + fInline->Length()); 4469 if (confirmed) 4470 fInline->SetActive(false); 4471 fClickOffset = fSelStart = fSelEnd = oldOffset; 4472 } 4473 4474 const int32 stringLen = strlen(string); 4475 4476 fInline->SetOffset(fSelStart); 4477 fInline->SetLength(stringLen); 4478 fInline->ResetClauses(); 4479 4480 if (!confirmed && !fInline->IsActive()) 4481 fInline->SetActive(true); 4482 4483 // Get the clauses, and pass them to the _BInlineInput_ object 4484 // TODO: Find out if what we did it's ok, currently we don't consider clauses 4485 // at all, while the bebook says we should; though the visual effect we obtained 4486 // seems correct. Weird. 4487 int32 clauseCount = 0; 4488 int32 clauseStart; 4489 int32 clauseEnd; 4490 while (message->FindInt32("be:clause_start", clauseCount, &clauseStart) == B_OK && 4491 message->FindInt32("be:clause_end", clauseCount, &clauseEnd) == B_OK) { 4492 if (!fInline->AddClause(clauseStart, clauseEnd)) 4493 break; 4494 clauseCount++; 4495 } 4496 4497 int32 selectionStart = 0; 4498 int32 selectionEnd = 0; 4499 message->FindInt32("be:selection", 0, &selectionStart); 4500 message->FindInt32("be:selection", 1, &selectionEnd); 4501 4502 fInline->SetSelectionOffset(selectionStart); 4503 fInline->SetSelectionLength(selectionEnd - selectionStart); 4504 4505 const int32 inlineOffset = fInline->Offset(); 4506 InsertText(string, stringLen, fSelStart, NULL); 4507 fSelStart += stringLen; 4508 fClickOffset = fSelEnd = fSelStart; 4509 4510 _Refresh(inlineOffset, fSelEnd, true, true); 4511 4512 _ShowCaret(); 4513 } 4514 4515 4516 /*! \brief Called when the object receives a B_INPUT_METHOD_LOCATION_REQUEST 4517 message. 4518 */ 4519 void 4520 BTextView::_HandleInputMethodLocationRequest() 4521 { 4522 ASSERT(fInline != NULL); 4523 4524 int32 offset = fInline->Offset(); 4525 const int32 limit = offset + fInline->Length(); 4526 4527 BMessage message(B_INPUT_METHOD_EVENT); 4528 message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST); 4529 4530 // Add the location of the UTF8 characters 4531 while (offset < limit) { 4532 float height; 4533 BPoint where = PointAt(offset, &height); 4534 ConvertToScreen(&where); 4535 4536 message.AddPoint("be:location_reply", where); 4537 message.AddFloat("be:height_reply", height); 4538 4539 offset = _NextInitialByte(offset); 4540 } 4541 4542 fInline->Method()->SendMessage(&message); 4543 } 4544 4545 4546 /*! \brief Tells the input server method addon to stop the current transaction. 4547 */ 4548 void 4549 BTextView::_CancelInputMethod() 4550 { 4551 if (!fInline) 4552 return; 4553 4554 _BInlineInput_ *inlineInput = fInline; 4555 fInline = NULL; 4556 4557 if (inlineInput->IsActive() && Window()) 4558 _Refresh(inlineInput->Offset(), fText->Length() - inlineInput->Offset(), true, false); 4559 4560 BMessage message(B_INPUT_METHOD_EVENT); 4561 message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED); 4562 inlineInput->Method()->SendMessage(&message); 4563 4564 delete inlineInput; 4565 } 4566 4567 4568 /*! \brief Locks the static _BWidthBuffer_ object to be able to access it safely. 4569 */ 4570 void 4571 BTextView::LockWidthBuffer() 4572 { 4573 if (atomic_add(&sWidthAtom, 1) > 0) { 4574 while (acquire_sem(sWidthSem) == B_INTERRUPTED) 4575 ; 4576 } 4577 } 4578 4579 4580 /*! \brief Unlocks the static _BWidthBuffer_ object. 4581 */ 4582 void 4583 BTextView::UnlockWidthBuffer() 4584 { 4585 if (atomic_add(&sWidthAtom, -1) > 1) 4586 release_sem(sWidthSem); 4587 } 4588 4589 4590 // _BTextTrackState_ 4591 _BTextTrackState_::_BTextTrackState_(BMessenger messenger) 4592 : 4593 clickOffset(0), 4594 shiftDown(false), 4595 anchor(0), 4596 selStart(0), 4597 selEnd(0), 4598 fRunner(NULL) 4599 { 4600 BMessage message(_PING_); 4601 fRunner = new (nothrow) BMessageRunner(messenger, &message, 300000); 4602 } 4603 4604 4605 _BTextTrackState_::~_BTextTrackState_() 4606 { 4607 delete fRunner; 4608 } 4609 4610 4611 void 4612 _BTextTrackState_::SimulateMouseMovement(BTextView *textView) 4613 { 4614 BPoint where; 4615 ulong buttons; 4616 // When the mouse cursor is still and outside the textview, 4617 // no B_MOUSE_MOVED message are sent, obviously. But scrolling 4618 // has to work neverthless, so we "fake" a MouseMoved() call here. 4619 textView->GetMouse(&where, &buttons); 4620 textView->_PerformMouseMoved(where, B_INSIDE_VIEW); 4621 } 4622 4623 4624