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