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