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