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