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