1 /* 2 * Copyright 2001-2007, 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 (burton666@libero.it) 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 clip->AddData("text/plain", B_MIME_TYPE, Text() + fSelStart, 1237 fSelEnd - fSelStart); 1238 1239 int32 size; 1240 if (fStylable) { 1241 text_run_array *runArray = RunArray(fSelStart, fSelEnd, &size); 1242 clip->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE, 1243 runArray, size); 1244 FreeRunArray(runArray); 1245 } 1246 clipboard->Commit(); 1247 } 1248 clipboard->Unlock(); 1249 } 1250 } 1251 1252 1253 /*! \brief Paste the text contained in the clipboard to the BTextView. 1254 \param clipboard A pointer to the clipboard. 1255 */ 1256 void 1257 BTextView::Paste(BClipboard *clipboard) 1258 { 1259 CALLED(); 1260 _CancelInputMethod(); 1261 1262 if (clipboard->Lock()) { 1263 BMessage *clip = clipboard->Data(); 1264 if (clip != NULL) { 1265 const char *text = NULL; 1266 ssize_t len = 0; 1267 1268 if (clip->FindData("text/plain", B_MIME_TYPE, 1269 (const void **)&text, &len) == B_OK) { 1270 text_run_array *runArray = NULL; 1271 ssize_t runLen = 0; 1272 1273 if (fStylable) 1274 clip->FindData("application/x-vnd.Be-text_run_array", B_MIME_TYPE, 1275 (const void **)&runArray, &runLen); 1276 1277 if (fUndo) { 1278 delete fUndo; 1279 fUndo = new _BPasteUndoBuffer_(this, text, len, runArray, runLen); 1280 } 1281 1282 if (fSelStart != fSelEnd) 1283 Delete(); 1284 1285 Insert(text, len, runArray); 1286 } 1287 } 1288 1289 clipboard->Unlock(); 1290 } 1291 } 1292 1293 1294 /*! \brief Deletes the currently selected text. 1295 */ 1296 void 1297 BTextView::Clear() 1298 { 1299 // We always check for fUndo != NULL (not only here), 1300 // because when fUndo is NULL, undo is deactivated. 1301 if (fUndo) { 1302 delete fUndo; 1303 fUndo = new _BClearUndoBuffer_(this); 1304 } 1305 1306 Delete(); 1307 } 1308 1309 1310 bool 1311 BTextView::AcceptsPaste(BClipboard *clipboard) 1312 { 1313 bool result = false; 1314 1315 if (fEditable && clipboard && clipboard->Lock()) { 1316 BMessage *data = clipboard->Data(); 1317 result = data && data->HasData("text/plain", B_MIME_TYPE); 1318 clipboard->Unlock(); 1319 } 1320 1321 return result; 1322 } 1323 1324 1325 bool 1326 BTextView::AcceptsDrop(const BMessage *inMessage) 1327 { 1328 if (fEditable && inMessage && inMessage->HasData("text/plain", B_MIME_TYPE)) 1329 return true; 1330 1331 return false; 1332 } 1333 1334 1335 /*! \brief Selects the text within the given offsets. 1336 \param startOffset The offset of the text to select. 1337 \param endOffset The offset where the text ends. 1338 */ 1339 void 1340 BTextView::Select(int32 startOffset, int32 endOffset) 1341 { 1342 CALLED(); 1343 if (!fSelectable) 1344 return; 1345 1346 _CancelInputMethod(); 1347 1348 // a negative selection? 1349 if (startOffset > endOffset) 1350 return; 1351 1352 // pin offsets at reasonable values 1353 if (startOffset < 0) 1354 startOffset = 0; 1355 if (endOffset < 0) 1356 endOffset = 0; 1357 else if (endOffset > fText->Length()) 1358 endOffset = fText->Length(); 1359 1360 // is the new selection any different from the current selection? 1361 if (startOffset == fSelStart && endOffset == fSelEnd) 1362 return; 1363 1364 fStyles->InvalidateNullStyle(); 1365 1366 _HideCaret(); 1367 1368 if (startOffset == endOffset) { 1369 if (fSelStart != fSelEnd) { 1370 // unhilite the selection 1371 if (fActive) 1372 Highlight(fSelStart, fSelEnd); 1373 } 1374 fSelStart = fSelEnd = fClickOffset = startOffset; 1375 } else { 1376 if (fActive) { 1377 // draw only those ranges that are different 1378 long start, end; 1379 if (startOffset != fSelStart) { 1380 // start of selection has changed 1381 if (startOffset > fSelStart) { 1382 start = fSelStart; 1383 end = startOffset; 1384 } else { 1385 start = startOffset; 1386 end = fSelStart; 1387 } 1388 Highlight(start, end); 1389 } 1390 1391 if (endOffset != fSelEnd) { 1392 // end of selection has changed 1393 if (endOffset > fSelEnd) { 1394 start = fSelEnd; 1395 end = endOffset; 1396 } else { 1397 start = endOffset; 1398 end = fSelEnd; 1399 } 1400 Highlight(start, end); 1401 } 1402 } 1403 fSelStart = startOffset; 1404 fSelEnd = fClickOffset = endOffset; 1405 } 1406 } 1407 1408 1409 /*! \brief Selects all the text within the BTextView. 1410 */ 1411 void 1412 BTextView::SelectAll() 1413 { 1414 Select(0, fText->Length()); 1415 } 1416 1417 1418 /*! \brief Gets the current selection. 1419 \param outStart A pointer to an int32 which will contain the selection start's offset. 1420 \param outEnd A pointer to an int32 which will contain the selection end's offset. 1421 */ 1422 void 1423 BTextView::GetSelection(int32 *outStart, int32 *outEnd) const 1424 { 1425 int32 start = 0, end = 0; 1426 1427 if (fSelectable) { 1428 start = fSelStart; 1429 end = fSelEnd; 1430 } 1431 1432 if (outStart) 1433 *outStart = start; 1434 if (outEnd) 1435 *outEnd = end; 1436 } 1437 1438 1439 void 1440 BTextView::SetFontAndColor(const BFont *inFont, uint32 inMode, const rgb_color *inColor) 1441 { 1442 SetFontAndColor(fSelStart, fSelEnd, inFont, inMode, inColor); 1443 } 1444 1445 1446 void 1447 BTextView::SetFontAndColor(int32 startOffset, int32 endOffset, const BFont* font, 1448 uint32 fontMode, const rgb_color* color) 1449 { 1450 CALLED(); 1451 1452 if (startOffset > endOffset) 1453 return; 1454 1455 BFont newFont; 1456 if (font != NULL) { 1457 newFont = font; 1458 _NormalizeFont(&newFont); 1459 } 1460 1461 const int32 textLength = fText->Length(); 1462 1463 if (!fStylable) { 1464 // When the text view is not stylable, we always set the whole text's 1465 // style and ignore the offsets 1466 startOffset = 0; 1467 endOffset = textLength; 1468 } 1469 1470 // pin offsets at reasonable values 1471 if (startOffset < 0) 1472 startOffset = 0; 1473 else if (startOffset > textLength) 1474 startOffset = textLength; 1475 1476 if (endOffset < 0) 1477 endOffset = 0; 1478 else if (endOffset > textLength) 1479 endOffset = textLength; 1480 1481 // add the style to the style buffer 1482 fStyles->SetStyleRange(startOffset, endOffset, fText->Length(), 1483 fontMode, &newFont, color); 1484 1485 if (fontMode & B_FONT_FAMILY_AND_STYLE || fontMode & B_FONT_SIZE) { 1486 // recalc the line breaks and redraw with new style 1487 _Refresh(startOffset, endOffset, startOffset != endOffset, false); 1488 } else { 1489 // the line breaks wont change, simply redraw 1490 _DrawLines(LineAt(startOffset), LineAt(endOffset), startOffset, true); 1491 } 1492 } 1493 1494 1495 void 1496 BTextView::GetFontAndColor(int32 inOffset, BFont *outFont, rgb_color *outColor) const 1497 { 1498 fStyles->GetStyle(inOffset, outFont, outColor); 1499 } 1500 1501 1502 void 1503 BTextView::GetFontAndColor(BFont *outFont, uint32 *outMode, rgb_color *outColor, bool *outEqColor) const 1504 { 1505 fStyles->ContinuousGetStyle(outFont, outMode, outColor, outEqColor, fSelStart, fSelEnd); 1506 } 1507 1508 1509 void 1510 BTextView::SetRunArray(int32 startOffset, int32 endOffset, const text_run_array *inRuns) 1511 { 1512 CALLED(); 1513 1514 _CancelInputMethod(); 1515 1516 _SetRunArray(startOffset, endOffset, inRuns); 1517 1518 _Refresh(startOffset, endOffset, true, false); 1519 } 1520 1521 1522 /*! \brief Returns a RunArray for the text within the given offsets. 1523 \param startOffset The offset where to start. 1524 \param endOffset The offset where the wanted text ends. 1525 \param outSize A pointer to an int32 which will contain the size 1526 of the run array. 1527 \return A text_run_array for the text in the given offsets. 1528 1529 The returned text_run_array belongs to the caller, so you better 1530 free it as soon as you don't need it. 1531 */ 1532 text_run_array * 1533 BTextView::RunArray(int32 startOffset, int32 endOffset, int32 *outSize) const 1534 { 1535 STEStyleRange* styleRange = fStyles->GetStyleRange(startOffset, endOffset - 1); 1536 if (styleRange == NULL) 1537 return NULL; 1538 1539 text_run_array *runArray = AllocRunArray(styleRange->count, outSize); 1540 if (runArray != NULL) { 1541 for (int32 i = 0; i < runArray->count; i++) { 1542 runArray->runs[i].offset = styleRange->runs[i].offset; 1543 runArray->runs[i].font = styleRange->runs[i].style.font; 1544 runArray->runs[i].color = styleRange->runs[i].style.color; 1545 } 1546 } 1547 1548 free(styleRange); 1549 1550 return runArray; 1551 } 1552 1553 1554 /*! \brief Returns the line number for the character at the given offset. 1555 \param offset The offset of the wanted character. 1556 \return A line number. 1557 */ 1558 int32 1559 BTextView::LineAt(int32 offset) const 1560 { 1561 return fLines->OffsetToLine(offset); 1562 } 1563 1564 1565 /*! \brief Returns the line number for the given point. 1566 \param point A point. 1567 \return A line number. 1568 */ 1569 int32 1570 BTextView::LineAt(BPoint point) const 1571 { 1572 return fLines->PixelToLine(point.y - fTextRect.top); 1573 } 1574 1575 1576 /*! \brief Returns the location of the character at the given offset. 1577 \param inOffset The offset of the character. 1578 \param outHeight Here the function will put the height of the character at the 1579 given offset. 1580 \return A BPoint which is the location of the character. 1581 */ 1582 BPoint 1583 BTextView::PointAt(int32 inOffset, float *outHeight) const 1584 { 1585 // TODO: Cleanup. 1586 const int32 textLength = fText->Length(); 1587 int32 lineNum = LineAt(inOffset); 1588 STELine* line = (*fLines)[lineNum]; 1589 float height = 0; 1590 1591 BPoint result; 1592 result.x = 0.0; 1593 result.y = line->origin + fTextRect.top; 1594 1595 // Handle the case where there is only one line 1596 // (no text inserted) 1597 // TODO: See if we can do this better 1598 if (fStyles->NumRuns() == 0) { 1599 const rgb_color *color = NULL; 1600 const BFont *font = NULL; 1601 fStyles->GetNullStyle(&font, &color); 1602 1603 font_height fontHeight; 1604 font->GetHeight(&fontHeight); 1605 height = fontHeight.ascent + fontHeight.descent; 1606 1607 } else { 1608 height = (line + 1)->origin - line->origin; 1609 1610 // special case: go down one line if inOffset is a newline 1611 if (inOffset == textLength && fText->RealCharAt(inOffset - 1) == B_ENTER) { 1612 result.y += height; 1613 height = LineHeight(CountLines() - 1); 1614 1615 } else { 1616 int32 offset = line->offset; 1617 int32 length = inOffset - line->offset; 1618 int32 numBytes = length; 1619 bool foundTab = false; 1620 do { 1621 foundTab = fText->FindChar(B_TAB, offset, &numBytes); 1622 float width = _StyledWidth(offset, numBytes); 1623 result.x += width; 1624 1625 if (foundTab) { 1626 result.x += _ActualTabWidth(result.x); 1627 numBytes++; 1628 } 1629 1630 offset += numBytes; 1631 length -= numBytes; 1632 numBytes = length; 1633 } while (foundTab && length > 0); 1634 } 1635 } 1636 1637 if (fAlignment != B_ALIGN_LEFT) { 1638 float modifier = fTextRect.right - LineWidth(lineNum); 1639 if (fAlignment == B_ALIGN_CENTER) 1640 modifier /= 2; 1641 result.x += modifier; 1642 } 1643 // convert from text rect coordinates 1644 // NOTE: I didn't understand why "- 1.0" 1645 // and it works only correct without it on Haiku app_server. 1646 // Feel free to enlighten me though! 1647 result.x += fTextRect.left;// - 1.0; 1648 1649 // round up 1650 result.x = ceilf(result.x); 1651 result.y = ceilf(result.y); 1652 if (outHeight != NULL) 1653 *outHeight = height; 1654 1655 return result; 1656 } 1657 1658 1659 /*! \brief Returns the offset for the given location. 1660 \param point A BPoint which specify the wanted location. 1661 \return The offset for the given point. 1662 */ 1663 int32 1664 BTextView::OffsetAt(BPoint point) const 1665 { 1666 const int32 textLength = fText->Length(); 1667 1668 // should we even bother? 1669 if (point.y >= fTextRect.bottom) 1670 return textLength; 1671 else if (point.y < fTextRect.top) 1672 return 0; 1673 1674 int32 lineNum = LineAt(point); 1675 STELine* line = (*fLines)[lineNum]; 1676 1677 // special case: if point is within the text rect and PixelToLine() 1678 // tells us that it's on the last line, but if point is actually 1679 // lower than the bottom of the last line, return the last offset 1680 // (can happen for newlines) 1681 if (lineNum == (fLines->NumLines() - 1)) { 1682 if (point.y >= ((line + 1)->origin + fTextRect.top)) 1683 return textLength; 1684 } 1685 1686 // convert to text rect coordinates 1687 if (fAlignment != B_ALIGN_LEFT) { 1688 float lineWidth = fTextRect.right - LineWidth(lineNum); 1689 if (fAlignment == B_ALIGN_CENTER) 1690 lineWidth /= 2; 1691 point.x -= lineWidth; 1692 } 1693 1694 point.x -= fTextRect.left; 1695 point.x = max_c(point.x, 0.0); 1696 1697 // TODO: The following code isn't very efficient because it always starts from the left end, 1698 // so when the point is near the right end it's very slow. 1699 int32 offset = line->offset; 1700 const int32 limit = (line + 1)->offset; 1701 float location = 0; 1702 do { 1703 const int32 nextInitial = _NextInitialByte(offset); 1704 const int32 saveOffset = offset; 1705 float width = 0; 1706 if (ByteAt(offset) == B_TAB) 1707 width = _ActualTabWidth(location); 1708 else 1709 width = _StyledWidth(saveOffset, nextInitial - saveOffset); 1710 if (location + width > point.x) { 1711 if (fabs(location + width - point.x) < fabs(location - point.x)) 1712 offset = nextInitial; 1713 break; 1714 } 1715 1716 location += width; 1717 offset = nextInitial; 1718 } while (offset < limit); 1719 1720 if (offset == (line + 1)->offset) { 1721 // special case: newlines aren't visible 1722 // return the offset of the character preceding the newline 1723 if (ByteAt(offset - 1) == B_ENTER) 1724 return --offset; 1725 1726 // special case: return the offset preceding any spaces that 1727 // aren't at the end of the buffer 1728 if (offset != textLength && ByteAt(offset - 1) == B_SPACE) 1729 return --offset; 1730 } 1731 1732 return offset; 1733 } 1734 1735 1736 /*! \brief Returns the offset of the given line. 1737 \param line A line number. 1738 \return The offset of the passed line. 1739 */ 1740 int32 1741 BTextView::OffsetAt(int32 line) const 1742 { 1743 if (line > fLines->NumLines()) 1744 return fText->Length(); 1745 1746 return (*fLines)[line]->offset; 1747 } 1748 1749 1750 /*! \brief Looks for a sequence of character that qualifies as a word. 1751 \param inOffset The offset where to start looking. 1752 \param outFromOffset A pointer to an integer which will contain the starting 1753 offset of the word. 1754 \param outToOffset A pointer to an integer which will contain the ending 1755 offset of the word. 1756 */ 1757 void 1758 BTextView::FindWord(int32 inOffset, int32 *outFromOffset, int32 *outToOffset) 1759 { 1760 int32 offset; 1761 uint32 charType = _CharClassification(inOffset); 1762 1763 // check to the left 1764 int32 previous; 1765 for (offset = inOffset, previous = offset; offset > 0; 1766 previous = _PreviousInitialByte(offset)) { 1767 if (_CharClassification(previous) != charType) 1768 break; 1769 offset = previous; 1770 } 1771 1772 if (outFromOffset) 1773 *outFromOffset = offset; 1774 1775 // check to the right 1776 int32 textLen = TextLength(); 1777 for (offset = inOffset; offset < textLen; offset = _NextInitialByte(offset)) { 1778 if (_CharClassification(offset) != charType) 1779 break; 1780 } 1781 1782 if (outToOffset) 1783 *outToOffset = offset; 1784 } 1785 1786 1787 /*! \brief Returns true if the character at the given offset can be the last character in a line. 1788 \param offset The offset of the character. 1789 \return true if the character can be the last of a line, false if not. 1790 */ 1791 bool 1792 BTextView::CanEndLine(int32 offset) 1793 { 1794 // TODO: Could be improved, the bebook says there are other checks to do 1795 return (_CharClassification(offset) == B_SEPARATOR_CHARACTER); 1796 } 1797 1798 1799 /*! \brief Returns the width of the line at the given index. 1800 \param lineNum A line index. 1801 */ 1802 float 1803 BTextView::LineWidth(int32 lineNum) const 1804 { 1805 if (lineNum < 0 || lineNum >= fLines->NumLines()) 1806 return 0; 1807 1808 STELine* line = (*fLines)[lineNum]; 1809 return _StyledWidth(line->offset, (line + 1)->offset - line->offset); 1810 } 1811 1812 1813 /*! \brief Returns the height of the line at the given index. 1814 \param lineNum A line index. 1815 */ 1816 float 1817 BTextView::LineHeight(int32 lineNum) const 1818 { 1819 return TextHeight(lineNum, lineNum); 1820 } 1821 1822 1823 /*! \brief Returns the height of the text comprised between the two given lines. 1824 \param startLine The index of the starting line. 1825 \param endLine The index of the ending line. 1826 */ 1827 float 1828 BTextView::TextHeight(int32 startLine, int32 endLine) const 1829 { 1830 const int32 numLines = fLines->NumLines(); 1831 if (startLine < 0) 1832 startLine = 0; 1833 if (endLine > numLines - 1) 1834 endLine = numLines - 1; 1835 1836 float height = (*fLines)[endLine + 1]->origin 1837 - (*fLines)[startLine]->origin; 1838 1839 if (startLine != endLine && endLine == numLines - 1 1840 && fText->RealCharAt(fText->Length() - 1) == B_ENTER) 1841 height += (*fLines)[endLine + 1]->origin - (*fLines)[endLine]->origin; 1842 1843 return ceilf(height); 1844 } 1845 1846 1847 void 1848 BTextView::GetTextRegion(int32 startOffset, int32 endOffset, BRegion *outRegion) const 1849 { 1850 if (!outRegion) 1851 return; 1852 1853 outRegion->MakeEmpty(); 1854 1855 // return an empty region if the range is invalid 1856 if (startOffset >= endOffset) 1857 return; 1858 1859 float startLineHeight = 0.0; 1860 float endLineHeight = 0.0; 1861 BPoint startPt = PointAt(startOffset, &startLineHeight); 1862 BPoint endPt = PointAt(endOffset, &endLineHeight); 1863 1864 startLineHeight = ceilf(startLineHeight); 1865 endLineHeight = ceilf(endLineHeight); 1866 1867 BRect selRect; 1868 1869 if (startPt.y == endPt.y) { 1870 // this is a one-line region 1871 selRect.left = max_c(startPt.x, fTextRect.left); 1872 selRect.top = startPt.y; 1873 selRect.right = endPt.x - 1.0; 1874 selRect.bottom = endPt.y + endLineHeight - 1.0; 1875 outRegion->Include(selRect); 1876 } else { 1877 // more than one line in the specified offset range 1878 selRect.left = max_c(startPt.x, fTextRect.left); 1879 selRect.top = startPt.y; 1880 selRect.right = fTextRect.right; 1881 selRect.bottom = startPt.y + startLineHeight - 1.0; 1882 outRegion->Include(selRect); 1883 1884 if (startPt.y + startLineHeight < endPt.y) { 1885 // more than two lines in the range 1886 selRect.left = fTextRect.left; 1887 selRect.top = startPt.y + startLineHeight; 1888 selRect.right = fTextRect.right; 1889 selRect.bottom = endPt.y - 1.0; 1890 outRegion->Include(selRect); 1891 } 1892 1893 selRect.left = fTextRect.left; 1894 selRect.top = endPt.y; 1895 selRect.right = endPt.x - 1.0; 1896 selRect.bottom = endPt.y + endLineHeight - 1.0; 1897 outRegion->Include(selRect); 1898 } 1899 } 1900 1901 1902 /*! \brief Scrolls the text so that the character at "inOffset" is within the visible range. 1903 \param inOffset The offset of the character. 1904 */ 1905 void 1906 BTextView::ScrollToOffset(int32 inOffset) 1907 { 1908 _ScrollToOffset(inOffset, ScrollBar(B_HORIZONTAL) != NULL, ScrollBar(B_VERTICAL) != NULL); 1909 } 1910 1911 void 1912 BTextView::_ScrollToOffset(int32 inOffset, bool useHorz, bool useVert) 1913 { 1914 BRect bounds = Bounds(); 1915 float lineHeight = 0.0; 1916 float xdiff = 0.0; 1917 float ydiff = 0.0; 1918 BPoint point = PointAt(inOffset, &lineHeight); 1919 1920 if (useHorz) { 1921 if (point.x < bounds.left) { 1922 xdiff = -1 * (bounds.IntegerWidth() / 2); 1923 // normalize scroll value to prevent scrolling past left boundary of view 1924 if (bounds.left < fabs(xdiff)) 1925 xdiff = -1 * bounds.left; 1926 } else if (point.x >= bounds.right) 1927 xdiff = bounds.IntegerWidth() / 2; 1928 } 1929 1930 if (useVert) { 1931 if (point.y < bounds.top) { 1932 ydiff = -1 * (bounds.IntegerHeight() / 2); 1933 // normalize scroll value to prevent scrolling past top of view 1934 if (bounds.top < fabs(ydiff)) 1935 ydiff = -1 * bounds.top; 1936 } else if (point.y >= bounds.bottom) 1937 ydiff = bounds.IntegerHeight() / 2; 1938 } 1939 1940 ScrollBy(xdiff, ydiff); 1941 } 1942 1943 1944 /*! \brief Scrolls the text so that the character which begins the current selection 1945 is within the visible range. 1946 \param inOffset The offset of the character. 1947 */ 1948 void 1949 BTextView::ScrollToSelection() 1950 { 1951 ScrollToOffset(fSelStart); 1952 } 1953 1954 1955 /*! \brief Highlight the text comprised between the given offset. 1956 \param startOffset The offset of the text to highlight. 1957 \param endOffset The offset where the text to highlight ends. 1958 */ 1959 void 1960 BTextView::Highlight(int32 startOffset, int32 endOffset) 1961 { 1962 // get real 1963 if (startOffset >= endOffset) 1964 return; 1965 1966 BRegion selRegion; 1967 GetTextRegion(startOffset, endOffset, &selRegion); 1968 1969 SetDrawingMode(B_OP_INVERT); 1970 FillRegion(&selRegion, B_SOLID_HIGH); 1971 SetDrawingMode(B_OP_COPY); 1972 } 1973 1974 1975 /*! \brief Sets the BTextView's text rectangle to be the same as the passed rect. 1976 \param rect A BRect. 1977 */ 1978 void 1979 BTextView::SetTextRect(BRect rect) 1980 { 1981 if (rect == fTextRect) 1982 return; 1983 1984 fTextRect = rect; 1985 1986 if (Window() != NULL) { 1987 Invalidate(); 1988 Window()->UpdateIfNeeded(); 1989 } 1990 } 1991 1992 1993 /*! \brief Returns the current BTextView's text rectangle. 1994 \return The current text rectangle. 1995 */ 1996 BRect 1997 BTextView::TextRect() const 1998 { 1999 return fTextRect; 2000 } 2001 2002 2003 /*! \brief Sets whether the BTextView accepts multiple character styles. 2004 */ 2005 void 2006 BTextView::SetStylable(bool stylable) 2007 { 2008 fStylable = stylable; 2009 } 2010 2011 2012 /*! \brief Tells if the object is stylable. 2013 \return true if the object is stylable, false otherwise. 2014 If the object is stylable, it can show multiple fonts at the same time. 2015 */ 2016 bool 2017 BTextView::IsStylable() const 2018 { 2019 return fStylable; 2020 } 2021 2022 2023 /*! \brief Sets the distance between tab stops (in pixel). 2024 \param width The distance (in pixel) between tab stops. 2025 */ 2026 void 2027 BTextView::SetTabWidth(float width) 2028 { 2029 if (width == fTabWidth) 2030 return; 2031 2032 fTabWidth = width; 2033 2034 if (Window() != NULL) 2035 _Refresh(0, fText->Length(), true, false); 2036 } 2037 2038 2039 /*! \brief Returns the BTextView's tab width. 2040 \return The BTextView's tab width. 2041 */ 2042 float 2043 BTextView::TabWidth() const 2044 { 2045 return fTabWidth; 2046 } 2047 2048 2049 /*! \brief Makes the object selectable, or not selectable. 2050 \param selectable If true, the object will be selectable from now on. 2051 if false, it won't be selectable anymore. 2052 */ 2053 void 2054 BTextView::MakeSelectable(bool selectable) 2055 { 2056 if (selectable == fSelectable) 2057 return; 2058 2059 fSelectable = selectable; 2060 2061 if (Window() != NULL) { 2062 if (fActive) { 2063 // show/hide the caret, hilite/unhilite the selection 2064 if (fSelStart != fSelEnd) 2065 Highlight(fSelStart, fSelEnd); 2066 else 2067 _InvertCaret(); 2068 } 2069 } 2070 } 2071 2072 2073 /*! \brief Tells if the object is selectable 2074 \return \c true if the object is selectable, 2075 \c false if not. 2076 */ 2077 bool 2078 BTextView::IsSelectable() const 2079 { 2080 return fSelectable; 2081 } 2082 2083 2084 /*! \brief Set (or remove) the editable property for the object. 2085 \param editable If true, will make the object editable, 2086 if false, will make it not editable. 2087 */ 2088 void 2089 BTextView::MakeEditable(bool editable) 2090 { 2091 if (editable == fEditable) 2092 return; 2093 2094 fEditable = editable; 2095 // TextControls change the color of the text when 2096 // they are made editable, so we need to invalidate 2097 // the NULL style here 2098 // TODO: it works well, but it could be caused by a bug somewhere else 2099 if (fEditable) 2100 fStyles->InvalidateNullStyle(); 2101 if (Window() != NULL && fActive) { 2102 if (!fEditable) { 2103 _HideCaret(); 2104 _CancelInputMethod(); 2105 } 2106 } 2107 } 2108 2109 2110 /*! \brief Tells if the object is editable. 2111 \return \c true if the object is editable, 2112 \c false if not. 2113 */ 2114 bool 2115 BTextView::IsEditable() const 2116 { 2117 return fEditable; 2118 } 2119 2120 2121 /*! \brief Set (or unset) word wrapping mode. 2122 \param wrap Specifies if you want word wrapping active or not. 2123 */ 2124 void 2125 BTextView::SetWordWrap(bool wrap) 2126 { 2127 if (wrap == fWrap) 2128 return; 2129 2130 if (Window() != NULL) { 2131 if (fActive) { 2132 // hide the caret, unhilite the selection 2133 if (fSelStart != fSelEnd) 2134 Highlight(fSelStart, fSelEnd); 2135 else { 2136 _HideCaret(); 2137 } 2138 } 2139 2140 fWrap = wrap; 2141 _Refresh(0, fText->Length(), true, true); 2142 2143 if (fActive) { 2144 // show the caret, hilite the selection 2145 if (fSelStart != fSelEnd && fSelectable) 2146 Highlight(fSelStart, fSelEnd); 2147 else 2148 _ShowCaret(); 2149 } 2150 } 2151 } 2152 2153 2154 /*! \brief Tells if word wrapping is activated. 2155 \return true if word wrapping is active, false otherwise. 2156 */ 2157 bool 2158 BTextView::DoesWordWrap() const 2159 { 2160 return fWrap; 2161 } 2162 2163 2164 /*! \brief Sets the maximun number of bytes that the BTextView can contain. 2165 \param max The new max number of bytes. 2166 */ 2167 void 2168 BTextView::SetMaxBytes(int32 max) 2169 { 2170 const int32 textLength = fText->Length(); 2171 fMaxBytes = max; 2172 2173 if (fMaxBytes < textLength) 2174 Delete(fMaxBytes, textLength); 2175 } 2176 2177 2178 /*! \brief Returns the maximum number of bytes that the BTextView can contain. 2179 \return the maximum number of bytes that the BTextView can contain. 2180 */ 2181 int32 2182 BTextView::MaxBytes() const 2183 { 2184 return fMaxBytes; 2185 } 2186 2187 2188 /*! \brief Adds the given char to the disallowed chars list. 2189 \param aChar The character to add to the list. 2190 2191 After this function returns, the given character won't be accepted 2192 by the textview anymore. 2193 */ 2194 void 2195 BTextView::DisallowChar(uint32 aChar) 2196 { 2197 if (fDisallowedChars == NULL) 2198 fDisallowedChars = new BList; 2199 if (!fDisallowedChars->HasItem(reinterpret_cast<void *>(aChar))) 2200 fDisallowedChars->AddItem(reinterpret_cast<void *>(aChar)); 2201 } 2202 2203 2204 /*! \brief Removes the given character from the disallowed list. 2205 \param aChar The character to remove from the list. 2206 */ 2207 void 2208 BTextView::AllowChar(uint32 aChar) 2209 { 2210 if (fDisallowedChars != NULL) 2211 fDisallowedChars->RemoveItem(reinterpret_cast<void *>(aChar)); 2212 } 2213 2214 2215 /*! \brief Sets the way text is aligned within the text rectangle. 2216 \param flag The new alignment. 2217 */ 2218 void 2219 BTextView::SetAlignment(alignment flag) 2220 { 2221 // Do a reality check 2222 if (fAlignment != flag && 2223 (flag == B_ALIGN_LEFT || 2224 flag == B_ALIGN_RIGHT || 2225 flag == B_ALIGN_CENTER)) { 2226 fAlignment = flag; 2227 2228 // After setting new alignment, update the view/window 2229 BWindow *window = Window(); 2230 if (window) { 2231 Invalidate(); 2232 window->UpdateIfNeeded(); 2233 } 2234 } 2235 } 2236 2237 2238 /*! \brief Returns the current alignment of the text. 2239 \return The current alignment. 2240 */ 2241 alignment 2242 BTextView::Alignment() const 2243 { 2244 return fAlignment; 2245 } 2246 2247 2248 /*! \brief Sets wheter a new line of text is automatically indented. 2249 \param state The new autoindent state 2250 */ 2251 void 2252 BTextView::SetAutoindent(bool state) 2253 { 2254 fAutoindent = state; 2255 } 2256 2257 2258 /*! \brief Returns the current autoindent state. 2259 \return The current autoindent state. 2260 */ 2261 bool 2262 BTextView::DoesAutoindent() const 2263 { 2264 return fAutoindent; 2265 } 2266 2267 2268 /*! \brief Set the color space for the offscreen BBitmap. 2269 \param colors The new colorspace for the offscreen BBitmap. 2270 */ 2271 void 2272 BTextView::SetColorSpace(color_space colors) 2273 { 2274 if (colors != fColorSpace && fOffscreen) { 2275 fColorSpace = colors; 2276 _DeleteOffscreen(); 2277 _NewOffscreen(); 2278 } 2279 } 2280 2281 2282 /*! \brief Returns the colorspace of the offscreen BBitmap, if any. 2283 \return The colorspace of the BTextView's offscreen BBitmap. 2284 */ 2285 color_space 2286 BTextView::ColorSpace() const 2287 { 2288 return fColorSpace; 2289 } 2290 2291 2292 /*! \brief Gives to the BTextView the ability to automatically resize itself when needed. 2293 \param resize If true, the BTextView will automatically resize itself. 2294 \param resizeView The BTextView's parent view, it's the view which resizes itself. 2295 The resizing mechanism is alternative to the BView resizing. The container view 2296 (the one passed to this function) should not automatically resize itself when the parent is 2297 resized. 2298 */ 2299 void 2300 BTextView::MakeResizable(bool resize, BView *resizeView) 2301 { 2302 if (resize) { 2303 fResizable = true; 2304 fContainerView = resizeView; 2305 2306 // Wrapping mode and resizable mode can't live together 2307 if (fWrap) { 2308 fWrap = false; 2309 2310 if (fActive && Window() != NULL) { 2311 if (fSelStart != fSelEnd && fSelectable) 2312 Highlight(fSelStart, fSelEnd); 2313 else 2314 _HideCaret(); 2315 } 2316 } 2317 } else { 2318 fResizable = false; 2319 fContainerView = NULL; 2320 if (fOffscreen) 2321 _DeleteOffscreen(); 2322 _NewOffscreen(); 2323 } 2324 2325 _Refresh(0, fText->Length(), true, false); 2326 } 2327 2328 2329 /*! \brief Returns whether the BTextView is currently resizable. 2330 \returns whether the BTextView is currently resizable. 2331 */ 2332 bool 2333 BTextView::IsResizable() const 2334 { 2335 return fResizable; 2336 } 2337 2338 2339 /*! \brief Enables or disables the undo mechanism. 2340 \param undo If true enables the undo mechanism, if false, disables it. 2341 */ 2342 void 2343 BTextView::SetDoesUndo(bool undo) 2344 { 2345 if (undo && fUndo == NULL) 2346 fUndo = new _BUndoBuffer_(this, B_UNDO_UNAVAILABLE); 2347 else if (!undo && fUndo != NULL) { 2348 delete fUndo; 2349 fUndo = NULL; 2350 } 2351 } 2352 2353 2354 /*! \brief Tells if the object is undoable. 2355 \return Whether the object is undoable. 2356 */ 2357 bool 2358 BTextView::DoesUndo() const 2359 { 2360 return fUndo != NULL; 2361 } 2362 2363 2364 void 2365 BTextView::HideTyping(bool enabled) 2366 { 2367 if (enabled) 2368 Delete(0, fText->Length()); 2369 2370 fText->SetPasswordMode(enabled); 2371 } 2372 2373 2374 bool 2375 BTextView::IsTypingHidden() const 2376 { 2377 return fText->PasswordMode(); 2378 } 2379 2380 2381 void 2382 BTextView::ResizeToPreferred() 2383 { 2384 float widht, height; 2385 GetPreferredSize(&widht, &height); 2386 BView::ResizeTo(widht, height); 2387 } 2388 2389 2390 void 2391 BTextView::GetPreferredSize(float *width, float *height) 2392 { 2393 BView::GetPreferredSize(width, height); 2394 } 2395 2396 2397 void 2398 BTextView::AllAttached() 2399 { 2400 BView::AllAttached(); 2401 } 2402 2403 2404 void 2405 BTextView::AllDetached() 2406 { 2407 BView::AllDetached(); 2408 } 2409 2410 2411 /* static */ 2412 text_run_array * 2413 BTextView::AllocRunArray(int32 entryCount, int32 *outSize) 2414 { 2415 int32 size = sizeof(text_run_array) + (entryCount - 1) * sizeof(text_run); 2416 2417 text_run_array *runArray = (text_run_array *)malloc(size); 2418 if (runArray == NULL) { 2419 if (outSize != NULL) 2420 *outSize = 0; 2421 return NULL; 2422 } 2423 2424 memset(runArray, 0, sizeof(size)); 2425 2426 runArray->count = entryCount; 2427 2428 // Call constructors explicitly as the text_run_array 2429 // was allocated with malloc (and has to, for backwards 2430 // compatibility) 2431 for (int32 i = 0; i < runArray->count; i++) { 2432 new (&runArray->runs[i].font) BFont; 2433 } 2434 2435 if (outSize != NULL) 2436 *outSize = size; 2437 2438 return runArray; 2439 } 2440 2441 2442 /* static */ 2443 text_run_array * 2444 BTextView::CopyRunArray(const text_run_array *orig, int32 countDelta) 2445 { 2446 text_run_array *copy = AllocRunArray(countDelta, NULL); 2447 if (copy != NULL) { 2448 for (int32 i = 0; i < countDelta; i++) { 2449 copy->runs[i].offset = orig->runs[i].offset; 2450 copy->runs[i].font = orig->runs[i].font; 2451 copy->runs[i].color = orig->runs[i].color; 2452 } 2453 } 2454 return copy; 2455 } 2456 2457 2458 /* static */ 2459 void 2460 BTextView::FreeRunArray(text_run_array *array) 2461 { 2462 if (array == NULL) 2463 return; 2464 2465 // Call destructors explicitly 2466 for (int32 i = 0; i < array->count; i++) 2467 array->runs[i].font.~BFont(); 2468 2469 free(array); 2470 } 2471 2472 2473 /* static */ 2474 void * 2475 BTextView::FlattenRunArray(const text_run_array* runArray, int32* _size) 2476 { 2477 CALLED(); 2478 int32 size = sizeof(flattened_text_run_array) + (runArray->count - 1) 2479 * sizeof(flattened_text_run); 2480 2481 flattened_text_run_array *array = (flattened_text_run_array *)malloc(size); 2482 if (array == NULL) { 2483 if (_size) 2484 *_size = 0; 2485 return NULL; 2486 } 2487 2488 array->magic = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayMagic); 2489 array->version = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayVersion); 2490 array->count = B_HOST_TO_BENDIAN_INT32(runArray->count); 2491 2492 for (int32 i = 0; i < runArray->count; i++) { 2493 array->styles[i].offset = B_HOST_TO_BENDIAN_INT32(runArray->runs[i].offset); 2494 runArray->runs[i].font.GetFamilyAndStyle(&array->styles[i].family, 2495 &array->styles[i].style); 2496 array->styles[i].size = B_HOST_TO_BENDIAN_FLOAT(runArray->runs[i].font.Size()); 2497 array->styles[i].shear = B_HOST_TO_BENDIAN_FLOAT(runArray->runs[i].font.Shear()); 2498 array->styles[i].face = B_HOST_TO_BENDIAN_INT16(runArray->runs[i].font.Face()); 2499 array->styles[i].red = runArray->runs[i].color.red; 2500 array->styles[i].green = runArray->runs[i].color.green; 2501 array->styles[i].blue = runArray->runs[i].color.blue; 2502 array->styles[i].alpha = 255; 2503 array->styles[i]._reserved_ = 0; 2504 } 2505 2506 if (_size) 2507 *_size = size; 2508 2509 return array; 2510 } 2511 2512 2513 /* static */ 2514 text_run_array * 2515 BTextView::UnflattenRunArray(const void* data, int32* _size) 2516 { 2517 CALLED(); 2518 flattened_text_run_array *array = (flattened_text_run_array *)data; 2519 2520 if (B_BENDIAN_TO_HOST_INT32(array->magic) != kFlattenedTextRunArrayMagic 2521 || B_BENDIAN_TO_HOST_INT32(array->version) != kFlattenedTextRunArrayVersion) { 2522 if (_size) 2523 *_size = 0; 2524 2525 return NULL; 2526 } 2527 2528 int32 count = B_BENDIAN_TO_HOST_INT32(array->count); 2529 2530 text_run_array *runArray = AllocRunArray(count, _size); 2531 if (runArray == NULL) 2532 return NULL; 2533 2534 for (int32 i = 0; i < count; i++) { 2535 runArray->runs[i].offset = B_BENDIAN_TO_HOST_INT32(array->styles[i].offset); 2536 2537 // Set family and style independently from each other, so that 2538 // even if the family doesn't exist, we try to preserve the style 2539 runArray->runs[i].font.SetFamilyAndStyle(array->styles[i].family, NULL); 2540 runArray->runs[i].font.SetFamilyAndStyle(NULL, array->styles[i].style); 2541 2542 runArray->runs[i].font.SetSize(B_BENDIAN_TO_HOST_FLOAT(array->styles[i].size)); 2543 runArray->runs[i].font.SetShear(B_BENDIAN_TO_HOST_FLOAT(array->styles[i].shear)); 2544 2545 uint16 face = B_BENDIAN_TO_HOST_INT16(array->styles[i].face); 2546 if (face != B_REGULAR_FACE) { 2547 // Be's version doesn't seem to set this correctly 2548 runArray->runs[i].font.SetFace(face); 2549 } 2550 2551 runArray->runs[i].color.red = array->styles[i].red; 2552 runArray->runs[i].color.green = array->styles[i].green; 2553 runArray->runs[i].color.blue = array->styles[i].blue; 2554 runArray->runs[i].color.alpha = array->styles[i].alpha; 2555 } 2556 2557 return runArray; 2558 } 2559 2560 2561 void 2562 BTextView::InsertText(const char *inText, int32 inLength, int32 inOffset, 2563 const text_run_array *inRuns) 2564 { 2565 CALLED(); 2566 // why add nothing? 2567 if (inLength < 1) 2568 return; 2569 2570 // TODO: Pin offset/lenght 2571 // add the text to the buffer 2572 fText->InsertText(inText, inLength, inOffset); 2573 2574 // update the start offsets of each line below offset 2575 fLines->BumpOffset(inLength, LineAt(inOffset) + 1); 2576 2577 // update the style runs 2578 fStyles->BumpOffset(inLength, fStyles->OffsetToRun(inOffset - 1) + 1); 2579 2580 if (inRuns != NULL) { 2581 _SetRunArray(inOffset, inOffset + inLength, inRuns); 2582 } else { 2583 // apply nullStyle to inserted text 2584 fStyles->SyncNullStyle(inOffset); 2585 fStyles->SetStyleRange(inOffset, inOffset + inLength, 2586 fText->Length(), B_FONT_ALL, NULL, NULL); 2587 } 2588 } 2589 2590 2591 void 2592 BTextView::DeleteText(int32 fromOffset, int32 toOffset) 2593 { 2594 CALLED(); 2595 // sanity checking 2596 if (fromOffset >= toOffset || fromOffset < 0 || toOffset > fText->Length()) 2597 return; 2598 2599 // set nullStyle to style at beginning of range 2600 fStyles->InvalidateNullStyle(); 2601 fStyles->SyncNullStyle(fromOffset); 2602 2603 // remove from the text buffer 2604 fText->RemoveRange(fromOffset, toOffset); 2605 2606 // remove any lines that have been obliterated 2607 fLines->RemoveLineRange(fromOffset, toOffset); 2608 2609 // remove any style runs that have been obliterated 2610 fStyles->RemoveStyleRange(fromOffset, toOffset); 2611 } 2612 2613 2614 /*! \brief Undoes the last changes. 2615 \param clipboard A clipboard to use for the undo operation. 2616 */ 2617 void 2618 BTextView::Undo(BClipboard *clipboard) 2619 { 2620 if (fUndo) 2621 fUndo->Undo(clipboard); 2622 } 2623 2624 2625 undo_state 2626 BTextView::UndoState(bool *isRedo) const 2627 { 2628 return fUndo == NULL ? B_UNDO_UNAVAILABLE : fUndo->State(isRedo); 2629 } 2630 2631 2632 void 2633 BTextView::GetDragParameters(BMessage *drag, BBitmap **bitmap, BPoint *point, 2634 BHandler **handler) 2635 { 2636 CALLED(); 2637 if (drag == NULL) 2638 return; 2639 2640 // Add originator and action 2641 drag->AddPointer("be:originator", this); 2642 drag->AddInt32("be_actions", B_TRASH_TARGET); 2643 2644 // add the text 2645 drag->AddData("text/plain", B_MIME_TYPE, fText->RealText() + fSelStart, 2646 fSelEnd - fSelStart); 2647 2648 // add the corresponding styles 2649 int32 size = 0; 2650 text_run_array *styles = RunArray(fSelStart, fSelEnd, &size); 2651 2652 if (styles != NULL) { 2653 drag->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE, 2654 styles, size); 2655 2656 FreeRunArray(styles); 2657 } 2658 2659 if (bitmap != NULL) 2660 *bitmap = NULL; 2661 if (handler != NULL) 2662 *handler = NULL; 2663 } 2664 2665 2666 void BTextView::_ReservedTextView3() {} 2667 void BTextView::_ReservedTextView4() {} 2668 void BTextView::_ReservedTextView5() {} 2669 void BTextView::_ReservedTextView6() {} 2670 void BTextView::_ReservedTextView7() {} 2671 void BTextView::_ReservedTextView8() {} 2672 void BTextView::_ReservedTextView9() {} 2673 void BTextView::_ReservedTextView10() {} 2674 void BTextView::_ReservedTextView11() {} 2675 void BTextView::_ReservedTextView12() {} 2676 2677 2678 /*! \brief Inits the BTextView object. 2679 \param textRect The BTextView's text rect. 2680 \param initialFont The font which the BTextView will use. 2681 \param initialColor The initial color of the text. 2682 */ 2683 void 2684 BTextView::_InitObject(BRect textRect, const BFont *initialFont, 2685 const rgb_color *initialColor) 2686 { 2687 BFont font; 2688 if (initialFont == NULL) 2689 GetFont(&font); 2690 else 2691 font = *initialFont; 2692 2693 _NormalizeFont(&font); 2694 2695 if (initialColor == NULL) 2696 initialColor = &kBlackColor; 2697 2698 fText = new _BTextGapBuffer_; 2699 fLines = new _BLineBuffer_; 2700 fStyles = new _BStyleBuffer_(&font, initialColor); 2701 2702 // We put these here instead of in the constructor initializer list 2703 // to have less code duplication, and a single place where to do changes 2704 // if needed., 2705 fTextRect = textRect; 2706 fSelStart = fSelEnd = 0; 2707 fCaretVisible = false; 2708 fCaretTime = 0; 2709 fClickOffset = 0; 2710 fClickCount = 0; 2711 fClickTime = 0; 2712 fDragOffset = -1; 2713 fCursor = 0; 2714 fActive = false; 2715 fStylable = false; 2716 fTabWidth = 28.0; 2717 fSelectable = true; 2718 fEditable = true; 2719 fWrap = true; 2720 fMaxBytes = LONG_MAX; 2721 fDisallowedChars = NULL; 2722 fAlignment = B_ALIGN_LEFT; 2723 fAutoindent = false; 2724 fOffscreen = NULL; 2725 fColorSpace = B_CMAP8; 2726 fResizable = false; 2727 fContainerView = NULL; 2728 fUndo = NULL; 2729 fInline = NULL; 2730 fDragRunner = NULL; 2731 fClickRunner = NULL; 2732 fTrackingMouse = NULL; 2733 fTextChange = NULL; 2734 } 2735 2736 2737 /*! \brief Called when Backspace key is pressed. 2738 */ 2739 void 2740 BTextView::_HandleBackspace() 2741 { 2742 if (fUndo) { 2743 _BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>( 2744 fUndo); 2745 if (!undoBuffer) { 2746 delete fUndo; 2747 fUndo = undoBuffer = new _BTypingUndoBuffer_(this); 2748 } 2749 undoBuffer->BackwardErase(); 2750 } 2751 2752 if (fSelStart == fSelEnd) { 2753 if (fSelStart == 0) 2754 return; 2755 else 2756 fSelStart = _PreviousInitialByte(fSelStart); 2757 } else 2758 Highlight(fSelStart, fSelEnd); 2759 2760 DeleteText(fSelStart, fSelEnd); 2761 fClickOffset = fSelEnd = fSelStart; 2762 2763 _Refresh(fSelStart, fSelEnd, true, true); 2764 } 2765 2766 2767 /*! \brief Called when any arrow key is pressed. 2768 \param inArrowKey The code for the pressed key. 2769 */ 2770 void 2771 BTextView::_HandleArrowKey(uint32 inArrowKey) 2772 { 2773 // return if there's nowhere to go 2774 if (fText->Length() == 0) 2775 return; 2776 2777 int32 selStart = fSelStart; 2778 int32 selEnd = fSelEnd; 2779 2780 int32 modifiers = 0; 2781 BMessage *message = Window()->CurrentMessage(); 2782 if (message != NULL) 2783 message->FindInt32("modifiers", &modifiers); 2784 2785 bool shiftDown = modifiers & B_SHIFT_KEY; 2786 2787 int32 currentOffset = fClickOffset; 2788 switch (inArrowKey) { 2789 case B_LEFT_ARROW: 2790 if (shiftDown) { 2791 fClickOffset = _PreviousInitialByte(fClickOffset); 2792 if (fClickOffset != currentOffset) { 2793 if (fClickOffset >= fSelStart) 2794 selEnd = fClickOffset; 2795 else 2796 selStart = fClickOffset; 2797 } 2798 } else if (fSelStart != fSelEnd) 2799 fClickOffset = fSelStart; 2800 else 2801 fClickOffset = _PreviousInitialByte(fSelStart); 2802 2803 break; 2804 2805 case B_RIGHT_ARROW: 2806 if (shiftDown) { 2807 fClickOffset = _NextInitialByte(fClickOffset); 2808 if (fClickOffset != currentOffset) { 2809 if (fClickOffset <= fSelEnd) 2810 selStart = fClickOffset; 2811 else 2812 selEnd = fClickOffset; 2813 } 2814 } else if (fSelStart != fSelEnd) 2815 fClickOffset = fSelEnd; 2816 else 2817 fClickOffset = _NextInitialByte(fSelEnd); 2818 break; 2819 2820 case B_UP_ARROW: 2821 { 2822 float height; 2823 BPoint point = PointAt(fClickOffset, &height); 2824 point.y -= height; 2825 fClickOffset = OffsetAt(point); 2826 if (shiftDown) { 2827 if (fClickOffset != currentOffset) { 2828 if (fClickOffset >= fSelStart) 2829 selEnd = fClickOffset; 2830 else 2831 selStart = fClickOffset; 2832 } 2833 } 2834 break; 2835 } 2836 2837 case B_DOWN_ARROW: 2838 { 2839 float height; 2840 BPoint point = PointAt(fClickOffset, &height); 2841 point.y += height; 2842 fClickOffset = OffsetAt(point); 2843 if (shiftDown) { 2844 if (fClickOffset != currentOffset) { 2845 if (fClickOffset <= fSelEnd) 2846 selStart = fClickOffset; 2847 else 2848 selEnd = fClickOffset; 2849 } 2850 } 2851 break; 2852 } 2853 } 2854 2855 // invalidate the null style 2856 fStyles->InvalidateNullStyle(); 2857 2858 currentOffset = fClickOffset; 2859 if (shiftDown) 2860 Select(selStart, selEnd); 2861 else 2862 Select(fClickOffset, fClickOffset); 2863 2864 fClickOffset = currentOffset; 2865 // Select sets fClickOffset = fSelEnd 2866 2867 // scroll if needed 2868 ScrollToOffset(fClickOffset); 2869 } 2870 2871 2872 /*! \brief Called when the Delete key is pressed. 2873 */ 2874 void 2875 BTextView::_HandleDelete() 2876 { 2877 if (fUndo) { 2878 _BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>( 2879 fUndo); 2880 if (!undoBuffer) { 2881 delete fUndo; 2882 fUndo = undoBuffer = new _BTypingUndoBuffer_(this); 2883 } 2884 undoBuffer->ForwardErase(); 2885 } 2886 2887 if (fSelStart == fSelEnd) { 2888 if (fSelEnd == fText->Length()) 2889 return; 2890 else 2891 fSelEnd = _NextInitialByte(fSelEnd); 2892 } else 2893 Highlight(fSelStart, fSelEnd); 2894 2895 DeleteText(fSelStart, fSelEnd); 2896 2897 fClickOffset = fSelEnd = fSelStart; 2898 2899 _Refresh(fSelStart, fSelEnd, true, true); 2900 } 2901 2902 2903 /*! \brief Called when a "Page key" is pressed. 2904 \param inPageKey The page key which has been pressed. 2905 */ 2906 void 2907 BTextView::_HandlePageKey(uint32 inPageKey) 2908 { 2909 int32 mods = 0; 2910 BMessage *currentMessage = Window()->CurrentMessage(); 2911 if (currentMessage) 2912 currentMessage->FindInt32("modifiers", &mods); 2913 2914 bool shiftDown = mods & B_SHIFT_KEY; 2915 STELine* line = NULL; 2916 int32 start = fSelStart, end = fSelEnd; 2917 2918 switch (inPageKey) { 2919 case B_HOME: 2920 line = (*fLines)[CurrentLine()]; 2921 fClickOffset = line->offset; 2922 if (shiftDown) { 2923 if (fClickOffset <= fSelStart) { 2924 start = fClickOffset; 2925 end = fSelEnd; 2926 } else { 2927 start = fSelStart; 2928 end = fClickOffset; 2929 } 2930 } else 2931 start = end = fClickOffset; 2932 2933 break; 2934 2935 case B_END: 2936 // If we are on the last line, just go to the last 2937 // character in the buffer, otherwise get the starting 2938 // offset of the next line, and go to the previous character 2939 if (CurrentLine() + 1 < fLines->NumLines()) { 2940 line = (*fLines)[CurrentLine() + 1]; 2941 fClickOffset = _PreviousInitialByte(line->offset); 2942 } else { 2943 // This check if needed to avoid moving the cursor 2944 // when the cursor is on the last line, and that line 2945 // is empty 2946 if (fClickOffset != fText->Length()) { 2947 fClickOffset = fText->Length(); 2948 if (ByteAt(fClickOffset - 1) == B_ENTER) 2949 fClickOffset--; 2950 } 2951 } 2952 2953 if (shiftDown) { 2954 if (fClickOffset >= fSelEnd) { 2955 start = fSelStart; 2956 end = fClickOffset; 2957 } else { 2958 start = fClickOffset; 2959 end = fSelEnd; 2960 } 2961 } else 2962 start = end = fClickOffset; 2963 2964 break; 2965 2966 case B_PAGE_UP: 2967 { 2968 BPoint currentPos = PointAt(fClickOffset); 2969 2970 currentPos.y -= Bounds().Height(); 2971 fClickOffset = OffsetAt(LineAt(currentPos)); 2972 2973 if (shiftDown) { 2974 if (fClickOffset <= fSelStart) { 2975 start = fClickOffset; 2976 end = fSelEnd; 2977 } else { 2978 start = fSelStart; 2979 end = fClickOffset; 2980 } 2981 } else 2982 start = end = fClickOffset; 2983 break; 2984 } 2985 2986 case B_PAGE_DOWN: 2987 { 2988 BPoint currentPos = PointAt(fClickOffset); 2989 2990 currentPos.y += Bounds().Height(); 2991 fClickOffset = OffsetAt(LineAt(currentPos) + 1); 2992 2993 if (shiftDown) { 2994 if (fClickOffset >= fSelEnd) { 2995 start = fSelStart; 2996 end = fClickOffset; 2997 } else { 2998 start = fClickOffset; 2999 end = fSelEnd; 3000 } 3001 } else 3002 start = end = fClickOffset; 3003 3004 break; 3005 } 3006 } 3007 3008 ScrollToOffset(fClickOffset); 3009 Select(start, end); 3010 } 3011 3012 3013 /*! \brief Called when an alphanumeric key is pressed. 3014 \param bytes The string or character associated with the key. 3015 \param numBytes The amount of bytes containes in "bytes". 3016 */ 3017 void 3018 BTextView::_HandleAlphaKey(const char *bytes, int32 numBytes) 3019 { 3020 // TODO: block input if not editable (Andrew) 3021 if (fUndo) { 3022 _BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>(fUndo); 3023 if (!undoBuffer) { 3024 delete fUndo; 3025 fUndo = undoBuffer = new _BTypingUndoBuffer_(this); 3026 } 3027 undoBuffer->InputCharacter(numBytes); 3028 } 3029 3030 bool erase = fSelStart != fText->Length(); 3031 int32 saveStart = fSelStart; 3032 3033 if (fSelStart != fSelEnd) { 3034 Highlight(fSelStart, fSelEnd); 3035 DeleteText(fSelStart, fSelEnd); 3036 erase = true; 3037 } 3038 3039 if (fAutoindent && numBytes == 1 && *bytes == B_ENTER) { 3040 int32 start, offset; 3041 start = offset = OffsetAt(LineAt(fSelStart)); 3042 3043 while (ByteAt(offset) != '\0' && 3044 (ByteAt(offset) == B_TAB || ByteAt(offset) == B_SPACE)) 3045 offset++; 3046 3047 if (start != offset) 3048 InsertText(Text() + start, offset - start, fSelStart, NULL); 3049 3050 InsertText(bytes, numBytes, fSelStart, NULL); 3051 numBytes += offset - start; 3052 3053 } else 3054 InsertText(bytes, numBytes, fSelStart, NULL); 3055 3056 fClickOffset = fSelEnd = fSelStart = fSelStart + numBytes; 3057 3058 if (Window()) 3059 _Refresh(saveStart, fSelEnd, erase, true); 3060 } 3061 3062 3063 /*! \brief Redraw the text comprised between the two given offsets, 3064 recalculating linebreaks if needed. 3065 \param fromOffset The offset from where to refresh. 3066 \param toOffset The offset where to refresh to. 3067 \param erase If true, the function will also erase the textview content 3068 in the parts where text isn't present. 3069 \param scroll If true, function will scroll the view to the end offset. 3070 */ 3071 void 3072 BTextView::_Refresh(int32 fromOffset, int32 toOffset, bool erase, bool scroll) 3073 { 3074 // TODO: Cleanup 3075 float saveHeight = fTextRect.Height(); 3076 int32 fromLine = LineAt(fromOffset); 3077 int32 toLine = LineAt(toOffset); 3078 int32 saveFromLine = fromLine; 3079 int32 saveToLine = toLine; 3080 float saveLineHeight = LineHeight(fromLine); 3081 3082 _RecalculateLineBreaks(&fromLine, &toLine); 3083 3084 // TODO: Maybe there is still something we can do without a window... 3085 if (!Window()) 3086 return; 3087 3088 BRect bounds = Bounds(); 3089 float newHeight = fTextRect.Height(); 3090 3091 // if the line breaks have changed, force an erase 3092 if (fromLine != saveFromLine || toLine != saveToLine 3093 || newHeight != saveHeight ) { 3094 erase = true; 3095 fromOffset = -1; 3096 } 3097 3098 if (newHeight != saveHeight) { 3099 // the text area has changed 3100 if (newHeight < saveHeight) 3101 toLine = LineAt(BPoint(0.0f, saveHeight + fTextRect.top)); 3102 else 3103 toLine = LineAt(BPoint(0.0f, newHeight + fTextRect.top)); 3104 } 3105 3106 // draw only those lines that are visible 3107 int32 fromVisible = LineAt(BPoint(0.0f, bounds.top)); 3108 int32 toVisible = LineAt(BPoint(0.0f, bounds.bottom)); 3109 fromLine = max_c(fromVisible, fromLine); 3110 toLine = min_c(toLine, toVisible); 3111 3112 int32 drawOffset = fromOffset; 3113 if (LineHeight(fromLine) != saveLineHeight 3114 || newHeight < saveHeight || fromLine < saveFromLine 3115 || fAlignment != B_ALIGN_LEFT) 3116 drawOffset = (*fLines)[fromLine]->offset; 3117 3118 if (fResizable) 3119 _AutoResize(false); 3120 3121 _DrawLines(fromLine, toLine, drawOffset, erase); 3122 3123 // erase the area below the text 3124 BRect eraseRect = bounds; 3125 eraseRect.top = fTextRect.top + (*fLines)[fLines->NumLines()]->origin; 3126 eraseRect.bottom = fTextRect.top + saveHeight; 3127 if (eraseRect.bottom > eraseRect.top && eraseRect.Intersects(bounds)) { 3128 SetLowColor(ViewColor()); 3129 FillRect(eraseRect, B_SOLID_LOW); 3130 } 3131 3132 // update the scroll bars if the text area has changed 3133 if (newHeight != saveHeight) 3134 _UpdateScrollbars(); 3135 3136 if (scroll) 3137 ScrollToSelection(); 3138 3139 Flush(); 3140 } 3141 3142 3143 void 3144 BTextView::_RecalculateLineBreaks(int32 *startLine, int32 *endLine) 3145 { 3146 // are we insane? 3147 *startLine = (*startLine < 0) ? 0 : *startLine; 3148 *endLine = (*endLine > fLines->NumLines() - 1) ? fLines->NumLines() - 1 : *endLine; 3149 3150 int32 textLength = fText->Length(); 3151 int32 lineIndex = (*startLine > 0) ? *startLine - 1 : 0; 3152 int32 recalThreshold = (*fLines)[*endLine + 1]->offset; 3153 float width = fTextRect.Width(); 3154 STELine* curLine = (*fLines)[lineIndex]; 3155 STELine* nextLine = curLine + 1; 3156 3157 do { 3158 float ascent, descent; 3159 int32 fromOffset = curLine->offset; 3160 int32 toOffset = _FindLineBreak(fromOffset, &ascent, &descent, &width); 3161 3162 // we want to advance at least by one character 3163 int32 nextOffset = _NextInitialByte(fromOffset); 3164 if (toOffset < nextOffset && fromOffset < textLength) 3165 toOffset = nextOffset; 3166 3167 // set the ascent of this line 3168 curLine->ascent = ascent; 3169 3170 lineIndex++; 3171 STELine saveLine = *nextLine; 3172 if (lineIndex > fLines->NumLines() || toOffset < nextLine->offset) { 3173 // the new line comes before the old line start, add a line 3174 STELine newLine; 3175 newLine.offset = toOffset; 3176 newLine.origin = ceilf(curLine->origin + ascent + descent) + 1; 3177 newLine.ascent = 0; 3178 fLines->InsertLine(&newLine, lineIndex); 3179 } else { 3180 // update the exising line 3181 nextLine->offset = toOffset; 3182 nextLine->origin = ceilf(curLine->origin + ascent + descent) + 1; 3183 3184 // remove any lines that start before the current line 3185 while (lineIndex < fLines->NumLines() 3186 && toOffset >= ((*fLines)[lineIndex] + 1)->offset) { 3187 fLines->RemoveLines(lineIndex + 1); 3188 } 3189 3190 nextLine = (*fLines)[lineIndex]; 3191 if (nextLine->offset == saveLine.offset) { 3192 if (nextLine->offset >= recalThreshold) { 3193 if (nextLine->origin != saveLine.origin) 3194 fLines->BumpOrigin(nextLine->origin - saveLine.origin, 3195 lineIndex + 1); 3196 break; 3197 } 3198 } else { 3199 if (lineIndex > 0 && lineIndex == *startLine) 3200 *startLine = lineIndex - 1; 3201 } 3202 } 3203 3204 curLine = (*fLines)[lineIndex]; 3205 nextLine = curLine + 1; 3206 } while (curLine->offset < textLength); 3207 3208 // update the text rect 3209 float newHeight = TextHeight(0, fLines->NumLines() - 1); 3210 fTextRect.bottom = fTextRect.top + newHeight; 3211 3212 *endLine = lineIndex - 1; 3213 *startLine = min_c(*startLine, *endLine); 3214 } 3215 3216 3217 int32 3218 BTextView::_FindLineBreak(int32 fromOffset, float *outAscent, float *outDescent, 3219 float *ioWidth) 3220 { 3221 *outAscent = 0.0; 3222 *outDescent = 0.0; 3223 3224 const int32 limit = fText->Length(); 3225 3226 // is fromOffset at the end? 3227 if (fromOffset >= limit) { 3228 // try to return valid height info anyway 3229 if (fStyles->NumRuns() > 0) { 3230 fStyles->Iterate(fromOffset, 1, fInline, NULL, NULL, outAscent, 3231 outDescent); 3232 } else { 3233 if (fStyles->IsValidNullStyle()) { 3234 const BFont *font = NULL; 3235 fStyles->GetNullStyle(&font, NULL); 3236 3237 font_height fh; 3238 font->GetHeight(&fh); 3239 *outAscent = fh.ascent; 3240 *outDescent = fh.descent + fh.leading; 3241 } 3242 } 3243 3244 return limit; 3245 } 3246 3247 int32 offset = fromOffset; 3248 3249 // Text wrapping is turned off. 3250 // Just find the offset of the first \n character 3251 if (!fWrap) { 3252 offset = limit - fromOffset; 3253 fText->FindChar(B_ENTER, fromOffset, &offset); 3254 offset += fromOffset; 3255 offset = (offset < limit) ? offset + 1 : limit; 3256 3257 *ioWidth = _StyledWidth(fromOffset, offset - fromOffset, outAscent, outDescent); 3258 3259 return offset; 3260 } 3261 3262 bool done = false; 3263 float ascent = 0.0; 3264 float descent = 0.0; 3265 int32 delta = 0; 3266 float deltaWidth = 0.0; 3267 float tabWidth = 0.0; 3268 float strWidth = 0.0; 3269 3270 // wrap the text 3271 do { 3272 bool foundTab = false; 3273 3274 // find the next line break candidate 3275 for ( ; (offset + delta) < limit ; delta++) { 3276 if (CanEndLine(offset + delta)) 3277 break; 3278 } 3279 for ( ; (offset + delta) < limit; delta++) { 3280 uchar theChar = fText->RealCharAt(offset + delta); 3281 if (!CanEndLine(offset + delta)) 3282 break; 3283 3284 if (theChar == B_ENTER) { 3285 // found a newline, we're done! 3286 done = true; 3287 delta++; 3288 break; 3289 } else { 3290 // include all trailing spaces and tabs, 3291 // but not spaces after tabs 3292 if (theChar != B_SPACE && theChar != B_TAB) 3293 break; 3294 else { 3295 if (theChar == B_SPACE && foundTab) 3296 break; 3297 else { 3298 if (theChar == B_TAB) 3299 foundTab = true; 3300 } 3301 } 3302 } 3303 } 3304 delta = max_c(delta, 1); 3305 3306 deltaWidth = _StyledWidth(offset, delta, &ascent, &descent); 3307 strWidth += deltaWidth; 3308 3309 if (!foundTab) 3310 tabWidth = 0.0; 3311 else { 3312 int32 tabCount = 0; 3313 for (int32 i = delta - 1; fText->RealCharAt(offset + i) == B_TAB; 3314 i--) { 3315 tabCount++; 3316 } 3317 3318 tabWidth = _ActualTabWidth(strWidth); 3319 if (tabCount > 1) 3320 tabWidth += ((tabCount - 1) * fTabWidth); 3321 strWidth += tabWidth; 3322 } 3323 3324 if (strWidth >= *ioWidth) { 3325 // we've found where the line will wrap 3326 bool foundNewline = done; 3327 done = true; 3328 int32 pos = delta - 1; 3329 if (!CanEndLine(offset + pos)) 3330 break; 3331 3332 strWidth -= (deltaWidth + tabWidth); 3333 3334 while (offset + pos > offset) { 3335 if (!CanEndLine(offset + pos)) 3336 break; 3337 3338 pos--; 3339 } 3340 3341 strWidth += _StyledWidth(offset, pos + 1, &ascent, &descent); 3342 if (strWidth >= *ioWidth) 3343 break; 3344 3345 if (!foundNewline) { 3346 while (offset + delta < limit) { 3347 const char realChar = fText->RealCharAt(offset + delta); 3348 if (realChar != B_SPACE && realChar != B_TAB) 3349 break; 3350 3351 delta++; 3352 } 3353 if (offset + delta < limit 3354 && fText->RealCharAt(offset + delta) == B_ENTER) 3355 delta++; 3356 } 3357 // get the ascent and descent of the spaces/tabs 3358 _StyledWidth(offset, delta, &ascent, &descent); 3359 } 3360 3361 *outAscent = max_c(ascent, *outAscent); 3362 *outDescent = max_c(descent, *outDescent); 3363 3364 offset += delta; 3365 delta = 0; 3366 } while (offset < limit && !done); 3367 3368 if (offset - fromOffset < 1) { 3369 // there weren't any words that fit entirely in this line 3370 // force a break in the middle of a word 3371 *outAscent = 0.0; 3372 *outDescent = 0.0; 3373 strWidth = 0.0; 3374 3375 int32 current = fromOffset; 3376 for (offset = fromOffset; offset <= limit; current = offset, 3377 offset = _NextInitialByte(offset)) { 3378 strWidth += _StyledWidth(current, offset - current, &ascent, 3379 &descent); 3380 if (strWidth >= *ioWidth) { 3381 offset = _PreviousInitialByte(offset); 3382 break; 3383 } 3384 3385 *outAscent = max_c(ascent, *outAscent); 3386 *outDescent = max_c(descent, *outDescent); 3387 } 3388 } 3389 3390 return min_c(offset, limit); 3391 } 3392 3393 3394 /*! \brief Calculate the width of the text within the given limits. 3395 \param fromOffset The offset where to start. 3396 \param length The length of the text to examine. 3397 \param outAscent A pointer to a float which will contain the maximum ascent. 3398 \param outDescent A pointer to a float which will contain the maximum descent. 3399 \return The width for the text within the given limits. 3400 */ 3401 float 3402 BTextView::_StyledWidth(int32 fromOffset, int32 length, float *outAscent, 3403 float *outDescent) const 3404 { 3405 float result = 0.0; 3406 float ascent = 0.0; 3407 float descent = 0.0; 3408 float maxAscent = 0.0; 3409 float maxDescent = 0.0; 3410 3411 // iterate through the style runs 3412 const BFont *font = NULL; 3413 int32 numChars; 3414 while ((numChars = fStyles->Iterate(fromOffset, length, fInline, &font, 3415 NULL, &ascent, &descent)) != 0) { 3416 maxAscent = max_c(ascent, maxAscent); 3417 maxDescent = max_c(descent, maxDescent); 3418 3419 #if USE_WIDTHBUFFER 3420 // Use _BWidthBuffer_ if possible 3421 if (sWidths != NULL) { 3422 LockWidthBuffer(); 3423 result += sWidths->StringWidth(*fText, fromOffset, numChars, font); 3424 UnlockWidthBuffer(); 3425 } else 3426 #endif 3427 result += font->StringWidth(fText->RealText() + fromOffset, numChars); 3428 3429 fromOffset += numChars; 3430 length -= numChars; 3431 } 3432 3433 if (outAscent != NULL) 3434 *outAscent = maxAscent; 3435 if (outDescent != NULL) 3436 *outDescent = maxDescent; 3437 3438 return result; 3439 } 3440 3441 3442 // Unlike the _StyledWidth method, this one takes as parameter 3443 // the number of chars, not the number of bytes. 3444 float 3445 BTextView::_StyledWidthUTF8Safe(int32 fromOffset, int32 numChars, 3446 float *outAscent, float *outDescent) const 3447 { 3448 int32 toOffset = fromOffset; 3449 while (numChars--) 3450 toOffset = _NextInitialByte(toOffset); 3451 3452 const int32 length = toOffset - fromOffset; 3453 return _StyledWidth(fromOffset, length, outAscent, outDescent); 3454 } 3455 3456 3457 /*! \brief Calculate the actual tab width for the given location. 3458 \param location The location to calculate the tab width of. 3459 \return The actual tab width for the given location 3460 */ 3461 float 3462 BTextView::_ActualTabWidth(float location) const 3463 { 3464 return fTabWidth - fmod(location, fTabWidth); 3465 } 3466 3467 3468 void 3469 BTextView::_DoInsertText(const char *inText, int32 inLength, int32 inOffset, 3470 const text_run_array *inRuns, _BTextChangeResult_ *outResult) 3471 { 3472 _CancelInputMethod(); 3473 3474 // Don't do any check, the public methods will have adjusted 3475 // eventual bogus values... 3476 3477 const int32 textLength = TextLength(); 3478 if (inOffset > textLength) 3479 inOffset = textLength; 3480 3481 // copy data into buffer 3482 InsertText(inText, inLength, inOffset, inRuns); 3483 3484 // offset the caret/selection 3485 int32 saveStart = fSelStart; 3486 fSelStart += inLength; 3487 fSelEnd += inLength; 3488 3489 // recalc line breaks and draw the text 3490 _Refresh(saveStart, fSelEnd, true, false); 3491 } 3492 3493 3494 void 3495 BTextView::_DoDeleteText(int32 fromOffset, int32 toOffset, _BTextChangeResult_ *outResult) 3496 { 3497 CALLED(); 3498 } 3499 3500 3501 void 3502 BTextView::_DrawLine(BView *view, const int32 &lineNum, const int32 &startOffset, 3503 const bool &erase, BRect &eraseRect, BRegion &inputRegion) 3504 { 3505 STELine *line = (*fLines)[lineNum]; 3506 float startLeft = fTextRect.left; 3507 if (startOffset != -1) { 3508 if (ByteAt(startOffset) == B_ENTER) { 3509 // StartOffset is a newline 3510 startLeft = PointAt(line->offset).x; 3511 } else 3512 startLeft = PointAt(startOffset).x; 3513 } 3514 3515 int32 length = (line + 1)->offset; 3516 if (startOffset != -1) 3517 length -= startOffset; 3518 else 3519 length -= line->offset; 3520 3521 // DrawString() chokes if you draw a newline 3522 if (ByteAt((line + 1)->offset - 1) == B_ENTER) 3523 length--; 3524 if (fAlignment != B_ALIGN_LEFT) { 3525 // B_ALIGN_RIGHT 3526 startLeft = (fTextRect.right - LineWidth(lineNum)); 3527 if (fAlignment == B_ALIGN_CENTER) 3528 startLeft /= 2; 3529 startLeft += fTextRect.left; 3530 } 3531 3532 view->MovePenTo(startLeft, line->origin + line->ascent + fTextRect.top + 1); 3533 3534 if (erase) { 3535 eraseRect.top = line->origin + fTextRect.top; 3536 eraseRect.bottom = (line + 1)->origin + fTextRect.top; 3537 3538 view->FillRect(eraseRect, B_SOLID_LOW); 3539 } 3540 3541 // do we have any text to draw? 3542 if (length > 0) { 3543 bool foundTab = false; 3544 int32 tabChars = 0; 3545 int32 numTabs = 0; 3546 int32 offset = startOffset != -1 ? startOffset : line->offset; 3547 const BFont *font = NULL; 3548 const rgb_color *color = NULL; 3549 int32 numBytes; 3550 // iterate through each style on this line 3551 while ((numBytes = fStyles->Iterate(offset, length, fInline, &font, 3552 &color)) != 0) { 3553 view->SetFont(font); 3554 view->SetHighColor(*color); 3555 3556 tabChars = numBytes; 3557 do { 3558 foundTab = fText->FindChar(B_TAB, offset, &tabChars); 3559 if (foundTab) { 3560 do { 3561 numTabs++; 3562 if (ByteAt(offset + tabChars + numTabs) != B_TAB) 3563 break; 3564 } while ((tabChars + numTabs) < numBytes); 3565 } 3566 3567 if (inputRegion.CountRects() > 0) { 3568 BRegion textRegion; 3569 GetTextRegion(offset, offset + length, &textRegion); 3570 3571 textRegion.IntersectWith(&inputRegion); 3572 view->PushState(); 3573 3574 // Highlight in blue the inputted text 3575 view->SetHighColor(kBlueInputColor); 3576 view->FillRect(textRegion.Frame()); 3577 3578 // Highlight in red the selected part 3579 if (fInline->SelectionLength() > 0) { 3580 BRegion selectedRegion; 3581 GetTextRegion(fInline->Offset() 3582 + fInline->SelectionOffset(), fInline->Offset() 3583 + fInline->SelectionOffset() 3584 + fInline->SelectionLength(), &selectedRegion); 3585 3586 textRegion.IntersectWith(&selectedRegion); 3587 3588 view->SetHighColor(kRedInputColor); 3589 view->FillRect(textRegion.Frame()); 3590 } 3591 3592 view->PopState(); 3593 } 3594 3595 int32 returnedBytes = tabChars; 3596 const char *stringToDraw = fText->GetString(offset, 3597 &returnedBytes); 3598 3599 view->DrawString(stringToDraw, returnedBytes); 3600 if (foundTab) { 3601 float penPos = PenLocation().x - fTextRect.left; 3602 float tabWidth = _ActualTabWidth(penPos); 3603 if (numTabs > 1) 3604 tabWidth += ((numTabs - 1) * fTabWidth); 3605 3606 view->MovePenBy(tabWidth, 0.0); 3607 tabChars += numTabs; 3608 } 3609 3610 offset += tabChars; 3611 length -= tabChars; 3612 numBytes -= tabChars; 3613 tabChars = numBytes; 3614 numTabs = 0; 3615 } while (foundTab && tabChars > 0); 3616 } 3617 } 3618 } 3619 3620 3621 void 3622 BTextView::_DrawLines(int32 startLine, int32 endLine, int32 startOffset, 3623 bool erase) 3624 { 3625 if (!Window()) 3626 return; 3627 3628 // clip the text 3629 BRect clipRect = Bounds() & fTextRect; 3630 clipRect.InsetBy(-1, -1); 3631 3632 BRegion newClip; 3633 newClip.Set(clipRect); 3634 ConstrainClippingRegion(&newClip); 3635 3636 // set the low color to the view color so that 3637 // drawing to a non-white background will work 3638 SetLowColor(ViewColor()); 3639 3640 BView *view = NULL; 3641 if (fOffscreen == NULL) 3642 view = this; 3643 else { 3644 fOffscreen->Lock(); 3645 view = fOffscreen->ChildAt(0); 3646 view->SetLowColor(ViewColor()); 3647 view->FillRect(view->Bounds(), B_SOLID_LOW); 3648 } 3649 3650 long maxLine = fLines->NumLines() - 1; 3651 if (startLine < 0) 3652 startLine = 0; 3653 if (endLine > maxLine) 3654 endLine = maxLine; 3655 3656 // TODO: See if we can avoid this 3657 if (fAlignment != B_ALIGN_LEFT) 3658 erase = true; 3659 3660 // Actually hide the caret 3661 if (fCaretVisible) 3662 _DrawCaret(fSelStart); 3663 3664 BRect eraseRect = clipRect; 3665 int32 startEraseLine = startLine; 3666 STELine* line = (*fLines)[startLine]; 3667 3668 if (erase && startOffset != -1 && fAlignment == B_ALIGN_LEFT) { 3669 // erase only to the right of startOffset 3670 startEraseLine++; 3671 int32 startErase = startOffset; 3672 3673 BPoint erasePoint = PointAt(startErase); 3674 eraseRect.left = erasePoint.x; 3675 eraseRect.top = erasePoint.y; 3676 eraseRect.bottom = (line + 1)->origin + fTextRect.top; 3677 3678 view->FillRect(eraseRect, B_SOLID_LOW); 3679 3680 eraseRect = clipRect; 3681 } 3682 3683 BRegion inputRegion; 3684 if (fInline != NULL && fInline->IsActive()) 3685 GetTextRegion(fInline->Offset(), fInline->Offset() + fInline->Length(), &inputRegion); 3686 3687 //BPoint leftTop(startLeft, line->origin); 3688 for (int32 lineNum = startLine; lineNum <= endLine; lineNum++) { 3689 const bool eraseThisLine = erase && lineNum >= startEraseLine; 3690 _DrawLine(view, lineNum, startOffset, eraseThisLine, eraseRect, inputRegion); 3691 startOffset = -1; 3692 // Set this to -1 so the next iteration will use the line offset 3693 } 3694 3695 // draw the caret/hilite the selection 3696 if (fActive) { 3697 if (fSelStart != fSelEnd && fSelectable) 3698 Highlight(fSelStart, fSelEnd); 3699 else { 3700 if (fCaretVisible) 3701 _DrawCaret(fSelStart); 3702 } 3703 } 3704 3705 if (fOffscreen != NULL) { 3706 view->Sync(); 3707 /*BPoint penLocation = view->PenLocation(); 3708 BRect drawRect(leftTop.x, leftTop.y, penLocation.x, penLocation.y); 3709 DrawBitmap(fOffscreen, drawRect, drawRect);*/ 3710 fOffscreen->Unlock(); 3711 } 3712 3713 ConstrainClippingRegion(NULL); 3714 } 3715 3716 3717 void 3718 BTextView::_DrawCaret(int32 offset) 3719 { 3720 float lineHeight; 3721 BPoint caretPoint = PointAt(offset, &lineHeight); 3722 caretPoint.x = min_c(caretPoint.x, fTextRect.right); 3723 3724 BRect caretRect; 3725 caretRect.left = caretRect.right = caretPoint.x; 3726 caretRect.top = caretPoint.y; 3727 caretRect.bottom = caretPoint.y + lineHeight - 1; 3728 3729 InvertRect(caretRect); 3730 } 3731 3732 3733 inline void 3734 BTextView::_ShowCaret() 3735 { 3736 if (!fCaretVisible) 3737 _InvertCaret(); 3738 } 3739 3740 3741 inline void 3742 BTextView::_HideCaret() 3743 { 3744 if (fCaretVisible) 3745 _InvertCaret(); 3746 } 3747 3748 3749 /*! \brief Inverts the blinking caret status. 3750 Hides the caret if it is being shown, and if it's hidden, shows it. 3751 */ 3752 void 3753 BTextView::_InvertCaret() 3754 { 3755 _DrawCaret(fSelStart); 3756 fCaretVisible = !fCaretVisible; 3757 fCaretTime = system_time(); 3758 } 3759 3760 3761 /*! \brief Place the dragging caret at the given offset. 3762 \param offset The offset (zero based within the object's text) where to place 3763 the dragging caret. If it's -1, hide the caret. 3764 */ 3765 void 3766 BTextView::_DragCaret(int32 offset) 3767 { 3768 // does the caret need to move? 3769 if (offset == fDragOffset) 3770 return; 3771 3772 // hide the previous drag caret 3773 if (fDragOffset != -1) 3774 _DrawCaret(fDragOffset); 3775 3776 // do we have a new location? 3777 if (offset != -1) { 3778 if (fActive) { 3779 // ignore if offset is within active selection 3780 if (offset >= fSelStart && offset <= fSelEnd) { 3781 fDragOffset = -1; 3782 return; 3783 } 3784 } 3785 3786 _DrawCaret(offset); 3787 } 3788 3789 fDragOffset = offset; 3790 } 3791 3792 3793 void 3794 BTextView::_StopMouseTracking() 3795 { 3796 delete fTrackingMouse; 3797 fTrackingMouse = NULL; 3798 } 3799 3800 3801 bool 3802 BTextView::_PerformMouseUp(BPoint where) 3803 { 3804 if (fTrackingMouse == NULL) 3805 return false; 3806 3807 if (fTrackingMouse->selectionRect.IsValid() 3808 && fTrackingMouse->selectionRect.Contains(where)) 3809 Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset); 3810 3811 _StopMouseTracking(); 3812 3813 return true; 3814 } 3815 3816 3817 bool 3818 BTextView::_PerformMouseMoved(BPoint where, uint32 code) 3819 { 3820 fWhere = where; 3821 3822 if (fTrackingMouse == NULL) 3823 return false; 3824 3825 if (fTrackingMouse->selectionRect.IsValid() 3826 && fTrackingMouse->selectionRect.Contains(where)) { 3827 _StopMouseTracking(); 3828 _InitiateDrag(); 3829 return true; 3830 } 3831 3832 int32 oldOffset = fTrackingMouse->anchor; 3833 int32 currentOffset = OffsetAt(where); 3834 3835 switch (fClickCount) { 3836 case 0: 3837 // triple click, select line by line 3838 fTrackingMouse->selStart = (*fLines)[LineAt(fTrackingMouse->selStart)]->offset; 3839 fTrackingMouse->selEnd = (*fLines)[LineAt(fTrackingMouse->selEnd) + 1]->offset; 3840 break; 3841 3842 case 2: 3843 // double click, select word by word 3844 FindWord(currentOffset, &fTrackingMouse->selStart, &fTrackingMouse->selEnd); 3845 break; 3846 3847 default: 3848 // new click, select char by char 3849 if (oldOffset < currentOffset) { 3850 fTrackingMouse->selStart = oldOffset; 3851 fTrackingMouse->selEnd = currentOffset; 3852 } else { 3853 fTrackingMouse->selStart = currentOffset; 3854 fTrackingMouse->selEnd = oldOffset; 3855 } 3856 break; 3857 } 3858 3859 Select(fTrackingMouse->selStart, fTrackingMouse->selEnd); 3860 _TrackMouse(where, NULL); 3861 3862 return true; 3863 } 3864 3865 3866 /*! \brief Tracks the mouse position, doing special actions like changing the 3867 view cursor. 3868 \param where The point where the mouse has moved. 3869 \param message The dragging message, if there is any. 3870 \param force Passed as second parameter of SetViewCursor() 3871 */ 3872 void 3873 BTextView::_TrackMouse(BPoint where, const BMessage *message, bool force) 3874 { 3875 BRegion textRegion; 3876 GetTextRegion(fSelStart, fSelEnd, &textRegion); 3877 3878 if (message && AcceptsDrop(message)) 3879 _TrackDrag(where); 3880 else if ((fSelectable || fEditable) && !textRegion.Contains(where)) 3881 SetViewCursor(B_CURSOR_I_BEAM, force); 3882 else 3883 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, force); 3884 } 3885 3886 3887 /*! \brief Tracks the mouse position when the user is dragging some data. 3888 \param where The point where the mouse has moved. 3889 */ 3890 void 3891 BTextView::_TrackDrag(BPoint where) 3892 { 3893 CALLED(); 3894 if (Bounds().Contains(where)) 3895 _DragCaret(OffsetAt(where)); 3896 } 3897 3898 3899 /*! \brief Function called to initiate a drag operation. 3900 */ 3901 void 3902 BTextView::_InitiateDrag() 3903 { 3904 BMessage *dragMessage = new BMessage(B_MIME_DATA); 3905 BBitmap *dragBitmap = NULL; 3906 BPoint bitmapPoint; 3907 BHandler *dragHandler = NULL; 3908 3909 GetDragParameters(dragMessage, &dragBitmap, &bitmapPoint, &dragHandler); 3910 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 3911 3912 if (dragBitmap != NULL) 3913 DragMessage(dragMessage, dragBitmap, bitmapPoint, dragHandler); 3914 else { 3915 BRegion region; 3916 GetTextRegion(fSelStart, fSelEnd, ®ion); 3917 BRect bounds = Bounds(); 3918 BRect dragRect = region.Frame(); 3919 if (!bounds.Contains(dragRect)) 3920 dragRect = bounds & dragRect; 3921 3922 DragMessage(dragMessage, dragRect, dragHandler); 3923 } 3924 3925 BMessenger messenger(this); 3926 BMessage message(_DISPOSE_DRAG_); 3927 fDragRunner = new (nothrow) BMessageRunner(messenger, &message, 100000); 3928 } 3929 3930 3931 /*! \brief Called when some data is dropped on the view. 3932 \param inMessage The message which has been dropped. 3933 \param where The location where the message has been dropped. 3934 \param offset ? 3935 \return \c true if the message was handled, \c false if not. 3936 */ 3937 bool 3938 BTextView::_MessageDropped(BMessage *inMessage, BPoint where, BPoint offset) 3939 { 3940 ASSERT(inMessage); 3941 3942 void *from = NULL; 3943 bool internalDrop = false; 3944 if (inMessage->FindPointer("be:originator", &from) == B_OK 3945 && from == this && fSelEnd != fSelStart) 3946 internalDrop = true; 3947 3948 _DragCaret(-1); 3949 3950 delete fDragRunner; 3951 fDragRunner = NULL; 3952 3953 _TrackMouse(where, NULL); 3954 3955 // are we sure we like this message? 3956 if (!AcceptsDrop(inMessage)) 3957 return false; 3958 3959 int32 dropOffset = OffsetAt(where); 3960 if (dropOffset > TextLength()) 3961 dropOffset = TextLength(); 3962 3963 // if this view initiated the drag, move instead of copy 3964 if (internalDrop) { 3965 // dropping onto itself? 3966 if (dropOffset >= fSelStart && dropOffset <= fSelEnd) 3967 return true; 3968 } 3969 3970 ssize_t dataLen = 0; 3971 const char *text = NULL; 3972 if (inMessage->FindData("text/plain", B_MIME_TYPE, (const void **)&text, &dataLen) == B_OK) { 3973 text_run_array *runArray = NULL; 3974 ssize_t runLen = 0; 3975 if (fStylable) 3976 inMessage->FindData("application/x-vnd.Be-text_run_array", B_MIME_TYPE, 3977 (const void **)&runArray, &runLen); 3978 3979 if (fUndo) { 3980 delete fUndo; 3981 fUndo = new _BDropUndoBuffer_(this, text, dataLen, runArray, runLen, dropOffset, internalDrop); 3982 } 3983 3984 if (internalDrop) { 3985 if (dropOffset > fSelEnd) 3986 dropOffset -= dataLen; 3987 Delete(); 3988 } 3989 3990 Insert(dropOffset, text, dataLen, runArray); 3991 } 3992 3993 return true; 3994 } 3995 3996 3997 void 3998 BTextView::_PerformAutoScrolling() 3999 { 4000 // Scroll the view a bit if mouse is outside the view bounds 4001 BRect bounds = Bounds(); 4002 BPoint scrollBy; 4003 4004 BPoint constraint = fWhere; 4005 constraint.ConstrainTo(bounds); 4006 // Scroll char by char horizontally 4007 // TODO: Check how BeOS R5 behaves 4008 float value = _StyledWidthUTF8Safe(OffsetAt(constraint), 1); 4009 if (fWhere.x > bounds.right) { 4010 if (bounds.right + value <= fTextRect.Width()) 4011 scrollBy.x = value; 4012 } else if (fWhere.x < bounds.left) { 4013 if (bounds.left - value >= 0) 4014 scrollBy.x = -value; 4015 } 4016 4017 float lineHeight = 0; 4018 float vertDiff = 0; 4019 if (fWhere.y > bounds.bottom) { 4020 lineHeight = LineHeight(LineAt(bounds.LeftBottom())); 4021 vertDiff = fWhere.y - bounds.bottom; 4022 } else if (fWhere.y < bounds.top) { 4023 lineHeight = LineHeight(LineAt(bounds.LeftTop())); 4024 vertDiff = fWhere.y - bounds.top; // negative value 4025 } 4026 4027 // Always scroll vertically line by line or by multiples of that 4028 // based on the distance of the cursor from the border of the view 4029 // TODO: Refine this, I can't even remember how beos works here 4030 scrollBy.y = lineHeight > 0 ? lineHeight * (int32)(floorf(vertDiff) / lineHeight) : 0; 4031 4032 if (bounds.bottom + scrollBy.y > fTextRect.Height()) 4033 scrollBy.y = fTextRect.Height() - bounds.bottom; 4034 else if (bounds.top + scrollBy.y < 0) 4035 scrollBy.y = -bounds.top; 4036 4037 if (scrollBy != B_ORIGIN) 4038 ScrollBy(scrollBy.x, scrollBy.y); 4039 } 4040 4041 4042 /*! \brief Updates the scrollbars associated with the object (if any). 4043 */ 4044 void 4045 BTextView::_UpdateScrollbars() 4046 { 4047 BRect bounds(Bounds()); 4048 BScrollBar *horizontalScrollBar = ScrollBar(B_HORIZONTAL); 4049 BScrollBar *verticalScrollBar = ScrollBar(B_VERTICAL); 4050 4051 // do we have a horizontal scroll bar? 4052 if (horizontalScrollBar != NULL) { 4053 long viewWidth = bounds.IntegerWidth(); 4054 long dataWidth = fTextRect.IntegerWidth(); 4055 dataWidth += (long)ceilf(fTextRect.left) + 1; 4056 4057 long maxRange = dataWidth - viewWidth; 4058 maxRange = max_c(maxRange, 0); 4059 4060 horizontalScrollBar->SetRange(0, (float)maxRange); 4061 horizontalScrollBar->SetProportion((float)viewWidth / (float)dataWidth); 4062 horizontalScrollBar->SetSteps(10, dataWidth / 10); 4063 } 4064 4065 // how about a vertical scroll bar? 4066 if (verticalScrollBar != NULL) { 4067 long viewHeight = bounds.IntegerHeight(); 4068 long dataHeight = fTextRect.IntegerHeight(); 4069 dataHeight += (long)ceilf(fTextRect.top) + 1; 4070 4071 long maxRange = dataHeight - viewHeight; 4072 maxRange = max_c(maxRange, 0); 4073 4074 verticalScrollBar->SetRange(0, maxRange); 4075 verticalScrollBar->SetProportion((float)viewHeight / (float)dataHeight); 4076 verticalScrollBar->SetSteps(12, viewHeight); 4077 } 4078 } 4079 4080 4081 /*! \brief Autoresizes the view to fit the contained text. 4082 */ 4083 void 4084 BTextView::_AutoResize(bool redraw) 4085 { 4086 if (fResizable) { 4087 float oldWidth = Bounds().Width() + 1; 4088 float newWidth = 3; 4089 for (int32 i = 0; i < CountLines(); i++) 4090 newWidth += LineWidth(i); 4091 4092 BRect newRect(0, 0, ceilf(newWidth), ceilf(LineHeight(0)) + 2); 4093 4094 if (fContainerView != NULL) { 4095 fContainerView->ResizeTo(newRect.Width() + 1, newRect.Height()); 4096 if (fAlignment == B_ALIGN_CENTER) 4097 fContainerView->MoveBy(ceilf((oldWidth - (newRect.Width() + 1)) / 2), 0); 4098 else if (fAlignment == B_ALIGN_RIGHT) 4099 fContainerView->MoveBy(oldWidth - (newRect.Width() + 1), 0); 4100 fContainerView->Invalidate(); 4101 } 4102 4103 fTextRect = newRect.InsetBySelf(0, 1); 4104 4105 if (redraw) 4106 _DrawLines(0, 0); 4107 4108 // Erase the old text (TODO: Might not work for alignments different than B_ALIGN_LEFT) 4109 SetLowColor(ViewColor()); 4110 FillRect(BRect(fTextRect.right, fTextRect.top, Bounds().right, fTextRect.bottom), B_SOLID_LOW); 4111 } 4112 } 4113 4114 4115 /*! \brief Creates a new offscreen BBitmap with an associated BView. 4116 param padding Padding (?) 4117 4118 Creates an offscreen BBitmap which will be used to draw. 4119 */ 4120 void 4121 BTextView::_NewOffscreen(float padding) 4122 { 4123 if (fOffscreen != NULL) 4124 _DeleteOffscreen(); 4125 4126 #if USE_DOUBLEBUFFERING 4127 BRect bitmapRect(0, 0, fTextRect.Width() + padding, fTextRect.Height()); 4128 fOffscreen = new BBitmap(bitmapRect, fColorSpace, true, false); 4129 if (fOffscreen != NULL && fOffscreen->Lock()) { 4130 BView *bufferView = new BView(bitmapRect, "drawing view", 0, 0); 4131 fOffscreen->AddChild(bufferView); 4132 fOffscreen->Unlock(); 4133 } 4134 #endif 4135 } 4136 4137 4138 /*! \brief Deletes the textview's offscreen bitmap, if any. 4139 */ 4140 void 4141 BTextView::_DeleteOffscreen() 4142 { 4143 if (fOffscreen != NULL && fOffscreen->Lock()) { 4144 delete fOffscreen; 4145 fOffscreen = NULL; 4146 } 4147 } 4148 4149 4150 /*! \brief Creates a new offscreen bitmap, highlight the selection, and set the 4151 cursor to B_CURSOR_I_BEAM. 4152 */ 4153 void 4154 BTextView::_Activate() 4155 { 4156 fActive = true; 4157 4158 // Create a new offscreen BBitmap 4159 _NewOffscreen(); 4160 4161 if (fSelStart != fSelEnd) { 4162 if (fSelectable) 4163 Highlight(fSelStart, fSelEnd); 4164 } else { 4165 if (fEditable) 4166 _ShowCaret(); 4167 } 4168 4169 BPoint where; 4170 ulong buttons; 4171 GetMouse(&where, &buttons, false); 4172 if (Bounds().Contains(where)) 4173 _TrackMouse(where, NULL); 4174 } 4175 4176 4177 /*! \brief Unhilights the selection, set the cursor to B_CURSOR_SYSTEM_DEFAULT. 4178 */ 4179 void 4180 BTextView::_Deactivate() 4181 { 4182 fActive = false; 4183 4184 _CancelInputMethod(); 4185 _DeleteOffscreen(); 4186 4187 if (fSelStart != fSelEnd) { 4188 if (fSelectable) 4189 Highlight(fSelStart, fSelEnd); 4190 } else 4191 _HideCaret(); 4192 4193 BPoint where; 4194 ulong buttons; 4195 GetMouse(&where, &buttons); 4196 if (Bounds().Contains(where)) 4197 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 4198 } 4199 4200 4201 /*! \brief Changes the passed font to be displayable by the object. 4202 \param font A pointer to the font to normalize. 4203 4204 Set font rotation to 0, removes any font flag, set font spacing 4205 to \c B_BITMAP_SPACING and font encoding to \c B_UNICODE_UTF8 4206 */ 4207 void 4208 BTextView::_NormalizeFont(BFont *font) 4209 { 4210 if (font) { 4211 font->SetRotation(0.0f); 4212 font->SetFlags(0); 4213 font->SetSpacing(B_BITMAP_SPACING); 4214 font->SetEncoding(B_UNICODE_UTF8); 4215 } 4216 } 4217 4218 4219 void 4220 BTextView::_SetRunArray(int32 startOffset, int32 endOffset, 4221 const text_run_array *inRuns) 4222 { 4223 if (startOffset > endOffset) 4224 return; 4225 4226 const int32 textLength = fText->Length(); 4227 4228 // pin offsets at reasonable values 4229 if (startOffset < 0) 4230 startOffset = 0; 4231 else if (startOffset > textLength) 4232 startOffset = textLength; 4233 4234 if (endOffset < 0) 4235 endOffset = 0; 4236 else if (endOffset > textLength) 4237 endOffset = textLength; 4238 4239 const int32 numStyles = inRuns->count; 4240 if (numStyles > 0) { 4241 const text_run *theRun = &inRuns->runs[0]; 4242 for (int32 index = 0; index < numStyles; index++) { 4243 int32 fromOffset = theRun->offset + startOffset; 4244 int32 toOffset = endOffset; 4245 if (index + 1 < numStyles) { 4246 toOffset = (theRun + 1)->offset + startOffset; 4247 toOffset = (toOffset > endOffset) ? endOffset : toOffset; 4248 } 4249 4250 BFont font = theRun->font; 4251 _NormalizeFont(&font); 4252 fStyles->SetStyleRange(fromOffset, toOffset, textLength, 4253 B_FONT_ALL, &theRun->font, &theRun->color); 4254 4255 theRun++; 4256 } 4257 fStyles->InvalidateNullStyle(); 4258 } 4259 } 4260 4261 4262 /*! \brief Returns a value which tells if the given character is a separator 4263 character or not. 4264 \param offset The offset where the wanted character can be found. 4265 \return A value which represents the character's classification. 4266 */ 4267 uint32 4268 BTextView::_CharClassification(int32 offset) const 4269 { 4270 // TODO:Should check against a list of characters containing also 4271 // japanese word breakers. 4272 // And what about other languages ? Isn't there a better way to check 4273 // for separator characters ? 4274 // Andrew suggested to have a look at UnicodeBlockObject.h 4275 switch (fText->RealCharAt(offset)) { 4276 case B_SPACE: 4277 case '_': 4278 case '.': 4279 case '\0': 4280 case B_TAB: 4281 case B_ENTER: 4282 case '&': 4283 case '*': 4284 case '+': 4285 case '-': 4286 case '/': 4287 case '<': 4288 case '=': 4289 case '>': 4290 case '\\': 4291 case '^': 4292 case '|': 4293 return B_SEPARATOR_CHARACTER; 4294 default: 4295 return B_OTHER_CHARACTER; 4296 } 4297 } 4298 4299 4300 /*! \brief Returns the offset of the next UTF8 character within the BTextView's text. 4301 \param offset The offset where to start looking. 4302 \return The offset of the next UTF8 character. 4303 */ 4304 int32 4305 BTextView::_NextInitialByte(int32 offset) const 4306 { 4307 int32 textLength = TextLength(); 4308 if (offset >= textLength) 4309 return textLength; 4310 4311 for (++offset; (ByteAt(offset) & 0xC0) == 0x80; ++offset) 4312 ; 4313 4314 return offset; 4315 } 4316 4317 4318 /*! \brief Returns the offset of the previous UTF8 character within the BTextView's text. 4319 \param offset The offset where to start looking. 4320 \return The offset of the previous UTF8 character. 4321 */ 4322 int32 4323 BTextView::_PreviousInitialByte(int32 offset) const 4324 { 4325 if (offset <= 0) 4326 return 0; 4327 4328 int32 count = 6; 4329 4330 for (--offset; offset > 0 && count; --offset, --count) { 4331 if ((ByteAt(offset) & 0xC0) != 0x80) 4332 break; 4333 } 4334 4335 return count ? offset : 0; 4336 } 4337 4338 4339 bool 4340 BTextView::_GetProperty(BMessage *specifier, int32 form, const char *property, BMessage *reply) 4341 { 4342 CALLED(); 4343 if (strcmp(property, "selection") == 0) { 4344 reply->what = B_REPLY; 4345 reply->AddInt32("result", fSelStart); 4346 reply->AddInt32("result", fSelEnd); 4347 reply->AddInt32("error", B_OK); 4348 4349 return true; 4350 } else if (strcmp(property, "Text") == 0) { 4351 if (IsTypingHidden()) { 4352 // Do not allow stealing passwords via scripting 4353 beep(); 4354 return false; 4355 } 4356 4357 int32 index, range; 4358 specifier->FindInt32("index", &index); 4359 specifier->FindInt32("range", &range); 4360 4361 char *buffer = new char[range + 1]; 4362 GetText(index, range, buffer); 4363 4364 reply->what = B_REPLY; 4365 reply->AddString("result", buffer); 4366 delete buffer; 4367 reply->AddInt32("error", B_OK); 4368 4369 return true; 4370 } else if (strcmp(property, "text_run_array") == 0) 4371 return false; 4372 4373 return false; 4374 } 4375 4376 4377 bool 4378 BTextView::_SetProperty(BMessage *specifier, int32 form, const char *property, 4379 BMessage *reply) 4380 { 4381 CALLED(); 4382 if (strcmp(property, "selection") == 0) { 4383 int32 index, range; 4384 4385 specifier->FindInt32("index", &index); 4386 specifier->FindInt32("range", &range); 4387 4388 Select(index, index + range); 4389 4390 reply->what = B_REPLY; 4391 reply->AddInt32("error", B_OK); 4392 4393 return true; 4394 } else if (strcmp(property, "Text") == 0) { 4395 int32 index, range; 4396 specifier->FindInt32("index", &index); 4397 specifier->FindInt32("range", &range); 4398 4399 const char *buffer = NULL; 4400 if (specifier->FindString("data", &buffer) == B_OK) 4401 InsertText(buffer, range, index, NULL); 4402 else 4403 DeleteText(index, range); 4404 4405 reply->what = B_REPLY; 4406 reply->AddInt32("error", B_OK); 4407 4408 return true; 4409 } else if (strcmp(property, "text_run_array") == 0) 4410 return false; 4411 4412 return false; 4413 } 4414 4415 4416 bool 4417 BTextView::_CountProperties(BMessage *specifier, int32 form, 4418 const char *property, BMessage *reply) 4419 { 4420 CALLED(); 4421 if (strcmp(property, "Text") == 0) { 4422 reply->what = B_REPLY; 4423 reply->AddInt32("result", TextLength()); 4424 reply->AddInt32("error", B_OK); 4425 return true; 4426 } 4427 4428 return false; 4429 } 4430 4431 4432 /*! \brief Called when the object receives a B_INPUT_METHOD_CHANGED message. 4433 \param message A B_INPUT_METHOD_CHANGED message. 4434 */ 4435 void 4436 BTextView::_HandleInputMethodChanged(BMessage *message) 4437 { 4438 // TODO: block input if not editable (Andrew) 4439 ASSERT(fInline != NULL); 4440 4441 const char *string = NULL; 4442 if (message->FindString("be:string", &string) < B_OK || string == NULL) 4443 return; 4444 4445 _HideCaret(); 4446 4447 be_app->ObscureCursor(); 4448 4449 // If we find the "be:confirmed" boolean (and the boolean is true), 4450 // it means it's over for now, so the current _BInlineInput_ object 4451 // should become inactive. We will probably receive a B_INPUT_METHOD_STOPPED 4452 // message after this one. 4453 bool confirmed; 4454 if (message->FindBool("be:confirmed", &confirmed) != B_OK) 4455 confirmed = false; 4456 4457 // Delete the previously inserted text (if any) 4458 if (fInline->IsActive()) { 4459 const int32 oldOffset = fInline->Offset(); 4460 DeleteText(oldOffset, oldOffset + fInline->Length()); 4461 if (confirmed) 4462 fInline->SetActive(false); 4463 fClickOffset = fSelStart = fSelEnd = oldOffset; 4464 } 4465 4466 const int32 stringLen = strlen(string); 4467 4468 fInline->SetOffset(fSelStart); 4469 fInline->SetLength(stringLen); 4470 fInline->ResetClauses(); 4471 4472 if (!confirmed && !fInline->IsActive()) 4473 fInline->SetActive(true); 4474 4475 // Get the clauses, and pass them to the _BInlineInput_ object 4476 // TODO: Find out if what we did it's ok, currently we don't consider clauses 4477 // at all, while the bebook says we should; though the visual effect we obtained 4478 // seems correct. Weird. 4479 int32 clauseCount = 0; 4480 int32 clauseStart; 4481 int32 clauseEnd; 4482 while (message->FindInt32("be:clause_start", clauseCount, &clauseStart) == B_OK && 4483 message->FindInt32("be:clause_end", clauseCount, &clauseEnd) == B_OK) { 4484 if (!fInline->AddClause(clauseStart, clauseEnd)) 4485 break; 4486 clauseCount++; 4487 } 4488 4489 int32 selectionStart = 0; 4490 int32 selectionEnd = 0; 4491 message->FindInt32("be:selection", 0, &selectionStart); 4492 message->FindInt32("be:selection", 1, &selectionEnd); 4493 4494 fInline->SetSelectionOffset(selectionStart); 4495 fInline->SetSelectionLength(selectionEnd - selectionStart); 4496 4497 const int32 inlineOffset = fInline->Offset(); 4498 InsertText(string, stringLen, fSelStart, NULL); 4499 fSelStart += stringLen; 4500 fClickOffset = fSelEnd = fSelStart; 4501 4502 _Refresh(inlineOffset, fSelEnd, true, true); 4503 4504 _ShowCaret(); 4505 } 4506 4507 4508 /*! \brief Called when the object receives a B_INPUT_METHOD_LOCATION_REQUEST 4509 message. 4510 */ 4511 void 4512 BTextView::_HandleInputMethodLocationRequest() 4513 { 4514 ASSERT(fInline != NULL); 4515 4516 int32 offset = fInline->Offset(); 4517 const int32 limit = offset + fInline->Length(); 4518 4519 BMessage message(B_INPUT_METHOD_EVENT); 4520 message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST); 4521 4522 // Add the location of the UTF8 characters 4523 while (offset < limit) { 4524 float height; 4525 BPoint where = PointAt(offset, &height); 4526 ConvertToScreen(&where); 4527 4528 message.AddPoint("be:location_reply", where); 4529 message.AddFloat("be:height_reply", height); 4530 4531 offset = _NextInitialByte(offset); 4532 } 4533 4534 fInline->Method()->SendMessage(&message); 4535 } 4536 4537 4538 /*! \brief Tells the input server method addon to stop the current transaction. 4539 */ 4540 void 4541 BTextView::_CancelInputMethod() 4542 { 4543 if (!fInline) 4544 return; 4545 4546 _BInlineInput_ *inlineInput = fInline; 4547 fInline = NULL; 4548 4549 if (inlineInput->IsActive() && Window()) 4550 _Refresh(inlineInput->Offset(), fText->Length() - inlineInput->Offset(), true, false); 4551 4552 BMessage message(B_INPUT_METHOD_EVENT); 4553 message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED); 4554 inlineInput->Method()->SendMessage(&message); 4555 4556 delete inlineInput; 4557 } 4558 4559 4560 /*! \brief Locks the static _BWidthBuffer_ object to be able to access it safely. 4561 */ 4562 void 4563 BTextView::LockWidthBuffer() 4564 { 4565 if (atomic_add(&sWidthAtom, 1) > 0) { 4566 while (acquire_sem(sWidthSem) == B_INTERRUPTED) 4567 ; 4568 } 4569 } 4570 4571 4572 /*! \brief Unlocks the static _BWidthBuffer_ object. 4573 */ 4574 void 4575 BTextView::UnlockWidthBuffer() 4576 { 4577 if (atomic_add(&sWidthAtom, -1) > 1) 4578 release_sem(sWidthSem); 4579 } 4580 4581 4582 // _BTextTrackState_ 4583 _BTextTrackState_::_BTextTrackState_(BMessenger messenger) 4584 : 4585 clickOffset(0), 4586 shiftDown(false), 4587 anchor(0), 4588 selStart(0), 4589 selEnd(0), 4590 fRunner(NULL) 4591 { 4592 BMessage message(_PING_); 4593 fRunner = new (nothrow) BMessageRunner(messenger, &message, 300000); 4594 } 4595 4596 4597 _BTextTrackState_::~_BTextTrackState_() 4598 { 4599 delete fRunner; 4600 } 4601 4602 4603 void 4604 _BTextTrackState_::SimulateMouseMovement(BTextView *textView) 4605 { 4606 BPoint where; 4607 ulong buttons; 4608 // When the mouse cursor is still and outside the textview, 4609 // no B_MOUSE_MOVED message are sent, obviously. But scrolling 4610 // has to work neverthless, so we "fake" a MouseMoved() call here. 4611 textView->GetMouse(&where, &buttons); 4612 textView->_PerformMouseMoved(where, B_INSIDE_VIEW); 4613 } 4614 4615 4616