2 * Copyright 2001-2014 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
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
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
23 // - Double buffering doesn't work well (disabled by default)
33 #include <Application.h>
36 #include <Clipboard.h>
40 #include <LayoutBuilder.h>
41 #include <LayoutUtils.h>
42 #include <MessageRunner.h>
44 #include <PopUpMenu.h>
45 #include <PropertyInfo.h>
47 #include <ScrollBar.h>
48 #include <SystemCatalog.h>
51 #include <binary_compatibility/Interface.h>
53 #include "InlineInput.h"
54 #include "LineBuffer.h"
55 #include "StyleBuffer.h"
56 #include "TextGapBuffer.h"
57 #include "UndoBuffer.h"
58 #include "WidthBuffer.h"
62 using BPrivate::gSystemCatalog
;
65 #undef B_TRANSLATION_CONTEXT
66 #define B_TRANSLATION_CONTEXT "TextView"
69 #define TRANSLATE(str) \
70 gSystemCatalog.GetString(B_TRANSLATE_MARK(str), "TextView")
74 //#define TRACE_TEXT_VIEW
75 #ifdef TRACE_TEXT_VIEW
76 # include <FunctionTracer.h>
77 static int32 sFunctionDepth
= -1;
78 # define CALLED(x...) FunctionTracer _ft("BTextView", __FUNCTION__, \
80 # define TRACE(x...) { BString _to; \
81 _to.Append(' ', (sFunctionDepth + 1) * 2); \
82 printf("%s", _to.String()); printf(x); }
89 #define USE_WIDTHBUFFER 1
90 #define USE_DOUBLEBUFFERING 0
93 struct flattened_text_run
{
98 float shear
; // typically 90.0
99 uint16 face
; // typically 0
103 uint8 alpha
; // 255 == opaque
104 uint16 _reserved_
; // 0
107 struct flattened_text_run_array
{
111 flattened_text_run styles
[1];
114 static const uint32 kFlattenedTextRunArrayMagic
= 'Ali!';
115 static const uint32 kFlattenedTextRunArrayVersion
= 0;
120 CHAR_CLASS_WHITESPACE
,
121 CHAR_CLASS_GRAPHICAL
,
123 CHAR_CLASS_PUNCTUATION
,
124 CHAR_CLASS_PARENS_OPEN
,
125 CHAR_CLASS_PARENS_CLOSE
,
126 CHAR_CLASS_END_OF_TEXT
130 class BTextView::TextTrackState
{
132 TextTrackState(BMessenger messenger
);
135 void SimulateMouseMovement(BTextView
* view
);
148 BMessageRunner
* fRunner
;
152 struct BTextView::LayoutData
{
162 void UpdateInsets(const BRect
& bounds
, const BRect
& textRect
)
164 // we disallow negative insets, as they would cause parts of the
166 leftInset
= textRect
.left
>= bounds
.left
167 ? textRect
.left
- bounds
.left
169 topInset
= textRect
.top
>= bounds
.top
170 ? textRect
.top
- bounds
.top
172 rightInset
= bounds
.right
>= textRect
.right
173 ? bounds
.right
- textRect
.right
175 bottomInset
= bounds
.bottom
>= textRect
.bottom
176 ? bounds
.bottom
- textRect
.bottom
191 static const rgb_color kBlackColor
= { 0, 0, 0, 255 };
192 static const rgb_color kBlueInputColor
= { 152, 203, 255, 255 };
193 static const rgb_color kRedInputColor
= { 255, 152, 152, 255 };
195 static const float kHorizontalScrollBarStep
= 10.0;
196 static const float kVerticalScrollBarStep
= 12.0;
198 static const int32 kMsgNavigateArrow
= '_NvA';
199 static const int32 kMsgNavigatePage
= '_NvP';
202 static property_info sPropertyList
[] = {
205 { B_GET_PROPERTY
, 0 },
206 { B_DIRECT_SPECIFIER
, 0 },
207 "Returns the current selection.", 0,
212 { B_SET_PROPERTY
, 0 },
213 { B_DIRECT_SPECIFIER
, 0 },
214 "Sets the current selection.", 0,
219 { B_COUNT_PROPERTIES
, 0 },
220 { B_DIRECT_SPECIFIER
, 0 },
221 "Returns the length of the text in bytes.", 0,
226 { B_GET_PROPERTY
, 0 },
227 { B_RANGE_SPECIFIER
, B_REVERSE_RANGE_SPECIFIER
, 0 },
228 "Returns the text in the specified range in the BTextView.", 0,
233 { B_SET_PROPERTY
, 0 },
234 { B_RANGE_SPECIFIER
, B_REVERSE_RANGE_SPECIFIER
, 0 },
235 "Removes or inserts text into the specified range in the BTextView.", 0,
240 { B_GET_PROPERTY
, 0 },
241 { B_RANGE_SPECIFIER
, B_REVERSE_RANGE_SPECIFIER
, 0 },
242 "Returns the style information for the text in the specified range in "
248 { B_SET_PROPERTY
, 0 },
249 { B_RANGE_SPECIFIER
, B_REVERSE_RANGE_SPECIFIER
, 0 },
250 "Sets the style information for the text in the specified range in the "
258 BTextView::BTextView(BRect frame
, const char* name
, BRect textRect
,
259 uint32 resizeMask
, uint32 flags
)
261 BView(frame
, name
, resizeMask
,
262 flags
| B_FRAME_EVENTS
| B_PULSE_NEEDED
| B_INPUT_METHOD_AWARE
)
264 _InitObject(textRect
, NULL
, NULL
);
268 BTextView::BTextView(BRect frame
, const char* name
, BRect textRect
,
269 const BFont
* initialFont
, const rgb_color
* initialColor
,
270 uint32 resizeMask
, uint32 flags
)
272 BView(frame
, name
, resizeMask
,
273 flags
| B_FRAME_EVENTS
| B_PULSE_NEEDED
| B_INPUT_METHOD_AWARE
)
275 _InitObject(textRect
, initialFont
, initialColor
);
279 BTextView::BTextView(const char* name
, uint32 flags
)
282 flags
| B_FRAME_EVENTS
| B_PULSE_NEEDED
| B_INPUT_METHOD_AWARE
)
284 _InitObject(Bounds(), NULL
, NULL
);
288 BTextView::BTextView(const char* name
, const BFont
* initialFont
,
289 const rgb_color
* initialColor
, uint32 flags
)
292 flags
| B_FRAME_EVENTS
| B_PULSE_NEEDED
| B_INPUT_METHOD_AWARE
)
294 _InitObject(Bounds(), initialFont
, initialColor
);
298 BTextView::BTextView(BMessage
* archive
)
305 if (archive
->FindRect("_trect", &rect
) != B_OK
)
306 rect
.Set(0, 0, 0, 0);
308 _InitObject(rect
, NULL
, NULL
);
310 const char* text
= NULL
;
311 if (archive
->FindString("_text", &text
) == B_OK
)
315 if (archive
->FindInt32("_align", &flag
) == B_OK
)
316 SetAlignment((alignment
)flag
);
320 if (archive
->FindFloat("_tab", &value
) == B_OK
)
323 if (archive
->FindInt32("_col_sp", &flag
) == B_OK
)
324 SetColorSpace((color_space
)flag
);
326 if (archive
->FindInt32("_max", &flag
) == B_OK
)
329 if (archive
->FindInt32("_sel", &flag
) == B_OK
&&
330 archive
->FindInt32("_sel", &flag2
) == B_OK
)
335 if (archive
->FindBool("_stylable", &toggle
) == B_OK
)
338 if (archive
->FindBool("_auto_in", &toggle
) == B_OK
)
339 SetAutoindent(toggle
);
341 if (archive
->FindBool("_wrap", &toggle
) == B_OK
)
344 if (archive
->FindBool("_nsel", &toggle
) == B_OK
)
345 MakeSelectable(!toggle
);
347 if (archive
->FindBool("_nedit", &toggle
) == B_OK
)
348 MakeEditable(!toggle
);
350 ssize_t disallowedCount
= 0;
351 const int32
* disallowedChars
= NULL
;
352 if (archive
->FindData("_dis_ch", B_RAW_TYPE
,
353 (const void**)&disallowedChars
, &disallowedCount
) == B_OK
) {
355 fDisallowedChars
= new BList
;
356 disallowedCount
/= sizeof(int32
);
357 for (int32 x
= 0; x
< disallowedCount
; x
++) {
358 fDisallowedChars
->AddItem(
359 reinterpret_cast<void*>(disallowedChars
[x
]));
364 const void* flattenedRun
= NULL
;
366 if (archive
->FindData("_runs", B_RAW_TYPE
, &flattenedRun
, &runSize
)
368 text_run_array
* runArray
= UnflattenRunArray(flattenedRun
,
371 SetRunArray(0, TextLength(), runArray
);
372 FreeRunArray(runArray
);
378 BTextView::~BTextView()
380 _CancelInputMethod();
381 _StopMouseTracking();
387 delete fDisallowedChars
;
396 BTextView::Instantiate(BMessage
* archive
)
399 if (validate_instantiation(archive
, "BTextView"))
400 return new BTextView(archive
);
406 BTextView::Archive(BMessage
* data
, bool deep
) const
409 status_t err
= BView::Archive(data
, deep
);
411 err
= data
->AddString("_text", Text());
413 err
= data
->AddInt32("_align", fAlignment
);
415 err
= data
->AddFloat("_tab", fTabWidth
);
417 err
= data
->AddInt32("_col_sp", fColorSpace
);
419 err
= data
->AddRect("_trect", fTextRect
);
421 err
= data
->AddInt32("_max", fMaxBytes
);
423 err
= data
->AddInt32("_sel", fSelStart
);
425 err
= data
->AddInt32("_sel", fSelEnd
);
427 err
= data
->AddBool("_stylable", fStylable
);
429 err
= data
->AddBool("_auto_in", fAutoindent
);
431 err
= data
->AddBool("_wrap", fWrap
);
433 err
= data
->AddBool("_nsel", !fSelectable
);
435 err
= data
->AddBool("_nedit", !fEditable
);
437 if (err
== B_OK
&& fDisallowedChars
!= NULL
) {
438 err
= data
->AddData("_dis_ch", B_RAW_TYPE
, fDisallowedChars
->Items(),
439 fDisallowedChars
->CountItems() * sizeof(int32
));
444 text_run_array
* runArray
= RunArray(0, TextLength());
446 void* flattened
= FlattenRunArray(runArray
, &runSize
);
447 if (flattened
!= NULL
) {
448 data
->AddData("_runs", B_RAW_TYPE
, flattened
, runSize
);
453 FreeRunArray(runArray
);
461 BTextView::AttachedToWindow()
463 BView::AttachedToWindow();
465 SetDrawingMode(B_OP_COPY
);
467 Window()->SetPulseRate(500000);
469 fCaretVisible
= false;
480 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT
);
485 BTextView::DetachedFromWindow()
487 BView::DetachedFromWindow();
492 BTextView::Draw(BRect updateRect
)
494 // what lines need to be drawn?
495 int32 startLine
= _LineAt(BPoint(0.0, updateRect
.top
));
496 int32 endLine
= _LineAt(BPoint(0.0, updateRect
.bottom
));
498 _DrawLines(startLine
, endLine
, -1, true);
503 BTextView::MouseDown(BPoint where
)
505 // should we even bother?
506 if (!fEditable
&& !fSelectable
)
509 _CancelInputMethod();
516 _StopMouseTracking();
520 BMessage
* currentMessage
= Window()->CurrentMessage();
521 if (currentMessage
!= NULL
) {
522 currentMessage
->FindInt32("modifiers", &modifiers
);
523 currentMessage
->FindInt32("buttons", (int32
*)&buttons
);
526 if (buttons
== B_SECONDARY_MOUSE_BUTTON
) {
527 _ShowContextMenu(where
);
531 BMessenger
messenger(this);
532 fTrackingMouse
= new (nothrow
) TextTrackState(messenger
);
533 if (fTrackingMouse
== NULL
)
536 fTrackingMouse
->clickOffset
= OffsetAt(where
);
537 fTrackingMouse
->shiftDown
= modifiers
& B_SHIFT_KEY
;
538 fTrackingMouse
->where
= where
;
540 bigtime_t clickTime
= system_time();
541 bigtime_t clickSpeed
= 0;
542 get_click_speed(&clickSpeed
);
544 = clickTime
- fClickTime
< clickSpeed
545 && fLastClickOffset
== fTrackingMouse
->clickOffset
;
549 SetMouseEventMask(B_POINTER_EVENTS
| B_KEYBOARD_EVENTS
,
550 B_LOCK_WINDOW_FOCUS
| B_NO_POINTER_HISTORY
);
552 if (fSelStart
!= fSelEnd
&& !fTrackingMouse
->shiftDown
&& !multipleClick
) {
554 GetTextRegion(fSelStart
, fSelEnd
, ®ion
);
555 if (region
.Contains(where
)) {
556 // Setup things for dragging
557 fTrackingMouse
->selectionRect
= region
.Frame();
559 fClickTime
= clickTime
;
560 fLastClickOffset
= OffsetAt(where
);
566 if (fClickCount
> 3) {
571 fClickTime
= clickTime
;
573 } else if (!fTrackingMouse
->shiftDown
) {
574 // If no multiple click yet and shift is not pressed, this is an
575 // independent first click somewhere into the textview - we initialize
576 // the corresponding members for handling potential multiple clicks:
577 fLastClickOffset
= fCaretOffset
= fTrackingMouse
->clickOffset
;
579 fClickTime
= clickTime
;
581 // Deselect any previously selected text
582 Select(fTrackingMouse
->clickOffset
, fTrackingMouse
->clickOffset
);
585 if (fClickTime
== clickTime
) {
586 BMessage
message(_PING_
);
587 message
.AddInt64("clickTime", clickTime
);
590 BMessenger
messenger(this);
591 fClickRunner
= new (nothrow
) BMessageRunner(messenger
, &message
,
596 _StopMouseTracking();
600 int32 offset
= fSelStart
;
601 if (fTrackingMouse
->clickOffset
> fSelStart
)
604 fTrackingMouse
->anchor
= offset
;
606 MouseMoved(where
, B_INSIDE_VIEW
, NULL
);
611 BTextView::MouseUp(BPoint where
)
613 BView::MouseUp(where
);
614 _PerformMouseUp(where
);
622 BTextView::MouseMoved(BPoint where
, uint32 code
, const BMessage
* dragMessage
)
624 // check if it's a "click'n'move"
625 if (_PerformMouseMoved(where
, code
))
631 _TrackMouse(where
, dragMessage
, true);
636 if (Window()->IsActive() && dragMessage
== NULL
)
637 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT
);
641 BView::MouseMoved(where
, code
, dragMessage
);
647 BTextView::WindowActivated(bool active
)
649 BView::WindowActivated(active
);
651 if (active
&& IsFocus()) {
661 GetMouse(&where
, &buttons
, false);
663 if (Bounds().Contains(where
))
664 _TrackMouse(where
, NULL
);
669 BTextView::KeyDown(const char* bytes
, int32 numBytes
)
671 const char keyPressed
= bytes
[0];
674 // only arrow and page keys are allowed
675 // (no need to hide the cursor)
676 switch (keyPressed
) {
681 _HandleArrowKey(keyPressed
);
688 _HandlePageKey(keyPressed
);
692 BView::KeyDown(bytes
, numBytes
);
699 // hide the cursor and caret
701 be_app
->ObscureCursor();
704 switch (keyPressed
) {
713 _HandleArrowKey(keyPressed
);
724 _HandlePageKey(keyPressed
);
730 // ignore, pass it up to superclass
731 BView::KeyDown(bytes
, numBytes
);
735 // bail out if the character is not allowed
737 && fDisallowedChars
->HasItem(
738 reinterpret_cast<void*>((uint32
)keyPressed
))) {
743 _HandleAlphaKey(bytes
, numBytes
);
748 if (fSelStart
== fSelEnd
)
756 if (fActive
&& fEditable
&& fSelStart
== fSelEnd
) {
757 if (system_time() > (fCaretTime
+ 500000.0))
764 BTextView::FrameResized(float newWidth
, float newHeight
)
766 BView::FrameResized(newWidth
, newHeight
);
772 BTextView::MakeFocus(bool focus
)
774 BView::MakeFocus(focus
);
776 if (focus
&& Window() != NULL
&& Window()->IsActive()) {
787 BTextView::MessageReceived(BMessage
* message
)
789 // ToDo: block input if not editable (Andrew)
791 // was this message dropped?
792 if (message
->WasDropped()) {
794 BPoint dropPoint
= message
->DropPoint(&dropOffset
);
795 ConvertFromScreen(&dropPoint
);
796 ConvertFromScreen(&dropOffset
);
797 if (!_MessageDropped(message
, dropPoint
, dropOffset
))
798 BView::MessageReceived(message
);
803 switch (message
->what
) {
805 if (!IsTypingHidden())
812 if (!IsTypingHidden())
830 case B_INPUT_METHOD_EVENT
:
833 if (message
->FindInt32("be:opcode", &opcode
) == B_OK
) {
835 case B_INPUT_METHOD_STARTED
:
837 BMessenger messenger
;
838 if (message
->FindMessenger("be:reply_to", &messenger
)
840 ASSERT(fInline
== NULL
);
841 fInline
= new InlineInput(messenger
);
846 case B_INPUT_METHOD_STOPPED
:
851 case B_INPUT_METHOD_CHANGED
:
853 _HandleInputMethodChanged(message
);
856 case B_INPUT_METHOD_LOCATION_REQUEST
:
858 _HandleInputMethodLocationRequest();
870 case B_COUNT_PROPERTIES
:
872 BPropertyInfo
propInfo(sPropertyList
);
874 const char* property
;
876 if (message
->GetCurrentSpecifier(NULL
, &specifier
) < B_OK
877 || specifier
.FindString("property", &property
) < B_OK
)
880 if (propInfo
.FindMatch(message
, 0, &specifier
, specifier
.what
,
882 BView::MessageReceived(message
);
887 bool handled
= false;
888 switch(message
->what
) {
890 handled
= _GetProperty(&specifier
, specifier
.what
, property
,
895 handled
= _SetProperty(&specifier
, specifier
.what
, property
,
899 case B_COUNT_PROPERTIES
:
900 handled
= _CountProperties(&specifier
, specifier
.what
,
908 message
->SendReply(&reply
);
910 BView::MessageReceived(message
);
916 if (message
->HasInt64("clickTime")) {
918 message
->FindInt64("clickTime", &clickTime
);
919 if (clickTime
== fClickTime
) {
920 if (fSelStart
!= fSelEnd
&& fSelectable
) {
922 GetTextRegion(fSelStart
, fSelEnd
, ®ion
);
923 if (region
.Contains(fWhere
))
924 _TrackMouse(fWhere
, NULL
);
929 } else if (fTrackingMouse
) {
930 fTrackingMouse
->SimulateMouseMovement(this);
931 _PerformAutoScrolling();
941 case kMsgNavigateArrow
:
943 int32 key
= message
->GetInt32("key", 0);
944 int32 modifiers
= message
->GetInt32("modifiers", 0);
945 _HandleArrowKey(key
, modifiers
);
949 case kMsgNavigatePage
:
951 int32 key
= message
->GetInt32("key", 0);
952 int32 modifiers
= message
->GetInt32("modifiers", 0);
953 _HandlePageKey(key
, modifiers
);
958 BView::MessageReceived(message
);
965 BTextView::ResolveSpecifier(BMessage
* message
, int32 index
, BMessage
* specifier
,
966 int32 what
, const char* property
)
968 BPropertyInfo
propInfo(sPropertyList
);
969 BHandler
* target
= this;
971 if (propInfo
.FindMatch(message
, index
, specifier
, what
, property
) < B_OK
) {
972 target
= BView::ResolveSpecifier(message
, index
, specifier
, what
,
981 BTextView::GetSupportedSuites(BMessage
* data
)
986 status_t err
= data
->AddString("suites", "suite/vnd.Be-text-view");
990 BPropertyInfo
prop_info(sPropertyList
);
991 err
= data
->AddFlat("messages", &prop_info
);
995 return BView::GetSupportedSuites(data
);
1000 BTextView::Perform(perform_code code
, void* _data
)
1003 case PERFORM_CODE_MIN_SIZE
:
1004 ((perform_data_min_size
*)_data
)->return_value
1005 = BTextView::MinSize();
1007 case PERFORM_CODE_MAX_SIZE
:
1008 ((perform_data_max_size
*)_data
)->return_value
1009 = BTextView::MaxSize();
1011 case PERFORM_CODE_PREFERRED_SIZE
:
1012 ((perform_data_preferred_size
*)_data
)->return_value
1013 = BTextView::PreferredSize();
1015 case PERFORM_CODE_LAYOUT_ALIGNMENT
:
1016 ((perform_data_layout_alignment
*)_data
)->return_value
1017 = BTextView::LayoutAlignment();
1019 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH
:
1020 ((perform_data_has_height_for_width
*)_data
)->return_value
1021 = BTextView::HasHeightForWidth();
1023 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH
:
1025 perform_data_get_height_for_width
* data
1026 = (perform_data_get_height_for_width
*)_data
;
1027 BTextView::GetHeightForWidth(data
->width
, &data
->min
, &data
->max
,
1031 case PERFORM_CODE_SET_LAYOUT
:
1033 perform_data_set_layout
* data
= (perform_data_set_layout
*)_data
;
1034 BTextView::SetLayout(data
->layout
);
1037 case PERFORM_CODE_LAYOUT_INVALIDATED
:
1039 perform_data_layout_invalidated
* data
1040 = (perform_data_layout_invalidated
*)_data
;
1041 BTextView::LayoutInvalidated(data
->descendants
);
1044 case PERFORM_CODE_DO_LAYOUT
:
1046 BTextView::DoLayout();
1051 return BView::Perform(code
, _data
);
1056 BTextView::SetText(const char* text
, const text_run_array
* runs
)
1058 SetText(text
, text
? strlen(text
) : 0, runs
);
1063 BTextView::SetText(const char* text
, int32 length
, const text_run_array
* runs
)
1065 _CancelInputMethod();
1067 // hide the caret/unhighlight the selection
1069 if (fSelStart
!= fSelEnd
) {
1071 Highlight(fSelStart
, fSelEnd
);
1076 // remove data from buffer
1077 if (fText
->Length() > 0)
1078 DeleteText(0, fText
->Length());
1080 if (text
!= NULL
&& length
> 0)
1081 InsertText(text
, length
, 0, runs
);
1083 // recalculate line breaks and draw the text
1084 _Refresh(0, length
, false);
1085 fCaretOffset
= fSelStart
= fSelEnd
= 0;
1094 BTextView::SetText(BFile
* file
, int32 offset
, int32 length
,
1095 const text_run_array
* runs
)
1099 _CancelInputMethod();
1104 if (fText
->Length() > 0)
1105 DeleteText(0, fText
->Length());
1107 fText
->InsertText(file
, offset
, length
, 0);
1109 // update the start offsets of each line below offset
1110 fLines
->BumpOffset(length
, _LineAt(offset
) + 1);
1112 // update the style runs
1113 fStyles
->BumpOffset(length
, fStyles
->OffsetToRun(offset
- 1) + 1);
1115 if (fStylable
&& runs
!= NULL
)
1116 SetRunArray(offset
, offset
+ length
, runs
);
1118 // apply null-style to inserted text
1119 _ApplyStyleRange(offset
, offset
+ length
);
1122 // recalculate line breaks and draw the text
1123 _Refresh(0, length
, false);
1124 fCaretOffset
= fSelStart
= fSelEnd
= 0;
1125 ScrollToOffset(fSelStart
);
1133 BTextView::Insert(const char* text
, const text_run_array
* runs
)
1136 _DoInsertText(text
, strlen(text
), fSelStart
, runs
);
1141 BTextView::Insert(const char* text
, int32 length
, const text_run_array
* runs
)
1143 if (text
!= NULL
&& length
> 0)
1144 _DoInsertText(text
, strnlen(text
, length
), fSelStart
, runs
);
1149 BTextView::Insert(int32 offset
, const char* text
, int32 length
,
1150 const text_run_array
* runs
)
1152 // pin offset at reasonable values
1155 else if (offset
> fText
->Length())
1156 offset
= fText
->Length();
1158 if (text
!= NULL
&& length
> 0)
1159 _DoInsertText(text
, strnlen(text
, length
), offset
, runs
);
1166 Delete(fSelStart
, fSelEnd
);
1171 BTextView::Delete(int32 startOffset
, int32 endOffset
)
1175 // pin offsets at reasonable values
1176 if (startOffset
< 0)
1178 else if (startOffset
> fText
->Length())
1179 startOffset
= fText
->Length();
1182 else if (endOffset
> fText
->Length())
1183 endOffset
= fText
->Length();
1185 // anything to delete?
1186 if (startOffset
== endOffset
)
1189 // hide the caret/unhighlight the selection
1191 if (fSelStart
!= fSelEnd
) {
1193 Highlight(fSelStart
, fSelEnd
);
1197 // remove data from buffer
1198 DeleteText(startOffset
, endOffset
);
1200 // check if the caret needs to be moved
1201 if (fCaretOffset
>= endOffset
)
1202 fCaretOffset
-= (endOffset
- startOffset
);
1203 else if (fCaretOffset
>= startOffset
&& fCaretOffset
< endOffset
)
1204 fCaretOffset
= startOffset
;
1206 fSelEnd
= fSelStart
= fCaretOffset
;
1208 // recalculate line breaks and draw what's left
1209 _Refresh(startOffset
, endOffset
, false);
1217 BTextView::Text() const
1219 return fText
->RealText();
1224 BTextView::TextLength() const
1226 return fText
->Length();
1231 BTextView::GetText(int32 offset
, int32 length
, char* buffer
) const
1234 fText
->GetString(offset
, length
, buffer
);
1239 BTextView::ByteAt(int32 offset
) const
1241 if (offset
< 0 || offset
>= fText
->Length())
1244 return fText
->RealCharAt(offset
);
1249 BTextView::CountLines() const
1251 return fLines
->NumLines();
1256 BTextView::CurrentLine() const
1258 return LineAt(fSelStart
);
1263 BTextView::GoToLine(int32 index
)
1265 _CancelInputMethod();
1267 fSelStart
= fSelEnd
= fCaretOffset
= OffsetAt(index
);
1273 BTextView::Cut(BClipboard
* clipboard
)
1275 _CancelInputMethod();
1280 fUndo
= new CutUndoBuffer(this);
1288 BTextView::Copy(BClipboard
* clipboard
)
1290 _CancelInputMethod();
1292 if (clipboard
->Lock()) {
1295 BMessage
* clip
= clipboard
->Data();
1297 int32 numBytes
= fSelEnd
- fSelStart
;
1298 const char* text
= fText
->GetString(fSelStart
, &numBytes
);
1299 clip
->AddData("text/plain", B_MIME_TYPE
, text
, numBytes
);
1303 text_run_array
* runArray
= RunArray(fSelStart
, fSelEnd
, &size
);
1304 clip
->AddData("application/x-vnd.Be-text_run_array",
1305 B_MIME_TYPE
, runArray
, size
);
1306 FreeRunArray(runArray
);
1308 clipboard
->Commit();
1310 clipboard
->Unlock();
1316 BTextView::Paste(BClipboard
* clipboard
)
1319 _CancelInputMethod();
1321 if (!fEditable
|| !clipboard
->Lock())
1324 BMessage
* clip
= clipboard
->Data();
1326 const char* text
= NULL
;
1329 if (clip
->FindData("text/plain", B_MIME_TYPE
,
1330 (const void**)&text
, &length
) == B_OK
) {
1331 text_run_array
* runArray
= NULL
;
1332 ssize_t runLength
= 0;
1335 clip
->FindData("application/x-vnd.Be-text_run_array",
1336 B_MIME_TYPE
, (const void**)&runArray
, &runLength
);
1339 _FilterDisallowedChars((char*)text
, length
, runArray
);
1343 clipboard
->Unlock();
1349 fUndo
= new PasteUndoBuffer(this, text
, length
, runArray
,
1353 if (fSelStart
!= fSelEnd
)
1356 Insert(text
, length
, runArray
);
1357 ScrollToOffset(fSelEnd
);
1361 clipboard
->Unlock();
1368 // We always check for fUndo != NULL (not only here),
1369 // because when fUndo is NULL, undo is deactivated.
1372 fUndo
= new ClearUndoBuffer(this);
1380 BTextView::AcceptsPaste(BClipboard
* clipboard
)
1382 bool result
= false;
1384 if (fEditable
&& clipboard
&& clipboard
->Lock()) {
1385 BMessage
* data
= clipboard
->Data();
1386 result
= data
&& data
->HasData("text/plain", B_MIME_TYPE
);
1387 clipboard
->Unlock();
1395 BTextView::AcceptsDrop(const BMessage
* message
)
1397 return fEditable
&& message
1398 && message
->HasData("text/plain", B_MIME_TYPE
);
1403 BTextView::Select(int32 startOffset
, int32 endOffset
)
1409 _CancelInputMethod();
1411 // pin offsets at reasonable values
1412 if (startOffset
< 0)
1414 else if (startOffset
> fText
->Length())
1415 startOffset
= fText
->Length();
1418 else if (endOffset
> fText
->Length())
1419 endOffset
= fText
->Length();
1421 // a negative selection?
1422 if (startOffset
> endOffset
)
1425 // is the new selection any different from the current selection?
1426 if (startOffset
== fSelStart
&& endOffset
== fSelEnd
)
1429 fStyles
->InvalidateNullStyle();
1433 if (startOffset
== endOffset
) {
1434 if (fSelStart
!= fSelEnd
) {
1435 // unhilite the selection
1437 Highlight(fSelStart
, fSelEnd
);
1439 fSelStart
= fSelEnd
= fCaretOffset
= startOffset
;
1443 // draw only those ranges that are different
1445 if (startOffset
!= fSelStart
) {
1446 // start of selection has changed
1447 if (startOffset
> fSelStart
) {
1451 start
= startOffset
;
1454 Highlight(start
, end
);
1457 if (endOffset
!= fSelEnd
) {
1458 // end of selection has changed
1459 if (endOffset
> fSelEnd
) {
1466 Highlight(start
, end
);
1469 fSelStart
= startOffset
;
1470 fSelEnd
= endOffset
;
1476 BTextView::SelectAll()
1478 Select(0, fText
->Length());
1483 BTextView::GetSelection(int32
* _start
, int32
* _end
) const
1502 BTextView::SetFontAndColor(const BFont
* font
, uint32 mode
,
1503 const rgb_color
* color
)
1505 SetFontAndColor(fSelStart
, fSelEnd
, font
, mode
, color
);
1510 BTextView::SetFontAndColor(int32 startOffset
, int32 endOffset
,
1511 const BFont
* font
, uint32 mode
, const rgb_color
* color
)
1517 const int32 textLength
= fText
->Length();
1520 // When the text view is not stylable, we always set the whole text's
1521 // style and ignore the offsets
1523 endOffset
= textLength
;
1525 // pin offsets at reasonable values
1526 if (startOffset
< 0)
1528 else if (startOffset
> textLength
)
1529 startOffset
= textLength
;
1533 else if (endOffset
> textLength
)
1534 endOffset
= textLength
;
1537 // apply the style to the style buffer
1538 fStyles
->InvalidateNullStyle();
1539 _ApplyStyleRange(startOffset
, endOffset
, mode
, font
, color
);
1541 if ((mode
& (B_FONT_FAMILY_AND_STYLE
| B_FONT_SIZE
)) != 0) {
1542 // ToDo: maybe only invalidate the layout (depending on
1543 // B_SUPPORTS_LAYOUT) and have it _Refresh() automatically?
1545 // recalc the line breaks and redraw with new style
1546 _Refresh(startOffset
, endOffset
, false);
1548 // the line breaks wont change, simply redraw
1549 _RequestDrawLines(_LineAt(startOffset
), _LineAt(endOffset
));
1557 BTextView::GetFontAndColor(int32 offset
, BFont
* _font
,
1558 rgb_color
* _color
) const
1560 fStyles
->GetStyle(offset
, _font
, _color
);
1565 BTextView::GetFontAndColor(BFont
* _font
, uint32
* _mode
,
1566 rgb_color
* _color
, bool* _sameColor
) const
1568 fStyles
->ContinuousGetStyle(_font
, _mode
, _color
, _sameColor
,
1569 fSelStart
, fSelEnd
);
1574 BTextView::SetRunArray(int32 startOffset
, int32 endOffset
,
1575 const text_run_array
* runs
)
1579 _CancelInputMethod();
1581 text_run_array oneRun
;
1584 // when the text view is not stylable, we always set the whole text's
1585 // style with the first run and ignore the offsets
1586 if (runs
->count
== 0)
1590 endOffset
= fText
->Length();
1592 oneRun
.runs
[0] = runs
->runs
[0];
1593 oneRun
.runs
[0].offset
= 0;
1596 // pin offsets at reasonable values
1597 if (startOffset
< 0)
1599 else if (startOffset
> fText
->Length())
1600 startOffset
= fText
->Length();
1604 else if (endOffset
> fText
->Length())
1605 endOffset
= fText
->Length();
1608 _SetRunArray(startOffset
, endOffset
, runs
);
1610 _Refresh(startOffset
, endOffset
, false);
1615 BTextView::RunArray(int32 startOffset
, int32 endOffset
, int32
* _size
) const
1617 // pin offsets at reasonable values
1618 if (startOffset
< 0)
1620 else if (startOffset
> fText
->Length())
1621 startOffset
= fText
->Length();
1625 else if (endOffset
> fText
->Length())
1626 endOffset
= fText
->Length();
1628 STEStyleRange
* styleRange
1629 = fStyles
->GetStyleRange(startOffset
, endOffset
- 1);
1630 if (styleRange
== NULL
)
1633 text_run_array
* runArray
= AllocRunArray(styleRange
->count
, _size
);
1634 if (runArray
!= NULL
) {
1635 for (int32 i
= 0; i
< runArray
->count
; i
++) {
1636 runArray
->runs
[i
].offset
= styleRange
->runs
[i
].offset
;
1637 runArray
->runs
[i
].font
= styleRange
->runs
[i
].style
.font
;
1638 runArray
->runs
[i
].color
= styleRange
->runs
[i
].style
.color
;
1649 BTextView::LineAt(int32 offset
) const
1651 // pin offset at reasonable values
1654 else if (offset
> fText
->Length())
1655 offset
= fText
->Length();
1657 int32 lineNum
= _LineAt(offset
);
1658 if (_IsOnEmptyLastLine(offset
))
1665 BTextView::LineAt(BPoint point
) const
1667 int32 lineNum
= _LineAt(point
);
1668 if ((*fLines
)[lineNum
+ 1]->origin
<= point
.y
- fTextRect
.top
)
1676 BTextView::PointAt(int32 offset
, float* _height
) const
1678 // pin offset at reasonable values
1681 else if (offset
> fText
->Length())
1682 offset
= fText
->Length();
1685 int32 lineNum
= _LineAt(offset
);
1686 STELine
* line
= (*fLines
)[lineNum
];
1691 result
.y
= line
->origin
+ fTextRect
.top
;
1693 bool onEmptyLastLine
= _IsOnEmptyLastLine(offset
);
1695 if (fStyles
->NumRuns() == 0) {
1696 // Handle the case where there is only one line (no text inserted)
1697 fStyles
->SyncNullStyle(0);
1698 height
= _NullStyleHeight();
1700 height
= (line
+ 1)->origin
- line
->origin
;
1702 if (onEmptyLastLine
) {
1703 // special case: go down one line if offset is at the newline
1704 // at the end of the buffer ...
1706 // ... and return the height of that (empty) line
1707 fStyles
->SyncNullStyle(offset
);
1708 height
= _NullStyleHeight();
1710 int32 length
= offset
- line
->offset
;
1711 result
.x
+= _TabExpandedStyledWidth(line
->offset
, length
);
1715 if (fAlignment
!= B_ALIGN_LEFT
) {
1716 float lineWidth
= onEmptyLastLine
? 0.0 : LineWidth(lineNum
);
1717 float alignmentOffset
= fTextRect
.Width() - lineWidth
;
1718 if (fAlignment
== B_ALIGN_CENTER
)
1719 alignmentOffset
/= 2;
1720 result
.x
+= alignmentOffset
;
1723 // convert from text rect coordinates
1724 result
.x
+= fTextRect
.left
;
1727 result
.x
= lroundf(result
.x
);
1728 result
.y
= lroundf(result
.y
);
1729 if (_height
!= NULL
)
1737 BTextView::OffsetAt(BPoint point
) const
1739 const int32 textLength
= fText
->Length();
1741 // should we even bother?
1742 if (point
.y
>= fTextRect
.bottom
)
1744 else if (point
.y
< fTextRect
.top
)
1747 int32 lineNum
= _LineAt(point
);
1748 STELine
* line
= (*fLines
)[lineNum
];
1750 #define COMPILE_PROBABLY_BAD_CODE 1
1752 #if COMPILE_PROBABLY_BAD_CODE
1753 // special case: if point is within the text rect and PixelToLine()
1754 // tells us that it's on the last line, but if point is actually
1755 // lower than the bottom of the last line, return the last offset
1756 // (can happen for newlines)
1757 if (lineNum
== (fLines
->NumLines() - 1)) {
1758 if (point
.y
>= ((line
+ 1)->origin
+ fTextRect
.top
))
1763 // convert to text rect coordinates
1764 if (fAlignment
!= B_ALIGN_LEFT
) {
1765 float alignmentOffset
= fTextRect
.Width() - LineWidth(lineNum
);
1766 if (fAlignment
== B_ALIGN_CENTER
)
1767 alignmentOffset
/= 2;
1768 point
.x
-= alignmentOffset
;
1771 point
.x
-= fTextRect
.left
;
1772 point
.x
= max_c(point
.x
, 0.0);
1774 // ToDo: The following code isn't very efficient, because it always starts
1775 // from the left end, so when the point is near the right end it's very
1777 int32 offset
= line
->offset
;
1778 const int32 limit
= (line
+ 1)->offset
;
1781 const int32 nextInitial
= _NextInitialByte(offset
);
1782 const int32 saveOffset
= offset
;
1784 if (ByteAt(offset
) == B_TAB
)
1785 width
= _ActualTabWidth(location
);
1787 width
= _StyledWidth(saveOffset
, nextInitial
- saveOffset
);
1788 if (location
+ width
> point
.x
) {
1789 if (fabs(location
+ width
- point
.x
) < fabs(location
- point
.x
))
1790 offset
= nextInitial
;
1795 offset
= nextInitial
;
1796 } while (offset
< limit
);
1798 if (offset
== (line
+ 1)->offset
) {
1799 // special case: newlines aren't visible
1800 // return the offset of the character preceding the newline
1801 if (ByteAt(offset
- 1) == B_ENTER
)
1804 // special case: return the offset preceding any spaces that
1805 // aren't at the end of the buffer
1806 if (offset
!= textLength
&& ByteAt(offset
- 1) == B_SPACE
)
1815 BTextView::OffsetAt(int32 line
) const
1820 if (line
> fLines
->NumLines())
1821 return fText
->Length();
1823 return (*fLines
)[line
]->offset
;
1828 BTextView::FindWord(int32 offset
, int32
* _fromOffset
, int32
* _toOffset
)
1840 if (offset
> fText
->Length()) {
1842 *_fromOffset
= fText
->Length();
1845 *_toOffset
= fText
->Length();
1851 *_fromOffset
= _PreviousWordBoundary(offset
);
1854 *_toOffset
= _NextWordBoundary(offset
);
1859 BTextView::CanEndLine(int32 offset
)
1861 if (offset
< 0 || offset
> fText
->Length())
1864 // TODO: This should be improved using the LocaleKit.
1865 uint32 classification
= _CharClassification(offset
);
1867 // wrapping is always allowed at end of text and at newlines
1868 if (classification
== CHAR_CLASS_END_OF_TEXT
|| ByteAt(offset
) == B_ENTER
)
1871 uint32 nextClassification
= _CharClassification(offset
+ 1);
1872 if (nextClassification
== CHAR_CLASS_END_OF_TEXT
)
1875 // never separate a punctuation char from its preceeding word
1876 if (classification
== CHAR_CLASS_DEFAULT
1877 && nextClassification
== CHAR_CLASS_PUNCTUATION
) {
1881 if ((classification
== CHAR_CLASS_WHITESPACE
1882 && nextClassification
!= CHAR_CLASS_WHITESPACE
)
1883 || (classification
!= CHAR_CLASS_WHITESPACE
1884 && nextClassification
== CHAR_CLASS_WHITESPACE
)) {
1888 // allow wrapping after whitespace, unless more whitespace (except for
1890 if (classification
== CHAR_CLASS_WHITESPACE
1891 && (nextClassification
!= CHAR_CLASS_WHITESPACE
1892 || ByteAt(offset
+ 1) == B_ENTER
)) {
1896 // allow wrapping after punctuation chars, unless more punctuation, closing
1897 // parens or quotes follow
1898 if (classification
== CHAR_CLASS_PUNCTUATION
1899 && nextClassification
!= CHAR_CLASS_PUNCTUATION
1900 && nextClassification
!= CHAR_CLASS_PARENS_CLOSE
1901 && nextClassification
!= CHAR_CLASS_QUOTE
) {
1905 // allow wrapping after quotes, graphical chars and closing parens only if
1906 // whitespace follows (not perfect, but seems to do the right thing most
1908 if ((classification
== CHAR_CLASS_QUOTE
1909 || classification
== CHAR_CLASS_GRAPHICAL
1910 || classification
== CHAR_CLASS_PARENS_CLOSE
)
1911 && nextClassification
== CHAR_CLASS_WHITESPACE
) {
1920 BTextView::LineWidth(int32 lineNumber
) const
1922 if (lineNumber
< 0 || lineNumber
>= fLines
->NumLines())
1925 STELine
* line
= (*fLines
)[lineNumber
];
1926 int32 length
= (line
+ 1)->offset
- line
->offset
;
1928 // skip newline at the end of the line, if any, as it does no contribute
1930 if (ByteAt((line
+ 1)->offset
- 1) == B_ENTER
)
1933 return _TabExpandedStyledWidth(line
->offset
, length
);
1938 BTextView::LineHeight(int32 lineNumber
) const
1940 float lineHeight
= TextHeight(lineNumber
, lineNumber
);
1941 if (lineHeight
== 0.0) {
1942 // We probably don't have text content yet. Take the initial
1943 // style's font height or fall back to the plain font.
1945 fStyles
->GetNullStyle(&font
, NULL
);
1947 font
= be_plain_font
;
1949 font_height fontHeight
;
1950 font
->GetHeight(&fontHeight
);
1951 // This is how the height is calculated in _RecalculateLineBreaks().
1952 lineHeight
= ceilf(fontHeight
.ascent
+ fontHeight
.descent
) + 1;
1960 BTextView::TextHeight(int32 startLine
, int32 endLine
) const
1962 const int32 numLines
= fLines
->NumLines();
1965 else if (startLine
> numLines
- 1)
1966 startLine
= numLines
- 1;
1970 else if (endLine
> numLines
- 1)
1971 endLine
= numLines
- 1;
1973 float height
= (*fLines
)[endLine
+ 1]->origin
1974 - (*fLines
)[startLine
]->origin
;
1976 if (startLine
!= endLine
&& endLine
== numLines
- 1
1977 && fText
->RealCharAt(fText
->Length() - 1) == B_ENTER
) {
1978 height
+= (*fLines
)[endLine
+ 1]->origin
- (*fLines
)[endLine
]->origin
;
1981 return ceilf(height
);
1986 BTextView::GetTextRegion(int32 startOffset
, int32 endOffset
,
1987 BRegion
* outRegion
) const
1992 outRegion
->MakeEmpty();
1994 // pin offsets at reasonable values
1995 if (startOffset
< 0)
1997 else if (startOffset
> fText
->Length())
1998 startOffset
= fText
->Length();
2001 else if (endOffset
> fText
->Length())
2002 endOffset
= fText
->Length();
2004 // return an empty region if the range is invalid
2005 if (startOffset
>= endOffset
)
2008 float startLineHeight
= 0.0;
2009 float endLineHeight
= 0.0;
2010 BPoint startPt
= PointAt(startOffset
, &startLineHeight
);
2011 BPoint endPt
= PointAt(endOffset
, &endLineHeight
);
2013 startLineHeight
= ceilf(startLineHeight
);
2014 endLineHeight
= ceilf(endLineHeight
);
2018 if (startPt
.y
== endPt
.y
) {
2019 // this is a one-line region
2020 selRect
.left
= max_c(startPt
.x
, fTextRect
.left
);
2021 selRect
.top
= startPt
.y
;
2022 selRect
.right
= endPt
.x
- 1.0;
2023 selRect
.bottom
= endPt
.y
+ endLineHeight
- 1.0;
2024 outRegion
->Include(selRect
);
2026 // more than one line in the specified offset range
2027 selRect
.left
= max_c(startPt
.x
, fTextRect
.left
);
2028 selRect
.top
= startPt
.y
;
2029 selRect
.right
= fTextRect
.right
;
2030 selRect
.bottom
= startPt
.y
+ startLineHeight
- 1.0;
2031 outRegion
->Include(selRect
);
2033 if (startPt
.y
+ startLineHeight
< endPt
.y
) {
2034 // more than two lines in the range
2035 selRect
.left
= fTextRect
.left
;
2036 selRect
.top
= startPt
.y
+ startLineHeight
;
2037 selRect
.right
= fTextRect
.right
;
2038 selRect
.bottom
= endPt
.y
- 1.0;
2039 outRegion
->Include(selRect
);
2042 selRect
.left
= fTextRect
.left
;
2043 selRect
.top
= endPt
.y
;
2044 selRect
.right
= endPt
.x
- 1.0;
2045 selRect
.bottom
= endPt
.y
+ endLineHeight
- 1.0;
2046 outRegion
->Include(selRect
);
2052 BTextView::ScrollToOffset(int32 offset
)
2054 // pin offset at reasonable values
2057 else if (offset
> fText
->Length())
2058 offset
= fText
->Length();
2060 BRect bounds
= Bounds();
2061 float lineHeight
= 0.0;
2064 BPoint point
= PointAt(offset
, &lineHeight
);
2067 float extraSpace
= fAlignment
== B_ALIGN_LEFT
?
2068 ceilf(bounds
.IntegerWidth() / 2) : 0.0;
2070 if (point
.x
< bounds
.left
)
2071 xDiff
= point
.x
- bounds
.left
- extraSpace
;
2072 else if (point
.x
> bounds
.right
)
2073 xDiff
= point
.x
- bounds
.right
+ extraSpace
;
2076 if (point
.y
< bounds
.top
)
2077 yDiff
= point
.y
- bounds
.top
;
2078 else if (point
.y
+ lineHeight
> bounds
.bottom
2079 && point
.y
- lineHeight
> bounds
.top
) {
2080 yDiff
= point
.y
+ lineHeight
- bounds
.bottom
;
2083 // prevent negative scroll offset
2084 if (bounds
.left
+ xDiff
< 0.0)
2085 xDiff
= -bounds
.left
;
2086 if (bounds
.top
+ yDiff
< 0.0)
2087 yDiff
= -bounds
.top
;
2089 ScrollBy(xDiff
, yDiff
);
2094 BTextView::ScrollToSelection()
2096 ScrollToOffset(fSelStart
);
2101 BTextView::Highlight(int32 startOffset
, int32 endOffset
)
2103 // pin offsets at reasonable values
2104 if (startOffset
< 0)
2106 else if (startOffset
> fText
->Length())
2107 startOffset
= fText
->Length();
2110 else if (endOffset
> fText
->Length())
2111 endOffset
= fText
->Length();
2113 if (startOffset
>= endOffset
)
2117 GetTextRegion(startOffset
, endOffset
, &selRegion
);
2119 SetDrawingMode(B_OP_INVERT
);
2120 FillRegion(&selRegion
, B_SOLID_HIGH
);
2121 SetDrawingMode(B_OP_COPY
);
2125 // #pragma mark - Configuration methods
2129 BTextView::SetTextRect(BRect rect
)
2131 if (rect
== fTextRect
)
2135 rect
.right
= Bounds().right
- fLayoutData
->rightInset
;
2136 rect
.bottom
= Bounds().bottom
- fLayoutData
->bottomInset
;
2139 fLayoutData
->UpdateInsets(Bounds().OffsetToCopy(B_ORIGIN
), rect
);
2146 BTextView::TextRect() const
2153 BTextView::_ResetTextRect()
2155 BRect
oldTextRect(fTextRect
);
2156 // reset text rect to bounds minus insets ...
2157 fTextRect
= Bounds().OffsetToCopy(B_ORIGIN
);
2158 fTextRect
.left
+= fLayoutData
->leftInset
;
2159 fTextRect
.top
+= fLayoutData
->topInset
;
2160 fTextRect
.right
-= fLayoutData
->rightInset
;
2161 fTextRect
.bottom
-= fLayoutData
->bottomInset
;
2163 // and rewrap (potentially adjusting the right and the bottom of the text
2165 _Refresh(0, TextLength(), false);
2167 // Make sure that the dirty area outside the text is redrawn too.
2168 BRegion
invalid(oldTextRect
| fTextRect
);
2169 invalid
.Exclude(fTextRect
);
2170 Invalidate(&invalid
);
2175 BTextView::SetInsets(float left
, float top
, float right
, float bottom
)
2177 if (fLayoutData
->leftInset
== left
2178 && fLayoutData
->topInset
== top
2179 && fLayoutData
->rightInset
== right
2180 && fLayoutData
->bottomInset
== bottom
)
2183 fLayoutData
->leftInset
= left
;
2184 fLayoutData
->topInset
= top
;
2185 fLayoutData
->rightInset
= right
;
2186 fLayoutData
->bottomInset
= bottom
;
2194 BTextView::GetInsets(float* _left
, float* _top
, float* _right
,
2195 float* _bottom
) const
2198 *_left
= fLayoutData
->leftInset
;
2200 *_top
= fLayoutData
->topInset
;
2202 *_right
= fLayoutData
->rightInset
;
2204 *_bottom
= fLayoutData
->bottomInset
;
2209 BTextView::SetStylable(bool stylable
)
2211 fStylable
= stylable
;
2216 BTextView::IsStylable() const
2223 BTextView::SetTabWidth(float width
)
2225 if (width
== fTabWidth
)
2230 if (Window() != NULL
)
2231 _Refresh(0, fText
->Length(), false);
2236 BTextView::TabWidth() const
2243 BTextView::MakeSelectable(bool selectable
)
2245 if (selectable
== fSelectable
)
2248 fSelectable
= selectable
;
2250 if (fActive
&& fSelStart
!= fSelEnd
&& Window() != NULL
)
2251 Highlight(fSelStart
, fSelEnd
);
2256 BTextView::IsSelectable() const
2263 BTextView::MakeEditable(bool editable
)
2265 if (editable
== fEditable
)
2268 fEditable
= editable
;
2269 // TextControls change the color of the text when
2270 // they are made editable, so we need to invalidate
2271 // the NULL style here
2272 // TODO: it works well, but it could be caused by a bug somewhere else
2274 fStyles
->InvalidateNullStyle();
2275 if (Window() != NULL
&& fActive
) {
2278 _CancelInputMethod();
2285 BTextView::IsEditable() const
2292 BTextView::SetWordWrap(bool wrap
)
2297 bool updateOnScreen
= fActive
&& Window() != NULL
;
2298 if (updateOnScreen
) {
2299 // hide the caret, unhilite the selection
2300 if (fSelStart
!= fSelEnd
) {
2302 Highlight(fSelStart
, fSelEnd
);
2310 _Refresh(0, fText
->Length(), false);
2312 if (updateOnScreen
) {
2313 // show the caret, hilite the selection
2314 if (fSelStart
!= fSelEnd
) {
2316 Highlight(fSelStart
, fSelEnd
);
2324 BTextView::DoesWordWrap() const
2331 BTextView::SetMaxBytes(int32 max
)
2333 const int32 textLength
= fText
->Length();
2336 if (fMaxBytes
< textLength
) {
2337 int32 offset
= fMaxBytes
;
2338 // Delete the text after fMaxBytes, but
2339 // respect multibyte characters boundaries.
2340 const int32 previousInitial
= _PreviousInitialByte(offset
);
2341 if (_NextInitialByte(previousInitial
) != offset
)
2342 offset
= previousInitial
;
2344 Delete(offset
, textLength
);
2350 BTextView::MaxBytes() const
2357 BTextView::DisallowChar(uint32 character
)
2359 if (fDisallowedChars
== NULL
)
2360 fDisallowedChars
= new BList
;
2361 if (!fDisallowedChars
->HasItem(reinterpret_cast<void*>(character
)))
2362 fDisallowedChars
->AddItem(reinterpret_cast<void*>(character
));
2367 BTextView::AllowChar(uint32 character
)
2369 if (fDisallowedChars
!= NULL
)
2370 fDisallowedChars
->RemoveItem(reinterpret_cast<void*>(character
));
2375 BTextView::SetAlignment(alignment align
)
2377 // Do a reality check
2378 if (fAlignment
!= align
&&
2379 (align
== B_ALIGN_LEFT
||
2380 align
== B_ALIGN_RIGHT
||
2381 align
== B_ALIGN_CENTER
)) {
2384 // After setting new alignment, update the view/window
2385 if (Window() != NULL
)
2392 BTextView::Alignment() const
2399 BTextView::SetAutoindent(bool state
)
2401 fAutoindent
= state
;
2406 BTextView::DoesAutoindent() const
2413 BTextView::SetColorSpace(color_space colors
)
2415 if (colors
!= fColorSpace
&& fOffscreen
) {
2416 fColorSpace
= colors
;
2424 BTextView::ColorSpace() const
2431 BTextView::MakeResizable(bool resize
, BView
* resizeView
)
2435 fContainerView
= resizeView
;
2437 // Wrapping mode and resizable mode can't live together
2441 if (fActive
&& Window() != NULL
) {
2442 if (fSelStart
!= fSelEnd
) {
2444 Highlight(fSelStart
, fSelEnd
);
2449 // We need to reset the right inset, as otherwise the auto-resize would
2450 // get confused about just how wide the textview needs to be.
2451 // This seems to be an artefact of how Tracker creates the textview
2452 // during a rename action.
2453 fLayoutData
->rightInset
= fLayoutData
->leftInset
;
2456 fContainerView
= NULL
;
2462 _Refresh(0, fText
->Length(), false);
2467 BTextView::IsResizable() const
2474 BTextView::SetDoesUndo(bool undo
)
2476 if (undo
&& fUndo
== NULL
)
2477 fUndo
= new UndoBuffer(this, B_UNDO_UNAVAILABLE
);
2478 else if (!undo
&& fUndo
!= NULL
) {
2486 BTextView::DoesUndo() const
2488 return fUndo
!= NULL
;
2493 BTextView::HideTyping(bool enabled
)
2496 Delete(0, fText
->Length());
2498 fText
->SetPasswordMode(enabled
);
2503 BTextView::IsTypingHidden() const
2505 return fText
->PasswordMode();
2509 // #pragma mark - Size methods
2513 BTextView::ResizeToPreferred()
2515 BView::ResizeToPreferred();
2520 BTextView::GetPreferredSize(float* _width
, float* _height
)
2524 _ValidateLayoutData();
2527 float width
= Bounds().Width();
2528 if (width
< fLayoutData
->min
.width
2529 || (Flags() & B_SUPPORTS_LAYOUT
) != 0) {
2530 width
= fLayoutData
->min
.width
;
2536 float height
= Bounds().Height();
2537 if (height
< fLayoutData
->min
.height
2538 || (Flags() & B_SUPPORTS_LAYOUT
) != 0) {
2539 height
= fLayoutData
->min
.height
;
2547 BTextView::MinSize()
2551 _ValidateLayoutData();
2552 return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData
->min
);
2557 BTextView::MaxSize()
2561 return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
2562 BSize(B_SIZE_UNLIMITED
, B_SIZE_UNLIMITED
));
2567 BTextView::PreferredSize()
2571 _ValidateLayoutData();
2572 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
2573 fLayoutData
->preferred
);
2578 BTextView::HasHeightForWidth()
2581 return BView::HasHeightForWidth();
2583 // When not editable, we assume that all text is supposed to be visible.
2589 BTextView::GetHeightForWidth(float width
, float* min
, float* max
,
2593 BView::GetHeightForWidth(width
, min
, max
, preferred
);
2597 // TODO: don't change the actual text rect!
2598 fTextRect
.right
= fTextRect
.left
+ width
;
2599 _Refresh(0, TextLength(), false);
2602 *min
= fTextRect
.Height();
2604 *max
= fTextRect
.Height();
2605 if (preferred
!= NULL
)
2606 *preferred
= fTextRect
.Height();
2610 // #pragma mark - Layout methods
2614 BTextView::LayoutInvalidated(bool descendants
)
2618 fLayoutData
->valid
= false;
2623 BTextView::DoLayout()
2625 // Bail out, if we shan't do layout.
2626 if (!(Flags() & B_SUPPORTS_LAYOUT
))
2631 // If the user set a layout, we let the base class version call its
2638 _ValidateLayoutData();
2640 // validate current size
2641 BSize
size(Bounds().Size());
2642 if (size
.width
< fLayoutData
->min
.width
)
2643 size
.width
= fLayoutData
->min
.width
;
2644 if (size
.height
< fLayoutData
->min
.height
)
2645 size
.height
= fLayoutData
->min
.height
;
2652 BTextView::_ValidateLayoutData()
2654 if (fLayoutData
->valid
)
2659 float lineHeight
= ceilf(LineHeight(0));
2660 TRACE("line height: %.2f\n", lineHeight
);
2662 // compute our minimal size
2663 BSize
min(lineHeight
* 3, lineHeight
);
2664 min
.width
+= fLayoutData
->leftInset
+ fLayoutData
->rightInset
;
2665 min
.height
+= fLayoutData
->topInset
+ fLayoutData
->bottomInset
;
2667 fLayoutData
->min
= min
;
2669 // compute our preferred size
2670 fLayoutData
->preferred
.height
= fTextRect
.Height()
2671 + fLayoutData
->topInset
+ fLayoutData
->bottomInset
;
2674 fLayoutData
->preferred
.width
= min
.width
+ 5 * lineHeight
;
2676 float maxWidth
= fLines
->MaxWidth();
2677 if (maxWidth
< min
.width
)
2678 maxWidth
= min
.width
;
2680 fLayoutData
->preferred
.width
2681 = maxWidth
+ fLayoutData
->leftInset
+ fLayoutData
->rightInset
;
2684 fLayoutData
->valid
= true;
2685 ResetLayoutInvalidation();
2687 TRACE("width: %.2f, height: %.2f\n", min
.width
, min
.height
);
2695 BTextView::AllAttached()
2697 BView::AllAttached();
2702 BTextView::AllDetached()
2704 BView::AllDetached();
2710 BTextView::AllocRunArray(int32 entryCount
, int32
* outSize
)
2712 int32 size
= sizeof(text_run_array
) + (entryCount
- 1) * sizeof(text_run
);
2714 text_run_array
* runArray
= (text_run_array
*)calloc(size
, 1);
2715 if (runArray
== NULL
) {
2716 if (outSize
!= NULL
)
2721 runArray
->count
= entryCount
;
2723 // Call constructors explicitly as the text_run_array
2724 // was allocated with malloc (and has to, for backwards
2726 for (int32 i
= 0; i
< runArray
->count
; i
++) {
2727 new (&runArray
->runs
[i
].font
) BFont
;
2730 if (outSize
!= NULL
)
2739 BTextView::CopyRunArray(const text_run_array
* orig
, int32 countDelta
)
2741 text_run_array
* copy
= AllocRunArray(countDelta
, NULL
);
2743 for (int32 i
= 0; i
< countDelta
; i
++) {
2744 copy
->runs
[i
].offset
= orig
->runs
[i
].offset
;
2745 copy
->runs
[i
].font
= orig
->runs
[i
].font
;
2746 copy
->runs
[i
].color
= orig
->runs
[i
].color
;
2755 BTextView::FreeRunArray(text_run_array
* array
)
2760 // Call destructors explicitly
2761 for (int32 i
= 0; i
< array
->count
; i
++)
2762 array
->runs
[i
].font
.~BFont();
2770 BTextView::FlattenRunArray(const text_run_array
* runArray
, int32
* _size
)
2773 int32 size
= sizeof(flattened_text_run_array
) + (runArray
->count
- 1)
2774 * sizeof(flattened_text_run
);
2776 flattened_text_run_array
* array
= (flattened_text_run_array
*)malloc(size
);
2777 if (array
== NULL
) {
2783 array
->magic
= B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayMagic
);
2784 array
->version
= B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayVersion
);
2785 array
->count
= B_HOST_TO_BENDIAN_INT32(runArray
->count
);
2787 for (int32 i
= 0; i
< runArray
->count
; i
++) {
2788 array
->styles
[i
].offset
= B_HOST_TO_BENDIAN_INT32(
2789 runArray
->runs
[i
].offset
);
2790 runArray
->runs
[i
].font
.GetFamilyAndStyle(&array
->styles
[i
].family
,
2791 &array
->styles
[i
].style
);
2792 array
->styles
[i
].size
= B_HOST_TO_BENDIAN_FLOAT(
2793 runArray
->runs
[i
].font
.Size());
2794 array
->styles
[i
].shear
= B_HOST_TO_BENDIAN_FLOAT(
2795 runArray
->runs
[i
].font
.Shear());
2796 array
->styles
[i
].face
= B_HOST_TO_BENDIAN_INT16(
2797 runArray
->runs
[i
].font
.Face());
2798 array
->styles
[i
].red
= runArray
->runs
[i
].color
.red
;
2799 array
->styles
[i
].green
= runArray
->runs
[i
].color
.green
;
2800 array
->styles
[i
].blue
= runArray
->runs
[i
].color
.blue
;
2801 array
->styles
[i
].alpha
= 255;
2802 array
->styles
[i
]._reserved_
= 0;
2814 BTextView::UnflattenRunArray(const void* data
, int32
* _size
)
2817 flattened_text_run_array
* array
= (flattened_text_run_array
*)data
;
2819 if (B_BENDIAN_TO_HOST_INT32(array
->magic
) != kFlattenedTextRunArrayMagic
2820 || B_BENDIAN_TO_HOST_INT32(array
->version
)
2821 != kFlattenedTextRunArrayVersion
) {
2828 int32 count
= B_BENDIAN_TO_HOST_INT32(array
->count
);
2830 text_run_array
* runArray
= AllocRunArray(count
, _size
);
2831 if (runArray
== NULL
)
2834 for (int32 i
= 0; i
< count
; i
++) {
2835 runArray
->runs
[i
].offset
= B_BENDIAN_TO_HOST_INT32(
2836 array
->styles
[i
].offset
);
2838 // Set family and style independently from each other, so that
2839 // even if the family doesn't exist, we try to preserve the style
2840 runArray
->runs
[i
].font
.SetFamilyAndStyle(array
->styles
[i
].family
, NULL
);
2841 runArray
->runs
[i
].font
.SetFamilyAndStyle(NULL
, array
->styles
[i
].style
);
2843 runArray
->runs
[i
].font
.SetSize(B_BENDIAN_TO_HOST_FLOAT(
2844 array
->styles
[i
].size
));
2845 runArray
->runs
[i
].font
.SetShear(B_BENDIAN_TO_HOST_FLOAT(
2846 array
->styles
[i
].shear
));
2848 uint16 face
= B_BENDIAN_TO_HOST_INT16(array
->styles
[i
].face
);
2849 if (face
!= B_REGULAR_FACE
) {
2850 // Be's version doesn't seem to set this correctly
2851 runArray
->runs
[i
].font
.SetFace(face
);
2854 runArray
->runs
[i
].color
.red
= array
->styles
[i
].red
;
2855 runArray
->runs
[i
].color
.green
= array
->styles
[i
].green
;
2856 runArray
->runs
[i
].color
.blue
= array
->styles
[i
].blue
;
2857 runArray
->runs
[i
].color
.alpha
= array
->styles
[i
].alpha
;
2865 BTextView::InsertText(const char* text
, int32 length
, int32 offset
,
2866 const text_run_array
* runs
)
2875 else if (offset
> fText
->Length())
2876 offset
= fText
->Length();
2879 // add the text to the buffer
2880 fText
->InsertText(text
, length
, offset
);
2882 // update the start offsets of each line below offset
2883 fLines
->BumpOffset(length
, _LineAt(offset
) + 1);
2885 // update the style runs
2886 fStyles
->BumpOffset(length
, fStyles
->OffsetToRun(offset
- 1) + 1);
2888 // offset the caret/selection, if the text was inserted before it
2889 if (offset
<= fSelEnd
) {
2890 fSelStart
+= length
;
2891 fCaretOffset
= fSelEnd
= fSelStart
;
2895 if (fStylable
&& runs
!= NULL
) {
2896 _SetRunArray(offset
, offset
+ length
, runs
);
2898 // apply null-style to inserted text
2899 _ApplyStyleRange(offset
, offset
+ length
);
2905 BTextView::DeleteText(int32 fromOffset
, int32 toOffset
)
2911 else if (fromOffset
> fText
->Length())
2912 fromOffset
= fText
->Length();
2916 else if (toOffset
> fText
->Length())
2917 toOffset
= fText
->Length();
2919 if (fromOffset
>= toOffset
)
2922 // set nullStyle to style at beginning of range
2923 fStyles
->InvalidateNullStyle();
2924 fStyles
->SyncNullStyle(fromOffset
);
2926 // remove from the text buffer
2927 fText
->RemoveRange(fromOffset
, toOffset
);
2929 // remove any lines that have been obliterated
2930 fLines
->RemoveLineRange(fromOffset
, toOffset
);
2932 // remove any style runs that have been obliterated
2933 fStyles
->RemoveStyleRange(fromOffset
, toOffset
);
2935 // adjust the selection accordingly, assumes fSelEnd >= fSelStart!
2936 int32 range
= toOffset
- fromOffset
;
2937 if (fSelStart
>= toOffset
) {
2938 // selection is behind the range that was removed
2941 } else if (fSelStart
>= fromOffset
&& fSelEnd
<= toOffset
) {
2942 // the selection is within the range that was removed
2943 fSelStart
= fSelEnd
= fromOffset
;
2944 } else if (fSelStart
>= fromOffset
&& fSelEnd
> toOffset
) {
2945 // the selection starts within and ends after the range
2946 // the remaining part is the part that was after the range
2947 fSelStart
= fromOffset
;
2948 fSelEnd
= fromOffset
+ fSelEnd
- toOffset
;
2949 } else if (fSelStart
< fromOffset
&& fSelEnd
< toOffset
) {
2950 // the selection starts before, but ends within the range
2951 fSelEnd
= fromOffset
;
2952 } else if (fSelStart
< fromOffset
&& fSelEnd
>= toOffset
) {
2953 // the selection starts before and ends after the range
2959 /*! Undoes the last changes.
2961 \param clipboard A \a clipboard to use for the undo operation.
2964 BTextView::Undo(BClipboard
* clipboard
)
2967 fUndo
->Undo(clipboard
);
2972 BTextView::UndoState(bool* isRedo
) const
2974 return fUndo
== NULL
? B_UNDO_UNAVAILABLE
: fUndo
->State(isRedo
);
2978 // #pragma mark - GetDragParameters() is protected
2982 BTextView::GetDragParameters(BMessage
* drag
, BBitmap
** bitmap
, BPoint
* point
,
2989 // Add originator and action
2990 drag
->AddPointer("be:originator", this);
2991 drag
->AddInt32("be_actions", B_TRASH_TARGET
);
2994 int32 numBytes
= fSelEnd
- fSelStart
;
2995 const char* text
= fText
->GetString(fSelStart
, &numBytes
);
2996 drag
->AddData("text/plain", B_MIME_TYPE
, text
, numBytes
);
2998 // add the corresponding styles
3000 text_run_array
* styles
= RunArray(fSelStart
, fSelEnd
, &size
);
3002 if (styles
!= NULL
) {
3003 drag
->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE
,
3006 FreeRunArray(styles
);
3012 if (handler
!= NULL
)
3017 // #pragma mark - FBC padding and forbidden methods
3020 void BTextView::_ReservedTextView3() {}
3021 void BTextView::_ReservedTextView4() {}
3022 void BTextView::_ReservedTextView5() {}
3023 void BTextView::_ReservedTextView6() {}
3024 void BTextView::_ReservedTextView7() {}
3025 void BTextView::_ReservedTextView8() {}
3026 void BTextView::_ReservedTextView9() {}
3027 void BTextView::_ReservedTextView10() {}
3028 void BTextView::_ReservedTextView11() {}
3029 void BTextView::_ReservedTextView12() {}
3032 // #pragma mark - Private methods
3035 /*! Inits the BTextView object.
3037 \param textRect The BTextView's text rect.
3038 \param initialFont The font which the BTextView will use.
3039 \param initialColor The initial color of the text.
3042 BTextView::_InitObject(BRect textRect
, const BFont
* initialFont
,
3043 const rgb_color
* initialColor
)
3046 if (initialFont
== NULL
)
3049 font
= *initialFont
;
3051 _NormalizeFont(&font
);
3053 if (initialColor
== NULL
)
3054 initialColor
= &kBlackColor
;
3056 fText
= new BPrivate::TextGapBuffer
;
3057 fLines
= new LineBuffer
;
3058 fStyles
= new StyleBuffer(&font
, initialColor
);
3060 fInstalledNavigateCommandWordwiseShortcuts
= false;
3061 fInstalledNavigateOptionWordwiseShortcuts
= false;
3062 fInstalledNavigateOptionLinewiseShortcuts
= false;
3063 fInstalledNavigateHomeEndDocwiseShortcuts
= false;
3065 fInstalledSelectCommandWordwiseShortcuts
= false;
3066 fInstalledSelectOptionWordwiseShortcuts
= false;
3067 fInstalledSelectOptionLinewiseShortcuts
= false;
3068 fInstalledSelectHomeEndDocwiseShortcuts
= false;
3070 // We put these here instead of in the constructor initializer list
3071 // to have less code duplication, and a single place where to do changes
3073 fTextRect
= textRect
;
3074 // NOTE: The only places where text rect is changed:
3075 // * width is possibly adjusted in _AutoResize(),
3076 // * height is adjusted in _RecalculateLineBreaks().
3077 // When used within the layout management framework, the
3078 // text rect is changed to maintain constant insets.
3079 fMinTextRectWidth
= fTextRect
.Width();
3080 // see SetTextRect()
3081 fSelStart
= fSelEnd
= 0;
3082 fCaretVisible
= false;
3095 fMaxBytes
= INT32_MAX
;
3096 fDisallowedChars
= NULL
;
3097 fAlignment
= B_ALIGN_LEFT
;
3098 fAutoindent
= false;
3100 fColorSpace
= B_CMAP8
;
3102 fContainerView
= NULL
;
3106 fClickRunner
= NULL
;
3107 fTrackingMouse
= NULL
;
3109 fLayoutData
= new LayoutData
;
3110 fLayoutData
->UpdateInsets(Bounds().OffsetToCopy(B_ORIGIN
), fTextRect
);
3112 fLastClickOffset
= -1;
3118 //! Handles when Backspace key is pressed.
3120 BTextView::_HandleBackspace()
3123 TypingUndoBuffer
* undoBuffer
= dynamic_cast<TypingUndoBuffer
*>(
3127 fUndo
= undoBuffer
= new TypingUndoBuffer(this);
3129 undoBuffer
->BackwardErase();
3132 if (fSelStart
== fSelEnd
) {
3136 fSelStart
= _PreviousInitialByte(fSelStart
);
3138 Highlight(fSelStart
, fSelEnd
);
3140 DeleteText(fSelStart
, fSelEnd
);
3141 fCaretOffset
= fSelEnd
= fSelStart
;
3143 _Refresh(fSelStart
, fSelEnd
, true);
3147 //! Handles when an arrow key is pressed.
3149 BTextView::_HandleArrowKey(uint32 arrowKey
, int32 modifiers
)
3151 // return if there's nowhere to go
3152 if (fText
->Length() == 0)
3155 int32 selStart
= fSelStart
;
3156 int32 selEnd
= fSelEnd
;
3158 if (modifiers
< 0) {
3159 BMessage
* currentMessage
= Window()->CurrentMessage();
3160 if (currentMessage
== NULL
3161 || currentMessage
->FindInt32("modifiers", &modifiers
) != B_OK
) {
3166 bool shiftKeyDown
= (modifiers
& B_SHIFT_KEY
) != 0;
3167 bool controlKeyDown
= (modifiers
& B_CONTROL_KEY
) != 0;
3168 bool optionKeyDown
= (modifiers
& B_OPTION_KEY
) != 0;
3169 bool commandKeyDown
= (modifiers
& B_COMMAND_KEY
) != 0;
3171 int32 lastClickOffset
= fCaretOffset
;
3176 _ScrollBy(-1 * kHorizontalScrollBarStep
, 0);
3177 else if (fSelStart
!= fSelEnd
&& !shiftKeyDown
)
3178 fCaretOffset
= fSelStart
;
3180 if ((commandKeyDown
|| optionKeyDown
) && !controlKeyDown
)
3181 fCaretOffset
= _PreviousWordStart(fCaretOffset
- 1);
3183 fCaretOffset
= _PreviousInitialByte(fCaretOffset
);
3185 if (shiftKeyDown
&& fCaretOffset
!= lastClickOffset
) {
3186 if (fCaretOffset
< fSelStart
) {
3187 // extend selection to the left
3188 selStart
= fCaretOffset
;
3189 if (lastClickOffset
> fSelStart
) {
3190 // caret has jumped across "anchor"
3194 // shrink selection from the right
3195 selEnd
= fCaretOffset
;
3203 _ScrollBy(kHorizontalScrollBarStep
, 0);
3204 else if (fSelStart
!= fSelEnd
&& !shiftKeyDown
)
3205 fCaretOffset
= fSelEnd
;
3207 if ((commandKeyDown
|| optionKeyDown
) && !controlKeyDown
)
3208 fCaretOffset
= _NextWordEnd(fCaretOffset
);
3210 fCaretOffset
= _NextInitialByte(fCaretOffset
);
3212 if (shiftKeyDown
&& fCaretOffset
!= lastClickOffset
) {
3213 if (fCaretOffset
> fSelEnd
) {
3214 // extend selection to the right
3215 selEnd
= fCaretOffset
;
3216 if (lastClickOffset
< fSelEnd
) {
3217 // caret has jumped across "anchor"
3221 // shrink selection from the left
3222 selStart
= fCaretOffset
;
3231 _ScrollBy(0, -1 * kVerticalScrollBarStep
);
3232 else if (fSelStart
!= fSelEnd
&& !shiftKeyDown
)
3233 fCaretOffset
= fSelStart
;
3235 if (optionKeyDown
&& !commandKeyDown
&& !controlKeyDown
)
3236 fCaretOffset
= _PreviousLineStart(fCaretOffset
);
3237 else if (commandKeyDown
&& !optionKeyDown
&& !controlKeyDown
) {
3242 BPoint point
= PointAt(fCaretOffset
, &height
);
3243 // find the caret position on the previous
3244 // line by gently stepping onto this line
3245 for (int i
= 1; i
<= height
; i
++) {
3247 int32 offset
= OffsetAt(point
);
3248 if (offset
< fCaretOffset
|| i
== height
) {
3249 fCaretOffset
= offset
;
3255 if (shiftKeyDown
&& fCaretOffset
!= lastClickOffset
) {
3256 if (fCaretOffset
< fSelStart
) {
3257 // extend selection to the top
3258 selStart
= fCaretOffset
;
3259 if (lastClickOffset
> fSelStart
) {
3260 // caret has jumped across "anchor"
3264 // shrink selection from the bottom
3265 selEnd
= fCaretOffset
;
3275 _ScrollBy(0, kVerticalScrollBarStep
);
3276 else if (fSelStart
!= fSelEnd
&& !shiftKeyDown
)
3277 fCaretOffset
= fSelEnd
;
3279 if (optionKeyDown
&& !commandKeyDown
&& !controlKeyDown
)
3280 fCaretOffset
= _NextLineEnd(fCaretOffset
);
3281 else if (commandKeyDown
&& !optionKeyDown
&& !controlKeyDown
) {
3282 _ScrollTo(0, fTextRect
.bottom
+ fLayoutData
->bottomInset
);
3283 fCaretOffset
= fText
->Length();
3286 BPoint point
= PointAt(fCaretOffset
, &height
);
3288 fCaretOffset
= OffsetAt(point
);
3291 if (shiftKeyDown
&& fCaretOffset
!= lastClickOffset
) {
3292 if (fCaretOffset
> fSelEnd
) {
3293 // extend selection to the bottom
3294 selEnd
= fCaretOffset
;
3295 if (lastClickOffset
< fSelEnd
) {
3296 // caret has jumped across "anchor"
3300 // shrink selection from the top
3301 selStart
= fCaretOffset
;
3309 fStyles
->InvalidateNullStyle();
3313 Select(selStart
, selEnd
);
3315 Select(fCaretOffset
, fCaretOffset
);
3318 ScrollToOffset(fCaretOffset
);
3323 //! Handles when the Delete key is pressed.
3325 BTextView::_HandleDelete()
3328 TypingUndoBuffer
* undoBuffer
= dynamic_cast<TypingUndoBuffer
*>(
3332 fUndo
= undoBuffer
= new TypingUndoBuffer(this);
3334 undoBuffer
->ForwardErase();
3337 if (fSelStart
== fSelEnd
) {
3338 if (fSelEnd
== fText
->Length())
3341 fSelEnd
= _NextInitialByte(fSelEnd
);
3343 Highlight(fSelStart
, fSelEnd
);
3345 DeleteText(fSelStart
, fSelEnd
);
3346 fCaretOffset
= fSelEnd
= fSelStart
;
3348 _Refresh(fSelStart
, fSelEnd
, true);
3352 //! Handles when the Page Up, Page Down, Home, or End key is pressed.
3354 BTextView::_HandlePageKey(uint32 pageKey
, int32 modifiers
)
3356 if (modifiers
< 0) {
3357 BMessage
* currentMessage
= Window()->CurrentMessage();
3358 if (currentMessage
== NULL
3359 || currentMessage
->FindInt32("modifiers", &modifiers
) != B_OK
) {
3364 bool shiftKeyDown
= (modifiers
& B_SHIFT_KEY
) != 0;
3365 bool controlKeyDown
= (modifiers
& B_CONTROL_KEY
) != 0;
3366 bool optionKeyDown
= (modifiers
& B_OPTION_KEY
) != 0;
3367 bool commandKeyDown
= (modifiers
& B_COMMAND_KEY
) != 0;
3369 STELine
* line
= NULL
;
3370 int32 selStart
= fSelStart
;
3371 int32 selEnd
= fSelEnd
;
3373 int32 lastClickOffset
= fCaretOffset
;
3381 if (commandKeyDown
&& !optionKeyDown
&& !controlKeyDown
) {
3385 // get the start of the last line if caret is on it
3386 line
= (*fLines
)[_LineAt(lastClickOffset
)];
3387 fCaretOffset
= line
->offset
;
3391 selStart
= selEnd
= fCaretOffset
;
3392 else if (fCaretOffset
!= lastClickOffset
) {
3393 if (fCaretOffset
< fSelStart
) {
3394 // extend selection to the left
3395 selStart
= fCaretOffset
;
3396 if (lastClickOffset
> fSelStart
) {
3397 // caret has jumped across "anchor"
3401 // shrink selection from the right
3402 selEnd
= fCaretOffset
;
3410 fCaretOffset
= fText
->Length();
3411 _ScrollTo(0, fTextRect
.bottom
+ fLayoutData
->bottomInset
);
3414 if (commandKeyDown
&& !optionKeyDown
&& !controlKeyDown
) {
3415 _ScrollTo(0, fTextRect
.bottom
+ fLayoutData
->bottomInset
);
3416 fCaretOffset
= fText
->Length();
3418 // If we are on the last line, just go to the last
3419 // character in the buffer, otherwise get the starting
3420 // offset of the next line, and go to the previous character
3421 int32 currentLine
= _LineAt(lastClickOffset
);
3422 if (currentLine
+ 1 < fLines
->NumLines()) {
3423 line
= (*fLines
)[currentLine
+ 1];
3424 fCaretOffset
= _PreviousInitialByte(line
->offset
);
3426 // This check is needed to avoid moving the cursor
3427 // when the cursor is on the last line, and that line
3429 if (fCaretOffset
!= fText
->Length()) {
3430 fCaretOffset
= fText
->Length();
3431 if (ByteAt(fCaretOffset
- 1) == B_ENTER
)
3438 selStart
= selEnd
= fCaretOffset
;
3439 else if (fCaretOffset
!= lastClickOffset
) {
3440 if (fCaretOffset
> fSelEnd
) {
3441 // extend selection to the right
3442 selEnd
= fCaretOffset
;
3443 if (lastClickOffset
< fSelEnd
) {
3444 // caret has jumped across "anchor"
3448 // shrink selection from the left
3449 selStart
= fCaretOffset
;
3458 BPoint currentPos
= PointAt(fCaretOffset
, &lineHeight
);
3459 BPoint
nextPos(currentPos
.x
,
3460 currentPos
.y
+ lineHeight
- Bounds().Height());
3461 fCaretOffset
= OffsetAt(nextPos
);
3462 nextPos
= PointAt(fCaretOffset
);
3463 _ScrollBy(0, nextPos
.y
- currentPos
.y
);
3469 selStart
= selEnd
= fCaretOffset
;
3470 else if (fCaretOffset
!= lastClickOffset
) {
3471 if (fCaretOffset
< fSelStart
) {
3472 // extend selection to the top
3473 selStart
= fCaretOffset
;
3474 if (lastClickOffset
> fSelStart
) {
3475 // caret has jumped across "anchor"
3479 // shrink selection from the bottom
3480 selEnd
= fCaretOffset
;
3489 BPoint currentPos
= PointAt(fCaretOffset
);
3490 BPoint
nextPos(currentPos
.x
, currentPos
.y
+ Bounds().Height());
3491 fCaretOffset
= OffsetAt(nextPos
);
3492 nextPos
= PointAt(fCaretOffset
);
3493 _ScrollBy(0, nextPos
.y
- currentPos
.y
);
3499 selStart
= selEnd
= fCaretOffset
;
3500 else if (fCaretOffset
!= lastClickOffset
) {
3501 if (fCaretOffset
> fSelEnd
) {
3502 // extend selection to the bottom
3503 selEnd
= fCaretOffset
;
3504 if (lastClickOffset
< fSelEnd
) {
3505 // caret has jumped across "anchor"
3509 // shrink selection from the top
3510 selStart
= fCaretOffset
;
3520 Select(selStart
, selEnd
);
3522 Select(fCaretOffset
, fCaretOffset
);
3524 ScrollToOffset(fCaretOffset
);
3529 /*! Handles when an alpha-numeric key is pressed.
3531 \param bytes The string or character associated with the key.
3532 \param numBytes The amount of bytes containes in "bytes".
3535 BTextView::_HandleAlphaKey(const char* bytes
, int32 numBytes
)
3537 // TODO: block input if not editable (Andrew)
3539 TypingUndoBuffer
* undoBuffer
= dynamic_cast<TypingUndoBuffer
*>(fUndo
);
3542 fUndo
= undoBuffer
= new TypingUndoBuffer(this);
3544 undoBuffer
->InputCharacter(numBytes
);
3547 if (fSelStart
!= fSelEnd
) {
3548 Highlight(fSelStart
, fSelEnd
);
3549 DeleteText(fSelStart
, fSelEnd
);
3552 if (fAutoindent
&& numBytes
== 1 && *bytes
== B_ENTER
) {
3553 int32 start
, offset
;
3554 start
= offset
= OffsetAt(_LineAt(fSelStart
));
3556 while (ByteAt(offset
) != '\0' &&
3557 (ByteAt(offset
) == B_TAB
|| ByteAt(offset
) == B_SPACE
)
3558 && offset
< fSelStart
)
3561 _DoInsertText(bytes
, numBytes
, fSelStart
, NULL
);
3562 if (start
!= offset
)
3563 _DoInsertText(Text() + start
, offset
- start
, fSelStart
, NULL
);
3565 _DoInsertText(bytes
, numBytes
, fSelStart
, NULL
);
3567 fCaretOffset
= fSelEnd
;
3569 ScrollToOffset(fCaretOffset
);
3573 /*! Redraw the text between the two given offsets, recalculating line-breaks
3576 \param fromOffset The offset from where to refresh.
3577 \param toOffset The offset where to refresh to.
3578 \param scroll If \c true, scroll the view to the end offset.
3581 BTextView::_Refresh(int32 fromOffset
, int32 toOffset
, bool scroll
)
3584 float saveHeight
= fTextRect
.Height();
3585 float saveWidth
= fTextRect
.Width();
3586 int32 fromLine
= _LineAt(fromOffset
);
3587 int32 toLine
= _LineAt(toOffset
);
3588 int32 saveFromLine
= fromLine
;
3589 int32 saveToLine
= toLine
;
3591 _RecalculateLineBreaks(&fromLine
, &toLine
);
3593 // TODO: Maybe there is still something we can do without a window...
3597 BRect bounds
= Bounds();
3598 float newHeight
= fTextRect
.Height();
3600 // if the line breaks have changed, force an erase
3601 if (fromLine
!= saveFromLine
|| toLine
!= saveToLine
3602 || newHeight
!= saveHeight
) {
3606 if (newHeight
!= saveHeight
) {
3607 // the text area has changed
3608 if (newHeight
< saveHeight
)
3609 toLine
= _LineAt(BPoint(0.0f
, saveHeight
+ fTextRect
.top
));
3611 toLine
= _LineAt(BPoint(0.0f
, newHeight
+ fTextRect
.top
));
3614 // draw only those lines that are visible
3615 int32 fromVisible
= _LineAt(BPoint(0.0f
, bounds
.top
));
3616 int32 toVisible
= _LineAt(BPoint(0.0f
, bounds
.bottom
));
3617 fromLine
= max_c(fromVisible
, fromLine
);
3618 toLine
= min_c(toLine
, toVisible
);
3622 _RequestDrawLines(fromLine
, toLine
);
3624 // erase the area below the text
3625 BRect eraseRect
= bounds
;
3626 eraseRect
.top
= fTextRect
.top
+ (*fLines
)[fLines
->NumLines()]->origin
;
3627 eraseRect
.bottom
= fTextRect
.top
+ saveHeight
;
3628 if (eraseRect
.bottom
> eraseRect
.top
&& eraseRect
.Intersects(bounds
)) {
3629 SetLowColor(ViewColor());
3630 FillRect(eraseRect
, B_SOLID_LOW
);
3633 // update the scroll bars if the text area has changed
3634 if (newHeight
!= saveHeight
|| fMinTextRectWidth
!= saveWidth
)
3635 _UpdateScrollbars();
3638 ScrollToOffset(fSelEnd
);
3644 /*! Recalculate line breaks between two lines.
3646 \param startLine The line number to start recalculating line breaks.
3647 \param endLine The line number to stop recalculating line breaks.
3650 BTextView::_RecalculateLineBreaks(int32
* startLine
, int32
* endLine
)
3655 *startLine
= (*startLine
< 0) ? 0 : *startLine
;
3656 *endLine
= (*endLine
> fLines
->NumLines() - 1) ? fLines
->NumLines() - 1
3659 int32 textLength
= fText
->Length();
3660 int32 lineIndex
= (*startLine
> 0) ? *startLine
- 1 : 0;
3661 int32 recalThreshold
= (*fLines
)[*endLine
+ 1]->offset
;
3662 float width
= max_c(fTextRect
.Width(), 10);
3663 // TODO: The minimum width of 10 is a work around for the following
3664 // problem: If the text rect is too small, we are not calculating any
3665 // line heights, not even for the first line. Maybe this is a bug
3666 // in the algorithm, but other places in the class rely on at least
3667 // the first line to return a valid height. Maybe "10" should really
3668 // be the width of the very first glyph instead.
3669 STELine
* curLine
= (*fLines
)[lineIndex
];
3670 STELine
* nextLine
= curLine
+ 1;
3673 float ascent
, descent
;
3674 int32 fromOffset
= curLine
->offset
;
3675 int32 toOffset
= _FindLineBreak(fromOffset
, &ascent
, &descent
, &width
);
3677 curLine
->ascent
= ascent
;
3678 curLine
->width
= width
;
3680 // we want to advance at least by one character
3681 int32 nextOffset
= _NextInitialByte(fromOffset
);
3682 if (toOffset
< nextOffset
&& fromOffset
< textLength
)
3683 toOffset
= nextOffset
;
3686 STELine saveLine
= *nextLine
;
3687 if (lineIndex
> fLines
->NumLines() || toOffset
< nextLine
->offset
) {
3688 // the new line comes before the old line start, add a line
3690 newLine
.offset
= toOffset
;
3691 newLine
.origin
= ceilf(curLine
->origin
+ ascent
+ descent
) + 1;
3693 fLines
->InsertLine(&newLine
, lineIndex
);
3695 // update the existing line
3696 nextLine
->offset
= toOffset
;
3697 nextLine
->origin
= ceilf(curLine
->origin
+ ascent
+ descent
) + 1;
3699 // remove any lines that start before the current line
3700 while (lineIndex
< fLines
->NumLines()
3701 && toOffset
>= ((*fLines
)[lineIndex
] + 1)->offset
) {
3702 fLines
->RemoveLines(lineIndex
+ 1);
3705 nextLine
= (*fLines
)[lineIndex
];
3706 if (nextLine
->offset
== saveLine
.offset
) {
3707 if (nextLine
->offset
>= recalThreshold
) {
3708 if (nextLine
->origin
!= saveLine
.origin
)
3709 fLines
->BumpOrigin(nextLine
->origin
- saveLine
.origin
,
3714 if (lineIndex
> 0 && lineIndex
== *startLine
)
3715 *startLine
= lineIndex
- 1;
3719 curLine
= (*fLines
)[lineIndex
];
3720 nextLine
= curLine
+ 1;
3721 } while (curLine
->offset
< textLength
);
3723 // make sure that the sentinel line (which starts at the end of the buffer)
3724 // has always a width of 0
3725 (*fLines
)[fLines
->NumLines()]->width
= 0;
3727 // update the text rect
3728 float newHeight
= TextHeight(0, fLines
->NumLines() - 1);
3729 fTextRect
.bottom
= fTextRect
.top
+ newHeight
;
3731 fMinTextRectWidth
= fLines
->MaxWidth();
3732 fTextRect
.right
= ceilf(fTextRect
.left
+ fMinTextRectWidth
);
3735 *endLine
= lineIndex
- 1;
3736 *startLine
= min_c(*startLine
, *endLine
);
3741 BTextView::_FindLineBreak(int32 fromOffset
, float* _ascent
, float* _descent
,
3747 const int32 limit
= fText
->Length();
3749 // is fromOffset at the end?
3750 if (fromOffset
>= limit
) {
3751 // try to return valid height info anyway
3752 if (fStyles
->NumRuns() > 0) {
3753 fStyles
->Iterate(fromOffset
, 1, fInline
, NULL
, NULL
, _ascent
,
3756 if (fStyles
->IsValidNullStyle()) {
3757 const BFont
* font
= NULL
;
3758 fStyles
->GetNullStyle(&font
, NULL
);
3761 font
->GetHeight(&fh
);
3762 *_ascent
= fh
.ascent
;
3763 *_descent
= fh
.descent
+ fh
.leading
;
3771 int32 offset
= fromOffset
;
3774 // Text wrapping is turned off.
3775 // Just find the offset of the first \n character
3776 offset
= limit
- fromOffset
;
3777 fText
->FindChar(B_ENTER
, fromOffset
, &offset
);
3778 offset
+= fromOffset
;
3779 int32 toOffset
= (offset
< limit
) ? offset
: limit
;
3781 *inOutWidth
= _TabExpandedStyledWidth(fromOffset
, toOffset
- fromOffset
,
3784 return offset
< limit
? offset
+ 1 : limit
;
3789 float descent
= 0.0;
3791 float deltaWidth
= 0.0;
3792 float strWidth
= 0.0;
3796 while (offset
< limit
&& !done
) {
3797 // find the next line break candidate
3798 for (; (offset
+ delta
) < limit
; delta
++) {
3799 if (CanEndLine(offset
+ delta
)) {
3800 theChar
= fText
->RealCharAt(offset
+ delta
);
3801 if (theChar
!= B_SPACE
&& theChar
!= B_TAB
3802 && theChar
!= B_ENTER
) {
3803 // we are scanning for trailing whitespace below, so we
3804 // have to skip non-whitespace characters, that can end
3812 int32 deltaBeforeWhitespace
= delta
;
3813 // now skip over trailing whitespace, if any
3814 for (; (offset
+ delta
) < limit
; delta
++) {
3815 theChar
= fText
->RealCharAt(offset
+ delta
);
3816 if (theChar
== B_ENTER
) {
3817 // found a newline, we're done!
3821 } else if (theChar
!= B_SPACE
&& theChar
!= B_TAB
) {
3822 // stop at anything else than trailing whitespace
3827 delta
= max_c(delta
, 1);
3829 // do not include B_ENTER-terminator into width & height calculations
3830 deltaWidth
= _TabExpandedStyledWidth(offset
,
3831 done
? delta
- 1 : delta
, &ascent
, &descent
);
3832 strWidth
+= deltaWidth
;
3834 if (strWidth
>= *inOutWidth
) {
3835 // we've found where the line will wrap
3838 // we have included trailing whitespace in the width computation
3839 // above, but that is not being shown anyway, so we try again
3840 // without the trailing whitespace
3841 if (delta
== deltaBeforeWhitespace
) {
3842 // there is no trailing whitespace, no point in trying
3846 // reset string width to start of current run ...
3847 strWidth
-= deltaWidth
;
3849 // ... and compute the resulting width (of visible characters)
3850 strWidth
+= _StyledWidth(offset
, deltaBeforeWhitespace
, NULL
, NULL
);
3851 if (strWidth
>= *inOutWidth
) {
3852 // width of visible characters exceeds line, we need to wrap
3853 // before the current "word"
3858 *_ascent
= max_c(ascent
, *_ascent
);
3859 *_descent
= max_c(descent
, *_descent
);
3865 if (offset
- fromOffset
< 1) {
3866 // there weren't any words that fit entirely in this line
3867 // force a break in the middle of a word
3872 int32 current
= fromOffset
;
3873 for (offset
= _NextInitialByte(current
); current
< limit
;
3874 current
= offset
, offset
= _NextInitialByte(offset
)) {
3875 strWidth
+= _StyledWidth(current
, offset
- current
, &ascent
,
3877 if (strWidth
>= *inOutWidth
) {
3878 offset
= _PreviousInitialByte(offset
);
3882 *_ascent
= max_c(ascent
, *_ascent
);
3883 *_descent
= max_c(descent
, *_descent
);
3887 return min_c(offset
, limit
);
3892 BTextView::_PreviousLineStart(int32 offset
)
3897 while (offset
> 0) {
3898 offset
= _PreviousInitialByte(offset
);
3899 if (_CharClassification(offset
) == CHAR_CLASS_WHITESPACE
3900 && ByteAt(offset
) == B_ENTER
) {
3910 BTextView::_NextLineEnd(int32 offset
)
3912 int32 textLen
= fText
->Length();
3913 if (offset
>= textLen
)
3916 while (offset
< textLen
) {
3917 if (_CharClassification(offset
) == CHAR_CLASS_WHITESPACE
3918 && ByteAt(offset
) == B_ENTER
) {
3921 offset
= _NextInitialByte(offset
);
3929 BTextView::_PreviousWordBoundary(int32 offset
)
3931 uint32 charType
= _CharClassification(offset
);
3933 while (offset
> 0) {
3934 previous
= _PreviousInitialByte(offset
);
3935 if (_CharClassification(previous
) != charType
)
3945 BTextView::_NextWordBoundary(int32 offset
)
3947 int32 textLen
= fText
->Length();
3948 uint32 charType
= _CharClassification(offset
);
3949 while (offset
< textLen
) {
3950 offset
= _NextInitialByte(offset
);
3951 if (_CharClassification(offset
) != charType
)
3960 BTextView::_PreviousWordStart(int32 offset
)
3966 // need to look at previous char
3967 if (_CharClassification(offset
) != CHAR_CLASS_DEFAULT
) {
3968 // skip non-word characters
3969 while (offset
> 0) {
3970 offset
= _PreviousInitialByte(offset
);
3971 if (_CharClassification(offset
) == CHAR_CLASS_DEFAULT
)
3975 while (offset
> 0) {
3976 // skip to start of word
3977 int32 previous
= _PreviousInitialByte(offset
);
3978 if (_CharClassification(previous
) != CHAR_CLASS_DEFAULT
)
3988 BTextView::_NextWordEnd(int32 offset
)
3990 int32 textLen
= fText
->Length();
3991 if (_CharClassification(offset
) != CHAR_CLASS_DEFAULT
) {
3992 // skip non-word characters
3993 while (offset
< textLen
) {
3994 offset
= _NextInitialByte(offset
);
3995 if (_CharClassification(offset
) == CHAR_CLASS_DEFAULT
)
3999 while (offset
< textLen
) {
4000 // skip to end of word
4001 offset
= _NextInitialByte(offset
);
4002 if (_CharClassification(offset
) != CHAR_CLASS_DEFAULT
)
4010 /*! Returns the width used by the characters starting at the given
4011 offset with the given length, expanding all tab characters as needed.
4014 BTextView::_TabExpandedStyledWidth(int32 offset
, int32 length
, float* _ascent
,
4015 float* _descent
) const
4018 float descent
= 0.0;
4019 float maxAscent
= 0.0;
4020 float maxDescent
= 0.0;
4023 int32 numBytes
= length
;
4024 bool foundTab
= false;
4026 foundTab
= fText
->FindChar(B_TAB
, offset
, &numBytes
);
4027 width
+= _StyledWidth(offset
, numBytes
, &ascent
, &descent
);
4029 if (maxAscent
< ascent
)
4031 if (maxDescent
< descent
)
4032 maxDescent
= descent
;
4035 width
+= _ActualTabWidth(width
);
4042 } while (foundTab
&& length
> 0);
4044 if (_ascent
!= NULL
)
4045 *_ascent
= maxAscent
;
4046 if (_descent
!= NULL
)
4047 *_descent
= maxDescent
;
4053 /*! Calculate the width of the text within the given limits.
4055 \param fromOffset The offset where to start.
4056 \param length The length of the text to examine.
4057 \param _ascent A pointer to a float which will contain the maximum ascent.
4058 \param _descent A pointer to a float which will contain the maximum descent.
4060 \return The width for the text within the given limits.
4063 BTextView::_StyledWidth(int32 fromOffset
, int32 length
, float* _ascent
,
4064 float* _descent
) const
4067 // determine height of char at given offset, but return empty width
4068 fStyles
->Iterate(fromOffset
, 1, fInline
, NULL
, NULL
, _ascent
,
4075 float descent
= 0.0;
4076 float maxAscent
= 0.0;
4077 float maxDescent
= 0.0;
4079 // iterate through the style runs
4080 const BFont
* font
= NULL
;
4082 while ((numBytes
= fStyles
->Iterate(fromOffset
, length
, fInline
, &font
,
4083 NULL
, &ascent
, &descent
)) != 0) {
4084 maxAscent
= max_c(ascent
, maxAscent
);
4085 maxDescent
= max_c(descent
, maxDescent
);
4088 // Use _BWidthBuffer_ if possible
4089 if (BPrivate::gWidthBuffer
!= NULL
) {
4090 result
+= BPrivate::gWidthBuffer
->StringWidth(*fText
, fromOffset
,
4094 const char* text
= fText
->GetString(fromOffset
, &numBytes
);
4095 result
+= font
->StringWidth(text
, numBytes
);
4101 fromOffset
+= numBytes
;
4105 if (_ascent
!= NULL
)
4106 *_ascent
= maxAscent
;
4107 if (_descent
!= NULL
)
4108 *_descent
= maxDescent
;
4114 //! Calculate the actual tab width for the given location.
4116 BTextView::_ActualTabWidth(float location
) const
4118 float tabWidth
= fTabWidth
- fmod(location
, fTabWidth
);
4119 if (round(tabWidth
) == 0)
4120 tabWidth
= fTabWidth
;
4127 BTextView::_DoInsertText(const char* text
, int32 length
, int32 offset
,
4128 const text_run_array
* runs
)
4130 _CancelInputMethod();
4132 if (TextLength() + length
> MaxBytes())
4135 if (fSelStart
!= fSelEnd
)
4136 Select(fSelStart
, fSelStart
);
4138 const int32 textLength
= TextLength();
4139 if (offset
> textLength
)
4140 offset
= textLength
;
4142 // copy data into buffer
4143 InsertText(text
, length
, offset
, runs
);
4145 // recalc line breaks and draw the text
4146 _Refresh(offset
, offset
+ length
, false);
4151 BTextView::_DoDeleteText(int32 fromOffset
, int32 toOffset
)
4158 BTextView::_DrawLine(BView
* view
, const int32
&lineNum
,
4159 const int32
&startOffset
, const bool &erase
, BRect
&eraseRect
,
4160 BRegion
&inputRegion
)
4162 STELine
* line
= (*fLines
)[lineNum
];
4163 float startLeft
= fTextRect
.left
;
4164 if (startOffset
!= -1) {
4165 if (ByteAt(startOffset
) == B_ENTER
) {
4166 // StartOffset is a newline
4167 startLeft
= PointAt(line
->offset
).x
;
4169 startLeft
= PointAt(startOffset
).x
;
4171 else if (fAlignment
!= B_ALIGN_LEFT
) {
4172 float alignmentOffset
= fTextRect
.Width() - LineWidth(lineNum
);
4173 if (fAlignment
== B_ALIGN_CENTER
)
4174 alignmentOffset
/= 2;
4175 startLeft
= fTextRect
.left
+ alignmentOffset
;
4178 int32 length
= (line
+ 1)->offset
;
4179 if (startOffset
!= -1)
4180 length
-= startOffset
;
4182 length
-= line
->offset
;
4184 // DrawString() chokes if you draw a newline
4185 if (ByteAt((line
+ 1)->offset
- 1) == B_ENTER
)
4188 view
->MovePenTo(startLeft
, line
->origin
+ line
->ascent
+ fTextRect
.top
+ 1);
4191 eraseRect
.top
= line
->origin
+ fTextRect
.top
;
4192 eraseRect
.bottom
= (line
+ 1)->origin
+ fTextRect
.top
;
4193 view
->FillRect(eraseRect
, B_SOLID_LOW
);
4196 // do we have any text to draw?
4200 bool foundTab
= false;
4203 int32 offset
= startOffset
!= -1 ? startOffset
: line
->offset
;
4204 const BFont
* font
= NULL
;
4205 const rgb_color
* color
= NULL
;
4207 drawing_mode defaultTextRenderingMode
= DrawingMode();
4208 // iterate through each style on this line
4209 while ((numBytes
= fStyles
->Iterate(offset
, length
, fInline
, &font
,
4211 view
->SetFont(font
);
4212 view
->SetHighColor(*color
);
4214 tabChars
= min_c(numBytes
, length
);
4216 foundTab
= fText
->FindChar(B_TAB
, offset
, &tabChars
);
4220 if (ByteAt(offset
+ tabChars
+ numTabs
) != B_TAB
)
4222 } while ((tabChars
+ numTabs
) < numBytes
);
4225 drawing_mode textRenderingMode
= defaultTextRenderingMode
;
4227 if (inputRegion
.CountRects() > 0
4228 && ((offset
<= fInline
->Offset()
4229 && fInline
->Offset() < offset
+ tabChars
)
4230 || (fInline
->Offset() <= offset
4231 && offset
< fInline
->Offset() + fInline
->Length()))) {
4233 textRenderingMode
= B_OP_OVER
;
4236 GetTextRegion(offset
, offset
+ length
, &textRegion
);
4238 textRegion
.IntersectWith(&inputRegion
);
4241 // Highlight in blue the inputted text
4242 view
->SetHighColor(kBlueInputColor
);
4243 view
->FillRect(textRegion
.Frame());
4245 // Highlight in red the selected part
4246 if (fInline
->SelectionLength() > 0) {
4247 BRegion selectedRegion
;
4248 GetTextRegion(fInline
->Offset()
4249 + fInline
->SelectionOffset(), fInline
->Offset()
4250 + fInline
->SelectionOffset()
4251 + fInline
->SelectionLength(), &selectedRegion
);
4253 textRegion
.IntersectWith(&selectedRegion
);
4255 view
->SetHighColor(kRedInputColor
);
4256 view
->FillRect(textRegion
.Frame());
4262 int32 returnedBytes
= tabChars
;
4263 const char* stringToDraw
= fText
->GetString(offset
, &returnedBytes
);
4264 view
->SetDrawingMode(textRenderingMode
);
4265 view
->DrawString(stringToDraw
, returnedBytes
);
4267 float penPos
= PenLocation().x
- fTextRect
.left
;
4268 float tabWidth
= _ActualTabWidth(penPos
);
4270 tabWidth
+= ((numTabs
- 1) * fTabWidth
);
4272 view
->MovePenBy(tabWidth
, 0.0);
4273 tabChars
+= numTabs
;
4278 numBytes
-= tabChars
;
4279 tabChars
= min_c(numBytes
, length
);
4281 } while (foundTab
&& tabChars
> 0);
4287 BTextView::_DrawLines(int32 startLine
, int32 endLine
, int32 startOffset
,
4294 BRect
textRect(fTextRect
);
4296 = Bounds().Width() - fLayoutData
->leftInset
- fLayoutData
->rightInset
;
4297 if (textRect
.Width() < minWidth
)
4298 textRect
.right
= textRect
.left
+ minWidth
;
4299 BRect clipRect
= Bounds() & textRect
;
4300 clipRect
.InsetBy(-1, -1);
4303 newClip
.Set(clipRect
);
4304 ConstrainClippingRegion(&newClip
);
4306 // set the low color to the view color so that
4307 // drawing to a non-white background will work
4308 SetLowColor(ViewColor());
4311 if (fOffscreen
== NULL
)
4315 view
= fOffscreen
->ChildAt(0);
4316 view
->SetLowColor(ViewColor());
4317 view
->FillRect(view
->Bounds(), B_SOLID_LOW
);
4320 long maxLine
= fLines
->NumLines() - 1;
4323 if (endLine
> maxLine
)
4326 // TODO: See if we can avoid this
4327 if (fAlignment
!= B_ALIGN_LEFT
)
4330 BRect eraseRect
= clipRect
;
4331 int32 startEraseLine
= startLine
;
4332 STELine
* line
= (*fLines
)[startLine
];
4334 if (erase
&& startOffset
!= -1 && fAlignment
== B_ALIGN_LEFT
) {
4335 // erase only to the right of startOffset
4337 int32 startErase
= startOffset
;
4339 BPoint erasePoint
= PointAt(startErase
);
4340 eraseRect
.left
= erasePoint
.x
;
4341 eraseRect
.top
= erasePoint
.y
;
4342 eraseRect
.bottom
= (line
+ 1)->origin
+ fTextRect
.top
;
4344 view
->FillRect(eraseRect
, B_SOLID_LOW
);
4346 eraseRect
= clipRect
;
4349 BRegion inputRegion
;
4350 if (fInline
!= NULL
&& fInline
->IsActive()) {
4351 GetTextRegion(fInline
->Offset(), fInline
->Offset() + fInline
->Length(),
4355 //BPoint leftTop(startLeft, line->origin);
4356 for (int32 lineNum
= startLine
; lineNum
<= endLine
; lineNum
++) {
4357 const bool eraseThisLine
= erase
&& lineNum
>= startEraseLine
;
4358 _DrawLine(view
, lineNum
, startOffset
, eraseThisLine
, eraseRect
,
4361 // Set this to -1 so the next iteration will use the line offset
4364 // draw the caret/hilite the selection
4366 if (fSelStart
!= fSelEnd
) {
4368 Highlight(fSelStart
, fSelEnd
);
4371 _DrawCaret(fSelStart
, true);
4375 if (fOffscreen
!= NULL
) {
4377 /*BPoint penLocation = view->PenLocation();
4378 BRect drawRect(leftTop.x, leftTop.y, penLocation.x, penLocation.y);
4379 DrawBitmap(fOffscreen, drawRect, drawRect);*/
4380 fOffscreen
->Unlock();
4383 ConstrainClippingRegion(NULL
);
4388 BTextView::_RequestDrawLines(int32 startLine
, int32 endLine
)
4393 long maxLine
= fLines
->NumLines() - 1;
4395 STELine
* from
= (*fLines
)[startLine
];
4396 STELine
* to
= endLine
== maxLine
? NULL
: (*fLines
)[endLine
+ 1];
4397 BRect
invalidRect(Bounds().left
, from
->origin
+ fTextRect
.top
,
4399 to
!= NULL
? to
->origin
+ fTextRect
.top
: fTextRect
.bottom
);
4400 Invalidate(invalidRect
);
4401 Window()->UpdateIfNeeded();
4406 BTextView::_DrawCaret(int32 offset
, bool visible
)
4409 BPoint caretPoint
= PointAt(offset
, &lineHeight
);
4410 caretPoint
.x
= min_c(caretPoint
.x
, fTextRect
.right
);
4413 caretRect
.left
= caretRect
.right
= caretPoint
.x
;
4414 caretRect
.top
= caretPoint
.y
;
4415 caretRect
.bottom
= caretPoint
.y
+ lineHeight
- 1;
4418 InvertRect(caretRect
);
4420 Invalidate(caretRect
);
4425 BTextView::_ShowCaret()
4427 if (fActive
&& !fCaretVisible
&& fEditable
&& fSelStart
== fSelEnd
)
4433 BTextView::_HideCaret()
4435 if (fCaretVisible
&& fSelStart
== fSelEnd
)
4440 //! Hides the caret if it is being shown, and if it's hidden, shows it.
4442 BTextView::_InvertCaret()
4444 fCaretVisible
= !fCaretVisible
;
4445 _DrawCaret(fSelStart
, fCaretVisible
);
4446 fCaretTime
= system_time();
4450 /*! Place the dragging caret at the given offset.
4452 \param offset The offset (zero based within the object's text) where to
4453 place the dragging caret. If it's -1, hide the caret.
4456 BTextView::_DragCaret(int32 offset
)
4458 // does the caret need to move?
4459 if (offset
== fDragOffset
)
4462 // hide the previous drag caret
4463 if (fDragOffset
!= -1)
4464 _DrawCaret(fDragOffset
, false);
4466 // do we have a new location?
4469 // ignore if offset is within active selection
4470 if (offset
>= fSelStart
&& offset
<= fSelEnd
) {
4476 _DrawCaret(offset
, true);
4479 fDragOffset
= offset
;
4484 BTextView::_StopMouseTracking()
4486 delete fTrackingMouse
;
4487 fTrackingMouse
= NULL
;
4492 BTextView::_PerformMouseUp(BPoint where
)
4494 if (fTrackingMouse
== NULL
)
4497 if (fTrackingMouse
->selectionRect
.IsValid())
4498 Select(fTrackingMouse
->clickOffset
, fTrackingMouse
->clickOffset
);
4500 _StopMouseTracking();
4501 // adjust cursor if necessary
4502 _TrackMouse(where
, NULL
, true);
4509 BTextView::_PerformMouseMoved(BPoint where
, uint32 code
)
4513 if (fTrackingMouse
== NULL
)
4516 int32 currentOffset
= OffsetAt(where
);
4517 if (fTrackingMouse
->selectionRect
.IsValid()) {
4518 // we are tracking the mouse for drag action, if the mouse has moved
4519 // to another index or more than three pixels from where it was clicked,
4520 // we initiate a drag now:
4521 if (currentOffset
!= fTrackingMouse
->clickOffset
4522 || fabs(fTrackingMouse
->where
.x
- where
.x
) > 3
4523 || fabs(fTrackingMouse
->where
.y
- where
.y
) > 3) {
4524 _StopMouseTracking();
4531 switch (fClickCount
) {
4533 // triple click, extend selection linewise
4534 if (currentOffset
<= fTrackingMouse
->anchor
) {
4535 fTrackingMouse
->selStart
4536 = (*fLines
)[_LineAt(currentOffset
)]->offset
;
4537 fTrackingMouse
->selEnd
= fTrackingMouse
->shiftDown
4539 : (*fLines
)[_LineAt(fTrackingMouse
->anchor
) + 1]->offset
;
4541 fTrackingMouse
->selStart
4542 = fTrackingMouse
->shiftDown
4544 : (*fLines
)[_LineAt(fTrackingMouse
->anchor
)]->offset
;
4545 fTrackingMouse
->selEnd
4546 = (*fLines
)[_LineAt(currentOffset
) + 1]->offset
;
4551 // double click, extend selection wordwise
4552 if (currentOffset
<= fTrackingMouse
->anchor
) {
4553 fTrackingMouse
->selStart
= _PreviousWordBoundary(currentOffset
);
4554 fTrackingMouse
->selEnd
4555 = fTrackingMouse
->shiftDown
4557 : _NextWordBoundary(fTrackingMouse
->anchor
);
4559 fTrackingMouse
->selStart
4560 = fTrackingMouse
->shiftDown
4562 : _PreviousWordBoundary(fTrackingMouse
->anchor
);
4563 fTrackingMouse
->selEnd
= _NextWordBoundary(currentOffset
);
4568 // new click, extend selection char by char
4569 if (currentOffset
<= fTrackingMouse
->anchor
) {
4570 fTrackingMouse
->selStart
= currentOffset
;
4571 fTrackingMouse
->selEnd
4572 = fTrackingMouse
->shiftDown
4573 ? fSelEnd
: fTrackingMouse
->anchor
;
4575 fTrackingMouse
->selStart
4576 = fTrackingMouse
->shiftDown
4577 ? fSelStart
: fTrackingMouse
->anchor
;
4578 fTrackingMouse
->selEnd
= currentOffset
;
4583 // position caret to follow the direction of the selection
4584 if (fTrackingMouse
->selEnd
!= fSelEnd
)
4585 fCaretOffset
= fTrackingMouse
->selEnd
;
4586 else if (fTrackingMouse
->selStart
!= fSelStart
)
4587 fCaretOffset
= fTrackingMouse
->selStart
;
4589 Select(fTrackingMouse
->selStart
, fTrackingMouse
->selEnd
);
4590 _TrackMouse(where
, NULL
);
4596 /*! Tracks the mouse position, doing special actions like changing the
4599 \param where The point where the mouse has moved.
4600 \param message The dragging message, if there is any.
4601 \param force Passed as second parameter of SetViewCursor()
4604 BTextView::_TrackMouse(BPoint where
, const BMessage
* message
, bool force
)
4607 GetTextRegion(fSelStart
, fSelEnd
, &textRegion
);
4609 if (message
&& AcceptsDrop(message
))
4611 else if ((fSelectable
|| fEditable
)
4612 && (fTrackingMouse
!= NULL
|| !textRegion
.Contains(where
))) {
4613 SetViewCursor(B_CURSOR_I_BEAM
, force
);
4615 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT
, force
);
4619 //! Tracks the mouse position when the user is dragging some data.
4621 BTextView::_TrackDrag(BPoint where
)
4624 if (Bounds().Contains(where
))
4625 _DragCaret(OffsetAt(where
));
4629 //! Initiates a drag operation.
4631 BTextView::_InitiateDrag()
4633 BMessage
dragMessage(B_MIME_DATA
);
4634 BBitmap
* dragBitmap
= NULL
;
4636 BHandler
* dragHandler
= NULL
;
4638 GetDragParameters(&dragMessage
, &dragBitmap
, &bitmapPoint
, &dragHandler
);
4639 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT
);
4641 if (dragBitmap
!= NULL
)
4642 DragMessage(&dragMessage
, dragBitmap
, bitmapPoint
, dragHandler
);
4645 GetTextRegion(fSelStart
, fSelEnd
, ®ion
);
4646 BRect bounds
= Bounds();
4647 BRect dragRect
= region
.Frame();
4648 if (!bounds
.Contains(dragRect
))
4649 dragRect
= bounds
& dragRect
;
4651 DragMessage(&dragMessage
, dragRect
, dragHandler
);
4654 BMessenger
messenger(this);
4655 BMessage
message(_DISPOSE_DRAG_
);
4656 fDragRunner
= new (nothrow
) BMessageRunner(messenger
, &message
, 100000);
4660 //! Handles when some data is dropped on the view.
4662 BTextView::_MessageDropped(BMessage
* message
, BPoint where
, BPoint offset
)
4667 bool internalDrop
= false;
4668 if (message
->FindPointer("be:originator", &from
) == B_OK
4669 && from
== this && fSelEnd
!= fSelStart
)
4670 internalDrop
= true;
4677 _TrackMouse(where
, NULL
);
4679 // are we sure we like this message?
4680 if (!AcceptsDrop(message
))
4683 int32 dropOffset
= OffsetAt(where
);
4684 if (dropOffset
> TextLength())
4685 dropOffset
= TextLength();
4687 // if this view initiated the drag, move instead of copy
4689 // dropping onto itself?
4690 if (dropOffset
>= fSelStart
&& dropOffset
<= fSelEnd
)
4694 ssize_t dataLength
= 0;
4695 const char* text
= NULL
;
4697 if (message
->FindData("text/plain", B_MIME_TYPE
, (const void**)&text
,
4698 &dataLength
) == B_OK
) {
4699 text_run_array
* runArray
= NULL
;
4700 ssize_t runLength
= 0;
4702 message
->FindData("application/x-vnd.Be-text_run_array",
4703 B_MIME_TYPE
, (const void**)&runArray
, &runLength
);
4706 _FilterDisallowedChars((char*)text
, dataLength
, runArray
);
4708 if (dataLength
< 1) {
4715 fUndo
= new DropUndoBuffer(this, text
, dataLength
, runArray
,
4716 runLength
, dropOffset
, internalDrop
);
4720 if (dropOffset
> fSelEnd
)
4721 dropOffset
-= dataLength
;
4725 Insert(dropOffset
, text
, dataLength
, runArray
);
4733 BTextView::_PerformAutoScrolling()
4735 // Scroll the view a bit if mouse is outside the view bounds
4736 BRect bounds
= Bounds();
4737 BPoint
scrollBy(B_ORIGIN
);
4739 // R5 does a pretty soft auto-scroll, we try to do the same by
4740 // simply scrolling the distance between cursor and border
4741 if (fWhere
.x
> bounds
.right
) {
4742 scrollBy
.x
= fWhere
.x
- bounds
.right
;
4743 } else if (fWhere
.x
< bounds
.left
) {
4744 scrollBy
.x
= fWhere
.x
- bounds
.left
; // negative value
4747 // prevent from scrolling out of view
4748 if (scrollBy
.x
!= 0.0) {
4749 float rightMax
= floorf(fTextRect
.right
+ fLayoutData
->rightInset
);
4750 if (bounds
.right
+ scrollBy
.x
> rightMax
)
4751 scrollBy
.x
= rightMax
- bounds
.right
;
4752 if (bounds
.left
+ scrollBy
.x
< 0)
4753 scrollBy
.x
= -bounds
.left
;
4756 if (CountLines() > 1) {
4757 // scroll in Y only if multiple lines!
4758 if (fWhere
.y
> bounds
.bottom
) {
4759 scrollBy
.y
= fWhere
.y
- bounds
.bottom
;
4760 } else if (fWhere
.y
< bounds
.top
) {
4761 scrollBy
.y
= fWhere
.y
- bounds
.top
; // negative value
4764 // prevent from scrolling out of view
4765 if (scrollBy
.y
!= 0.0) {
4766 float bottomMax
= floorf(fTextRect
.bottom
4767 + fLayoutData
->bottomInset
);
4768 if (bounds
.bottom
+ scrollBy
.y
> bottomMax
)
4769 scrollBy
.y
= bottomMax
- bounds
.bottom
;
4770 if (bounds
.top
+ scrollBy
.y
< 0)
4771 scrollBy
.y
= -bounds
.top
;
4775 if (scrollBy
!= B_ORIGIN
)
4776 ScrollBy(scrollBy
.x
, scrollBy
.y
);
4780 //! Updates the scrollbars associated with the object (if any).
4782 BTextView::_UpdateScrollbars()
4784 BRect
bounds(Bounds());
4785 BScrollBar
* horizontalScrollBar
= ScrollBar(B_HORIZONTAL
);
4786 BScrollBar
* verticalScrollBar
= ScrollBar(B_VERTICAL
);
4788 // do we have a horizontal scroll bar?
4789 if (horizontalScrollBar
!= NULL
) {
4790 long viewWidth
= bounds
.IntegerWidth();
4791 long dataWidth
= (long)ceilf(fTextRect
.IntegerWidth()
4792 + fLayoutData
->leftInset
+ fLayoutData
->rightInset
);
4794 long maxRange
= dataWidth
- viewWidth
;
4795 maxRange
= max_c(maxRange
, 0);
4797 horizontalScrollBar
->SetRange(0, (float)maxRange
);
4798 horizontalScrollBar
->SetProportion((float)viewWidth
/ (float)dataWidth
);
4799 horizontalScrollBar
->SetSteps(kHorizontalScrollBarStep
, dataWidth
/ 10);
4802 // how about a vertical scroll bar?
4803 if (verticalScrollBar
!= NULL
) {
4804 long viewHeight
= bounds
.IntegerHeight();
4805 long dataHeight
= (long)ceilf(fTextRect
.IntegerHeight()
4806 + fLayoutData
->topInset
+ fLayoutData
->bottomInset
);
4808 long maxRange
= dataHeight
- viewHeight
;
4809 maxRange
= max_c(maxRange
, 0);
4811 verticalScrollBar
->SetRange(0, maxRange
);
4812 verticalScrollBar
->SetProportion((float)viewHeight
/ (float)dataHeight
);
4813 verticalScrollBar
->SetSteps(kVerticalScrollBarStep
, viewHeight
);
4818 //! Scrolls by the given offsets
4820 BTextView::_ScrollBy(float horizontal
, float vertical
)
4822 BRect bounds
= Bounds();
4823 _ScrollTo(bounds
.left
+ horizontal
, bounds
.top
+ vertical
);
4827 //! Scrolls to the given position, making sure not to scroll out of bounds.
4829 BTextView::_ScrollTo(float x
, float y
)
4831 BRect bounds
= Bounds();
4832 long viewWidth
= bounds
.IntegerWidth();
4833 long viewHeight
= bounds
.IntegerHeight();
4835 if (x
> fTextRect
.right
- viewWidth
)
4836 x
= fTextRect
.right
- viewWidth
;
4840 if (y
> fTextRect
.bottom
+ fLayoutData
->bottomInset
- viewHeight
)
4841 y
= fTextRect
.bottom
+ fLayoutData
->bottomInset
- viewHeight
;
4849 //! Autoresizes the view to fit the contained text.
4851 BTextView::_AutoResize(bool redraw
)
4856 BRect bounds
= Bounds();
4857 float oldWidth
= bounds
.Width();
4858 float newWidth
= ceilf(fLayoutData
->leftInset
+ fTextRect
.Width()
4859 + fLayoutData
->rightInset
);
4861 if (fContainerView
!= NULL
) {
4862 // NOTE: This container view thing is only used by Tracker.
4863 // move container view if not left aligned
4864 if (fAlignment
== B_ALIGN_CENTER
) {
4865 if (fmod(ceilf(newWidth
- oldWidth
), 2.0) != 0.0)
4867 fContainerView
->MoveBy(ceilf(oldWidth
- newWidth
) / 2, 0);
4868 } else if (fAlignment
== B_ALIGN_RIGHT
) {
4869 fContainerView
->MoveBy(ceilf(oldWidth
- newWidth
), 0);
4871 // resize container view
4872 fContainerView
->ResizeBy(ceilf(newWidth
- oldWidth
), 0);
4877 _RequestDrawLines(0, 0);
4879 // erase any potential left over outside the text rect
4880 // (can only be on right hand side)
4881 BRect
dirty(fTextRect
.right
+ 1, fTextRect
.top
, bounds
.right
,
4883 if (dirty
.IsValid()) {
4884 SetLowColor(ViewColor());
4885 FillRect(dirty
, B_SOLID_LOW
);
4890 //! Creates a new offscreen BBitmap with an associated BView.
4892 BTextView::_NewOffscreen(float padding
)
4894 if (fOffscreen
!= NULL
)
4897 #if USE_DOUBLEBUFFERING
4898 BRect
bitmapRect(0, 0, fTextRect
.Width() + padding
, fTextRect
.Height());
4899 fOffscreen
= new BBitmap(bitmapRect
, fColorSpace
, true, false);
4900 if (fOffscreen
!= NULL
&& fOffscreen
->Lock()) {
4901 BView
* bufferView
= new BView(bitmapRect
, "drawing view", 0, 0);
4902 fOffscreen
->AddChild(bufferView
);
4903 fOffscreen
->Unlock();
4909 //! Deletes the textview's offscreen bitmap, if any.
4911 BTextView::_DeleteOffscreen()
4913 if (fOffscreen
!= NULL
&& fOffscreen
->Lock()) {
4920 /*! Creates a new offscreen bitmap, highlight the selection, and set the
4921 cursor to \c B_CURSOR_I_BEAM.
4924 BTextView::_Activate()
4928 // Create a new offscreen BBitmap
4931 if (fSelStart
!= fSelEnd
) {
4933 Highlight(fSelStart
, fSelEnd
);
4939 GetMouse(&where
, &buttons
, false);
4940 if (Bounds().Contains(where
))
4941 _TrackMouse(where
, NULL
);
4943 if (Window() != NULL
) {
4946 if (!Window()->HasShortcut(B_LEFT_ARROW
, B_COMMAND_KEY
)
4947 && !Window()->HasShortcut(B_RIGHT_ARROW
, B_COMMAND_KEY
)) {
4948 message
= new BMessage(kMsgNavigateArrow
);
4949 message
->AddInt32("key", B_LEFT_ARROW
);
4950 message
->AddInt32("modifiers", B_COMMAND_KEY
);
4951 Window()->AddShortcut(B_LEFT_ARROW
, B_COMMAND_KEY
, message
, this);
4953 message
= new BMessage(kMsgNavigateArrow
);
4954 message
->AddInt32("key", B_RIGHT_ARROW
);
4955 message
->AddInt32("modifiers", B_COMMAND_KEY
);
4956 Window()->AddShortcut(B_RIGHT_ARROW
, B_COMMAND_KEY
, message
, this);
4958 fInstalledNavigateCommandWordwiseShortcuts
= true;
4960 if (!Window()->HasShortcut(B_LEFT_ARROW
, B_COMMAND_KEY
| B_SHIFT_KEY
)
4961 && !Window()->HasShortcut(B_RIGHT_ARROW
,
4962 B_COMMAND_KEY
| B_SHIFT_KEY
)) {
4963 message
= new BMessage(kMsgNavigateArrow
);
4964 message
->AddInt32("key", B_LEFT_ARROW
);
4965 message
->AddInt32("modifiers", B_COMMAND_KEY
| B_SHIFT_KEY
);
4966 Window()->AddShortcut(B_LEFT_ARROW
, B_COMMAND_KEY
| B_SHIFT_KEY
,
4969 message
= new BMessage(kMsgNavigateArrow
);
4970 message
->AddInt32("key", B_RIGHT_ARROW
);
4971 message
->AddInt32("modifiers", B_COMMAND_KEY
| B_SHIFT_KEY
);
4972 Window()->AddShortcut(B_RIGHT_ARROW
, B_COMMAND_KEY
| B_SHIFT_KEY
,
4975 fInstalledSelectCommandWordwiseShortcuts
= true;
4978 if (!Window()->HasShortcut(B_LEFT_ARROW
, B_OPTION_KEY
)
4979 && !Window()->HasShortcut(B_RIGHT_ARROW
, B_OPTION_KEY
)) {
4980 message
= new BMessage(kMsgNavigateArrow
);
4981 message
->AddInt32("key", B_LEFT_ARROW
);
4982 message
->AddInt32("modifiers", B_OPTION_KEY
);
4983 Window()->AddShortcut(B_LEFT_ARROW
, B_OPTION_KEY
, message
, this);
4985 message
= new BMessage(kMsgNavigateArrow
);
4986 message
->AddInt32("key", B_RIGHT_ARROW
);
4987 message
->AddInt32("modifiers", B_OPTION_KEY
);
4988 Window()->AddShortcut(B_RIGHT_ARROW
, B_OPTION_KEY
, message
, this);
4990 fInstalledNavigateOptionWordwiseShortcuts
= true;
4992 if (!Window()->HasShortcut(B_LEFT_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
)
4993 && !Window()->HasShortcut(B_RIGHT_ARROW
,
4994 B_OPTION_KEY
| B_SHIFT_KEY
)) {
4995 message
= new BMessage(kMsgNavigateArrow
);
4996 message
->AddInt32("key", B_LEFT_ARROW
);
4997 message
->AddInt32("modifiers", B_OPTION_KEY
| B_SHIFT_KEY
);
4998 Window()->AddShortcut(B_LEFT_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
,
5001 message
= new BMessage(kMsgNavigateArrow
);
5002 message
->AddInt32("key", B_RIGHT_ARROW
);
5003 message
->AddInt32("modifiers", B_OPTION_KEY
| B_SHIFT_KEY
);
5004 Window()->AddShortcut(B_RIGHT_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
,
5007 fInstalledSelectOptionWordwiseShortcuts
= true;
5010 if (!Window()->HasShortcut(B_UP_ARROW
, B_OPTION_KEY
)
5011 && !Window()->HasShortcut(B_DOWN_ARROW
, B_OPTION_KEY
)) {
5012 message
= new BMessage(kMsgNavigateArrow
);
5013 message
->AddInt32("key", B_UP_ARROW
);
5014 message
->AddInt32("modifiers", B_OPTION_KEY
);
5015 Window()->AddShortcut(B_UP_ARROW
, B_OPTION_KEY
, message
, this);
5017 message
= new BMessage(kMsgNavigateArrow
);
5018 message
->AddInt32("key", B_DOWN_ARROW
);
5019 message
->AddInt32("modifiers", B_OPTION_KEY
);
5020 Window()->AddShortcut(B_DOWN_ARROW
, B_OPTION_KEY
, message
, this);
5022 fInstalledNavigateOptionLinewiseShortcuts
= true;
5024 if (!Window()->HasShortcut(B_UP_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
)
5025 && !Window()->HasShortcut(B_DOWN_ARROW
,
5026 B_OPTION_KEY
| B_SHIFT_KEY
)) {
5027 message
= new BMessage(kMsgNavigateArrow
);
5028 message
->AddInt32("key", B_UP_ARROW
);
5029 message
->AddInt32("modifiers", B_OPTION_KEY
| B_SHIFT_KEY
);
5030 Window()->AddShortcut(B_UP_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
,
5033 message
= new BMessage(kMsgNavigateArrow
);
5034 message
->AddInt32("key", B_DOWN_ARROW
);
5035 message
->AddInt32("modifiers", B_OPTION_KEY
| B_SHIFT_KEY
);
5036 Window()->AddShortcut(B_DOWN_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
,
5039 fInstalledSelectOptionLinewiseShortcuts
= true;
5042 if (!Window()->HasShortcut(B_HOME
, B_COMMAND_KEY
)
5043 && !Window()->HasShortcut(B_END
, B_COMMAND_KEY
)) {
5044 message
= new BMessage(kMsgNavigatePage
);
5045 message
->AddInt32("key", B_HOME
);
5046 message
->AddInt32("modifiers", B_COMMAND_KEY
);
5047 Window()->AddShortcut(B_HOME
, B_COMMAND_KEY
, message
, this);
5049 message
= new BMessage(kMsgNavigatePage
);
5050 message
->AddInt32("key", B_END
);
5051 message
->AddInt32("modifiers", B_COMMAND_KEY
);
5052 Window()->AddShortcut(B_END
, B_COMMAND_KEY
, message
, this);
5054 fInstalledNavigateHomeEndDocwiseShortcuts
= true;
5056 if (!Window()->HasShortcut(B_HOME
, B_COMMAND_KEY
| B_SHIFT_KEY
)
5057 && !Window()->HasShortcut(B_END
, B_COMMAND_KEY
| B_SHIFT_KEY
)) {
5058 message
= new BMessage(kMsgNavigatePage
);
5059 message
->AddInt32("key", B_HOME
);
5060 message
->AddInt32("modifiers", B_COMMAND_KEY
| B_SHIFT_KEY
);
5061 Window()->AddShortcut(B_HOME
, B_COMMAND_KEY
| B_SHIFT_KEY
,
5064 message
= new BMessage(kMsgNavigatePage
);
5065 message
->AddInt32("key", B_END
);
5066 message
->AddInt32("modifiers", B_COMMAND_KEY
| B_SHIFT_KEY
);
5067 Window()->AddShortcut(B_END
, B_COMMAND_KEY
| B_SHIFT_KEY
,
5070 fInstalledSelectHomeEndDocwiseShortcuts
= true;
5076 //! Unhilights the selection, set the cursor to \c B_CURSOR_SYSTEM_DEFAULT.
5078 BTextView::_Deactivate()
5082 _CancelInputMethod();
5085 if (fSelStart
!= fSelEnd
) {
5087 Highlight(fSelStart
, fSelEnd
);
5091 if (Window() != NULL
) {
5092 if (fInstalledNavigateCommandWordwiseShortcuts
) {
5093 Window()->RemoveShortcut(B_LEFT_ARROW
, B_COMMAND_KEY
);
5094 Window()->RemoveShortcut(B_RIGHT_ARROW
, B_COMMAND_KEY
);
5095 fInstalledNavigateCommandWordwiseShortcuts
= false;
5097 if (fInstalledSelectCommandWordwiseShortcuts
) {
5098 Window()->RemoveShortcut(B_LEFT_ARROW
, B_COMMAND_KEY
| B_SHIFT_KEY
);
5099 Window()->RemoveShortcut(B_RIGHT_ARROW
,
5100 B_COMMAND_KEY
| B_SHIFT_KEY
);
5101 fInstalledSelectCommandWordwiseShortcuts
= false;
5104 if (fInstalledNavigateOptionWordwiseShortcuts
) {
5105 Window()->RemoveShortcut(B_LEFT_ARROW
, B_OPTION_KEY
);
5106 Window()->RemoveShortcut(B_RIGHT_ARROW
, B_OPTION_KEY
);
5107 fInstalledNavigateOptionWordwiseShortcuts
= false;
5109 if (fInstalledSelectOptionWordwiseShortcuts
) {
5110 Window()->RemoveShortcut(B_LEFT_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
);
5111 Window()->RemoveShortcut(B_RIGHT_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
);
5112 fInstalledSelectOptionWordwiseShortcuts
= false;
5115 if (fInstalledNavigateOptionLinewiseShortcuts
) {
5116 Window()->RemoveShortcut(B_UP_ARROW
, B_OPTION_KEY
);
5117 Window()->RemoveShortcut(B_DOWN_ARROW
, B_OPTION_KEY
);
5118 fInstalledNavigateOptionLinewiseShortcuts
= false;
5120 if (fInstalledSelectOptionLinewiseShortcuts
) {
5121 Window()->RemoveShortcut(B_UP_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
);
5122 Window()->RemoveShortcut(B_DOWN_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
);
5123 fInstalledSelectOptionLinewiseShortcuts
= false;
5126 if (fInstalledNavigateHomeEndDocwiseShortcuts
) {
5127 Window()->RemoveShortcut(B_HOME
, B_COMMAND_KEY
);
5128 Window()->RemoveShortcut(B_END
, B_COMMAND_KEY
);
5129 fInstalledNavigateHomeEndDocwiseShortcuts
= false;
5131 if (fInstalledSelectHomeEndDocwiseShortcuts
) {
5132 Window()->RemoveShortcut(B_HOME
, B_COMMAND_KEY
| B_SHIFT_KEY
);
5133 Window()->RemoveShortcut(B_END
, B_COMMAND_KEY
| B_SHIFT_KEY
);
5134 fInstalledSelectHomeEndDocwiseShortcuts
= false;
5140 /*! Changes the passed in font to be displayable by the object.
5142 Set font rotation to 0, removes any font flag, set font spacing
5143 to \c B_BITMAP_SPACING and font encoding to \c B_UNICODE_UTF8.
5146 BTextView::_NormalizeFont(BFont
* font
)
5149 font
->SetRotation(0.0f
);
5151 font
->SetSpacing(B_BITMAP_SPACING
);
5152 font
->SetEncoding(B_UNICODE_UTF8
);
5158 BTextView::_SetRunArray(int32 startOffset
, int32 endOffset
,
5159 const text_run_array
* runs
)
5161 const int32 numStyles
= runs
->count
;
5162 if (numStyles
> 0) {
5163 const text_run
* theRun
= &runs
->runs
[0];
5164 for (int32 index
= 0; index
< numStyles
; index
++) {
5165 int32 fromOffset
= theRun
->offset
+ startOffset
;
5166 int32 toOffset
= endOffset
;
5167 if (index
+ 1 < numStyles
) {
5168 toOffset
= (theRun
+ 1)->offset
+ startOffset
;
5169 toOffset
= (toOffset
> endOffset
) ? endOffset
: toOffset
;
5172 _ApplyStyleRange(fromOffset
, toOffset
, B_FONT_ALL
, &theRun
->font
,
5173 &theRun
->color
, false);
5177 fStyles
->InvalidateNullStyle();
5182 /*! Returns the character class of the character at the given offset.
5184 \param offset The offset where the wanted character can be found.
5186 \return A value which represents the character's classification.
5189 BTextView::_CharClassification(int32 offset
) const
5191 // TODO: Should check against a list of characters containing also
5192 // japanese word breakers.
5193 // And what about other languages ? Isn't there a better way to check
5194 // for separator characters ?
5195 // Andrew suggested to have a look at UnicodeBlockObject.h
5196 switch (fText
->RealCharAt(offset
)) {
5198 return CHAR_CLASS_END_OF_TEXT
;
5203 return CHAR_CLASS_WHITESPACE
;
5220 return CHAR_CLASS_GRAPHICAL
;
5224 return CHAR_CLASS_QUOTE
;
5233 return CHAR_CLASS_PUNCTUATION
;
5238 return CHAR_CLASS_PARENS_OPEN
;
5243 return CHAR_CLASS_PARENS_CLOSE
;
5246 return CHAR_CLASS_DEFAULT
;
5251 /*! Returns the offset of the next UTF-8 character.
5253 \param offset The offset where to start looking.
5255 \return The offset of the next UTF-8 character.
5258 BTextView::_NextInitialByte(int32 offset
) const
5260 if (offset
>= fText
->Length())
5263 for (++offset
; (ByteAt(offset
) & 0xC0) == 0x80; ++offset
)
5270 /*! Returns the offset of the previous UTF-8 character.
5272 \param offset The offset where to start looking.
5274 \return The offset of the previous UTF-8 character.
5277 BTextView::_PreviousInitialByte(int32 offset
) const
5284 for (--offset
; offset
> 0 && count
; --offset
, --count
) {
5285 if ((ByteAt(offset
) & 0xC0) != 0x80)
5289 return count
? offset
: 0;
5294 BTextView::_GetProperty(BMessage
* specifier
, int32 form
, const char* property
,
5298 if (strcmp(property
, "selection") == 0) {
5299 reply
->what
= B_REPLY
;
5300 reply
->AddInt32("result", fSelStart
);
5301 reply
->AddInt32("result", fSelEnd
);
5302 reply
->AddInt32("error", B_OK
);
5305 } else if (strcmp(property
, "Text") == 0) {
5306 if (IsTypingHidden()) {
5307 // Do not allow stealing passwords via scripting
5313 specifier
->FindInt32("index", &index
);
5314 specifier
->FindInt32("range", &range
);
5316 char* buffer
= new char[range
+ 1];
5317 GetText(index
, range
, buffer
);
5319 reply
->what
= B_REPLY
;
5320 reply
->AddString("result", buffer
);
5321 reply
->AddInt32("error", B_OK
);
5326 } else if (strcmp(property
, "text_run_array") == 0)
5334 BTextView::_SetProperty(BMessage
* specifier
, int32 form
, const char* property
,
5338 if (strcmp(property
, "selection") == 0) {
5341 specifier
->FindInt32("index", &index
);
5342 specifier
->FindInt32("range", &range
);
5344 Select(index
, index
+ range
);
5346 reply
->what
= B_REPLY
;
5347 reply
->AddInt32("error", B_OK
);
5350 } else if (strcmp(property
, "Text") == 0) {
5352 specifier
->FindInt32("index", &index
);
5353 specifier
->FindInt32("range", &range
);
5355 const char* buffer
= NULL
;
5356 if (specifier
->FindString("data", &buffer
) == B_OK
)
5357 InsertText(buffer
, range
, index
, NULL
);
5359 DeleteText(index
, range
);
5361 reply
->what
= B_REPLY
;
5362 reply
->AddInt32("error", B_OK
);
5365 } else if (strcmp(property
, "text_run_array") == 0)
5373 BTextView::_CountProperties(BMessage
* specifier
, int32 form
,
5374 const char* property
, BMessage
* reply
)
5377 if (strcmp(property
, "Text") == 0) {
5378 reply
->what
= B_REPLY
;
5379 reply
->AddInt32("result", TextLength());
5380 reply
->AddInt32("error", B_OK
);
5388 //! Called when the object receives a \c B_INPUT_METHOD_CHANGED message.
5390 BTextView::_HandleInputMethodChanged(BMessage
* message
)
5392 // TODO: block input if not editable (Andrew)
5393 ASSERT(fInline
!= NULL
);
5395 const char* string
= NULL
;
5396 if (message
->FindString("be:string", &string
) < B_OK
|| string
== NULL
)
5402 be_app
->ObscureCursor();
5404 // If we find the "be:confirmed" boolean (and the boolean is true),
5405 // it means it's over for now, so the current InlineInput object
5406 // should become inactive. We will probably receive a
5407 // B_INPUT_METHOD_STOPPED message after this one.
5409 if (message
->FindBool("be:confirmed", &confirmed
) != B_OK
)
5412 // Delete the previously inserted text (if any)
5413 if (fInline
->IsActive()) {
5414 const int32 oldOffset
= fInline
->Offset();
5415 DeleteText(oldOffset
, oldOffset
+ fInline
->Length());
5417 fInline
->SetActive(false);
5418 fCaretOffset
= fSelStart
= fSelEnd
= oldOffset
;
5421 const int32 stringLen
= strlen(string
);
5423 fInline
->SetOffset(fSelStart
);
5424 fInline
->SetLength(stringLen
);
5425 fInline
->ResetClauses();
5427 if (!confirmed
&& !fInline
->IsActive())
5428 fInline
->SetActive(true);
5430 // Get the clauses, and pass them to the InlineInput object
5431 // TODO: Find out if what we did it's ok, currently we don't consider
5432 // clauses at all, while the bebook says we should; though the visual
5433 // effect we obtained seems correct. Weird.
5434 int32 clauseCount
= 0;
5437 while (message
->FindInt32("be:clause_start", clauseCount
, &clauseStart
)
5439 && message
->FindInt32("be:clause_end", clauseCount
, &clauseEnd
)
5441 if (!fInline
->AddClause(clauseStart
, clauseEnd
))
5447 _Refresh(fSelStart
, fSelEnd
, true);
5450 // now we need to feed ourselves the individual characters as if the
5451 // user would have pressed them now - this lets KeyDown() pick out all
5452 // the special characters like B_BACKSPACE, cursor keys and the like:
5453 const char* currPos
= string
;
5454 const char* prevPos
= currPos
;
5455 while (*currPos
!= '\0') {
5456 if ((*currPos
& 0xC0) == 0xC0) {
5457 // found the start of an UTF-8 char, we collect while it lasts
5459 while ((*currPos
& 0xC0) == 0x80)
5461 } else if ((*currPos
& 0xC0) == 0x80) {
5462 // illegal: character starts with utf-8 intermediate byte,
5464 prevPos
= ++currPos
;
5466 // single byte character/code, just feed that
5469 KeyDown(prevPos
, currPos
- prevPos
);
5473 _Refresh(fSelStart
, fSelEnd
, true);
5475 // temporarily show transient state of inline input
5476 int32 selectionStart
= 0;
5477 int32 selectionEnd
= 0;
5478 message
->FindInt32("be:selection", 0, &selectionStart
);
5479 message
->FindInt32("be:selection", 1, &selectionEnd
);
5481 fInline
->SetSelectionOffset(selectionStart
);
5482 fInline
->SetSelectionLength(selectionEnd
- selectionStart
);
5484 const int32 inlineOffset
= fInline
->Offset();
5485 InsertText(string
, stringLen
, fSelStart
, NULL
);
5487 _Refresh(inlineOffset
, fSelEnd
, true);
5494 /*! Called when the object receives a \c B_INPUT_METHOD_LOCATION_REQUEST
5498 BTextView::_HandleInputMethodLocationRequest()
5500 ASSERT(fInline
!= NULL
);
5502 int32 offset
= fInline
->Offset();
5503 const int32 limit
= offset
+ fInline
->Length();
5505 BMessage
message(B_INPUT_METHOD_EVENT
);
5506 message
.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST
);
5508 // Add the location of the UTF8 characters
5509 while (offset
< limit
) {
5511 BPoint where
= PointAt(offset
, &height
);
5512 ConvertToScreen(&where
);
5514 message
.AddPoint("be:location_reply", where
);
5515 message
.AddFloat("be:height_reply", height
);
5517 offset
= _NextInitialByte(offset
);
5520 fInline
->Method()->SendMessage(&message
);
5524 //! Tells the Input Server method add-on to stop the current transaction.
5526 BTextView::_CancelInputMethod()
5531 InlineInput
* inlineInput
= fInline
;
5534 if (inlineInput
->IsActive() && Window()) {
5535 _Refresh(inlineInput
->Offset(), fText
->Length() - inlineInput
->Offset(),
5538 BMessage
message(B_INPUT_METHOD_EVENT
);
5539 message
.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED
);
5540 inlineInput
->Method()->SendMessage(&message
);
5547 /*! Returns the line number of the character at the given \a offset.
5549 \note This will never return the last line (use LineAt() if you
5550 need to be correct about that.) N.B.
5552 \param offset The offset of the wanted character.
5554 \return The line number of the character at the given \a offset as an int32.
5557 BTextView::_LineAt(int32 offset
) const
5559 return fLines
->OffsetToLine(offset
);
5563 /*! Returns the line number that the given \a point is on.
5565 \note This will never return the last line (use LineAt() if you
5566 need to be correct about that.) N.B.
5568 \param point The \a point the get the line number of.
5570 \return The line number of the given \a point as an int32.
5573 BTextView::_LineAt(const BPoint
& point
) const
5575 return fLines
->PixelToLine(point
.y
- fTextRect
.top
);
5579 /*! Returns whether or not the given \a offset is on the empty line at the end
5583 BTextView::_IsOnEmptyLastLine(int32 offset
) const
5585 return (offset
== TextLength() && offset
> 0
5586 && fText
->RealCharAt(offset
- 1) == B_ENTER
);
5591 BTextView::_ApplyStyleRange(int32 fromOffset
, int32 toOffset
, uint32 mode
,
5592 const BFont
* font
, const rgb_color
* color
, bool syncNullStyle
)
5595 // if a font has been given, normalize it
5596 BFont normalized
= *font
;
5597 _NormalizeFont(&normalized
);
5602 // always apply font and color to full range for non-stylable textviews
5604 toOffset
= fText
->Length();
5608 fStyles
->SyncNullStyle(fromOffset
);
5610 fStyles
->SetStyleRange(fromOffset
, toOffset
, fText
->Length(), mode
,
5616 BTextView::_NullStyleHeight() const
5618 const BFont
* font
= NULL
;
5619 fStyles
->GetNullStyle(&font
, NULL
);
5621 font_height fontHeight
;
5622 font
->GetHeight(&fontHeight
);
5623 return ceilf(fontHeight
.ascent
+ fontHeight
.descent
+ 1);
5628 BTextView::_ShowContextMenu(BPoint where
)
5631 undo_state state
= UndoState(&isRedo
);
5632 bool isUndo
= state
!= B_UNDO_UNAVAILABLE
&& !isRedo
;
5636 GetSelection(&start
, &finish
);
5638 bool canEdit
= IsEditable();
5639 int32 length
= TextLength();
5641 BPopUpMenu
* menu
= new BPopUpMenu(B_EMPTY_STRING
, false, false);
5643 BLayoutBuilder::Menu
<>(menu
)
5644 .AddItem(TRANSLATE("Undo"), B_UNDO
/*, 'Z'*/)
5645 .SetEnabled(canEdit
&& isUndo
)
5646 .AddItem(TRANSLATE("Redo"), B_UNDO
/*, 'Z', B_SHIFT_KEY*/)
5647 .SetEnabled(canEdit
&& isRedo
)
5649 .AddItem(TRANSLATE("Cut"), B_CUT
, 'X')
5650 .SetEnabled(canEdit
&& start
!= finish
)
5651 .AddItem(TRANSLATE("Copy"), B_COPY
, 'C')
5652 .SetEnabled(start
!= finish
)
5653 .AddItem(TRANSLATE("Paste"), B_PASTE
, 'V')
5654 .SetEnabled(canEdit
&& be_clipboard
->SystemCount() > 0)
5656 .AddItem(TRANSLATE("Select all"), B_SELECT_ALL
, 'A')
5657 .SetEnabled(!(start
== 0 && finish
== length
))
5660 menu
->SetTargetForItems(this);
5661 ConvertToScreen(&where
);
5662 menu
->Go(where
, true, true, true);
5667 BTextView::_FilterDisallowedChars(char* text
, ssize_t
& length
,
5668 text_run_array
* runArray
)
5670 if (!fDisallowedChars
)
5673 if (fDisallowedChars
->IsEmpty() || !text
)
5676 ssize_t stringIndex
= 0;
5678 ssize_t remNext
= 0;
5680 for (int i
= 0; i
< runArray
->count
; i
++) {
5681 runArray
->runs
[i
].offset
-= remNext
;
5682 while (stringIndex
< runArray
->runs
[i
].offset
5683 && stringIndex
< length
) {
5684 if (fDisallowedChars
->HasItem(
5685 reinterpret_cast<void*>(text
[stringIndex
]))) {
5686 memmove(text
+ stringIndex
, text
+ stringIndex
+ 1,
5687 length
- stringIndex
- 1);
5689 runArray
->runs
[i
].offset
--;
5697 while (stringIndex
< length
) {
5698 if (fDisallowedChars
->HasItem(
5699 reinterpret_cast<void*>(text
[stringIndex
]))) {
5700 memmove(text
+ stringIndex
, text
+ stringIndex
+ 1,
5701 length
- stringIndex
- 1);
5709 // #pragma mark - BTextView::TextTrackState
5712 BTextView::TextTrackState::TextTrackState(BMessenger messenger
)
5721 BMessage
message(_PING_
);
5722 const bigtime_t scrollSpeed
= 25 * 1000; // 40 scroll steps per second
5723 fRunner
= new (nothrow
) BMessageRunner(messenger
, &message
, scrollSpeed
);
5727 BTextView::TextTrackState::~TextTrackState()
5734 BTextView::TextTrackState::SimulateMouseMovement(BTextView
* textView
)
5738 // When the mouse cursor is still and outside the textview,
5739 // no B_MOUSE_MOVED message are sent, obviously. But scrolling
5740 // has to work neverthless, so we "fake" a MouseMoved() call here.
5741 textView
->GetMouse(&where
, &buttons
);
5742 textView
->_PerformMouseMoved(where
, B_INSIDE_VIEW
);
5746 // #pragma mark - Binary ABI compat
5750 B_IF_GCC_2(InvalidateLayout__9BTextViewb
, _ZN9BTextView16InvalidateLayoutEb
)(
5751 BTextView
* view
, bool descendants
)
5753 perform_data_layout_invalidated data
;
5754 data
.descendants
= descendants
;
5756 view
->Perform(PERFORM_CODE_LAYOUT_INVALIDATED
, &data
);