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