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