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