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