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