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