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