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