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