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