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