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