2 * Copyright 2001-2015 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 "
259 BTextView::BTextView(BRect frame
, const char* name
, BRect textRect
,
260 uint32 resizeMask
, uint32 flags
)
262 BView(frame
, name
, resizeMask
,
263 flags
| B_FRAME_EVENTS
| B_PULSE_NEEDED
| B_INPUT_METHOD_AWARE
)
265 _InitObject(textRect
, NULL
, NULL
);
269 BTextView::BTextView(BRect frame
, const char* name
, BRect textRect
,
270 const BFont
* initialFont
, const rgb_color
* initialColor
,
271 uint32 resizeMask
, uint32 flags
)
273 BView(frame
, name
, resizeMask
,
274 flags
| B_FRAME_EVENTS
| B_PULSE_NEEDED
| B_INPUT_METHOD_AWARE
)
276 _InitObject(textRect
, initialFont
, initialColor
);
280 BTextView::BTextView(const char* name
, uint32 flags
)
283 flags
| B_FRAME_EVENTS
| B_PULSE_NEEDED
| B_INPUT_METHOD_AWARE
)
285 _InitObject(Bounds(), NULL
, NULL
);
289 BTextView::BTextView(const char* name
, const BFont
* initialFont
,
290 const rgb_color
* initialColor
, uint32 flags
)
293 flags
| B_FRAME_EVENTS
| B_PULSE_NEEDED
| B_INPUT_METHOD_AWARE
)
295 _InitObject(Bounds(), initialFont
, initialColor
);
299 BTextView::BTextView(BMessage
* archive
)
306 if (archive
->FindRect("_trect", &rect
) != B_OK
)
307 rect
.Set(0, 0, 0, 0);
309 _InitObject(rect
, NULL
, NULL
);
311 const char* text
= NULL
;
312 if (archive
->FindString("_text", &text
) == B_OK
)
316 if (archive
->FindInt32("_align", &flag
) == B_OK
)
317 SetAlignment((alignment
)flag
);
321 if (archive
->FindFloat("_tab", &value
) == B_OK
)
324 if (archive
->FindInt32("_col_sp", &flag
) == B_OK
)
325 SetColorSpace((color_space
)flag
);
327 if (archive
->FindInt32("_max", &flag
) == B_OK
)
330 if (archive
->FindInt32("_sel", &flag
) == B_OK
&&
331 archive
->FindInt32("_sel", &flag2
) == B_OK
)
336 if (archive
->FindBool("_stylable", &toggle
) == B_OK
)
339 if (archive
->FindBool("_auto_in", &toggle
) == B_OK
)
340 SetAutoindent(toggle
);
342 if (archive
->FindBool("_wrap", &toggle
) == B_OK
)
345 if (archive
->FindBool("_nsel", &toggle
) == B_OK
)
346 MakeSelectable(!toggle
);
348 if (archive
->FindBool("_nedit", &toggle
) == B_OK
)
349 MakeEditable(!toggle
);
351 ssize_t disallowedCount
= 0;
352 const int32
* disallowedChars
= NULL
;
353 if (archive
->FindData("_dis_ch", B_RAW_TYPE
,
354 (const void**)&disallowedChars
, &disallowedCount
) == B_OK
) {
356 fDisallowedChars
= new BList
;
357 disallowedCount
/= sizeof(int32
);
358 for (int32 x
= 0; x
< disallowedCount
; x
++) {
359 fDisallowedChars
->AddItem(
360 reinterpret_cast<void*>(disallowedChars
[x
]));
365 const void* flattenedRun
= NULL
;
367 if (archive
->FindData("_runs", B_RAW_TYPE
, &flattenedRun
, &runSize
)
369 text_run_array
* runArray
= UnflattenRunArray(flattenedRun
,
372 SetRunArray(0, TextLength(), runArray
);
373 FreeRunArray(runArray
);
379 BTextView::~BTextView()
381 _CancelInputMethod();
382 _StopMouseTracking();
388 delete fDisallowedChars
;
397 BTextView::Instantiate(BMessage
* archive
)
400 if (validate_instantiation(archive
, "BTextView"))
401 return new BTextView(archive
);
407 BTextView::Archive(BMessage
* data
, bool deep
) const
410 status_t err
= BView::Archive(data
, deep
);
412 err
= data
->AddString("_text", Text());
414 err
= data
->AddInt32("_align", fAlignment
);
416 err
= data
->AddFloat("_tab", fTabWidth
);
418 err
= data
->AddInt32("_col_sp", fColorSpace
);
420 err
= data
->AddRect("_trect", fTextRect
);
422 err
= data
->AddInt32("_max", fMaxBytes
);
424 err
= data
->AddInt32("_sel", fSelStart
);
426 err
= data
->AddInt32("_sel", fSelEnd
);
428 err
= data
->AddBool("_stylable", fStylable
);
430 err
= data
->AddBool("_auto_in", fAutoindent
);
432 err
= data
->AddBool("_wrap", fWrap
);
434 err
= data
->AddBool("_nsel", !fSelectable
);
436 err
= data
->AddBool("_nedit", !fEditable
);
438 if (err
== B_OK
&& fDisallowedChars
!= NULL
&& fDisallowedChars
->CountItems() > 0) {
439 err
= data
->AddData("_dis_ch", B_RAW_TYPE
, fDisallowedChars
->Items(),
440 fDisallowedChars
->CountItems() * sizeof(int32
));
445 text_run_array
* runArray
= RunArray(0, TextLength());
447 void* flattened
= FlattenRunArray(runArray
, &runSize
);
448 if (flattened
!= NULL
) {
449 data
->AddData("_runs", B_RAW_TYPE
, flattened
, runSize
);
454 FreeRunArray(runArray
);
462 BTextView::AttachedToWindow()
464 BView::AttachedToWindow();
466 SetDrawingMode(B_OP_COPY
);
468 Window()->SetPulseRate(500000);
470 fCaretVisible
= false;
481 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT
);
486 BTextView::DetachedFromWindow()
488 BView::DetachedFromWindow();
493 BTextView::Draw(BRect updateRect
)
495 // what lines need to be drawn?
496 int32 startLine
= _LineAt(BPoint(0.0, updateRect
.top
));
497 int32 endLine
= _LineAt(BPoint(0.0, updateRect
.bottom
));
499 _DrawLines(startLine
, endLine
, -1, true);
504 BTextView::MouseDown(BPoint where
)
506 // should we even bother?
507 if (!fEditable
&& !fSelectable
)
510 _CancelInputMethod();
517 _StopMouseTracking();
521 BMessage
* currentMessage
= Window()->CurrentMessage();
522 if (currentMessage
!= NULL
) {
523 currentMessage
->FindInt32("modifiers", &modifiers
);
524 currentMessage
->FindInt32("buttons", (int32
*)&buttons
);
527 if (buttons
== B_SECONDARY_MOUSE_BUTTON
) {
528 _ShowContextMenu(where
);
532 BMessenger
messenger(this);
533 fTrackingMouse
= new (nothrow
) TextTrackState(messenger
);
534 if (fTrackingMouse
== NULL
)
537 fTrackingMouse
->clickOffset
= OffsetAt(where
);
538 fTrackingMouse
->shiftDown
= modifiers
& B_SHIFT_KEY
;
539 fTrackingMouse
->where
= where
;
541 bigtime_t clickTime
= system_time();
542 bigtime_t clickSpeed
= 0;
543 get_click_speed(&clickSpeed
);
545 = clickTime
- fClickTime
< clickSpeed
546 && fLastClickOffset
== fTrackingMouse
->clickOffset
;
550 SetMouseEventMask(B_POINTER_EVENTS
| B_KEYBOARD_EVENTS
,
551 B_LOCK_WINDOW_FOCUS
| B_NO_POINTER_HISTORY
);
553 if (fSelStart
!= fSelEnd
&& !fTrackingMouse
->shiftDown
&& !multipleClick
) {
555 GetTextRegion(fSelStart
, fSelEnd
, ®ion
);
556 if (region
.Contains(where
)) {
557 // Setup things for dragging
558 fTrackingMouse
->selectionRect
= region
.Frame();
560 fClickTime
= clickTime
;
561 fLastClickOffset
= OffsetAt(where
);
567 if (fClickCount
> 3) {
572 fClickTime
= clickTime
;
574 } else if (!fTrackingMouse
->shiftDown
) {
575 // If no multiple click yet and shift is not pressed, this is an
576 // independent first click somewhere into the textview - we initialize
577 // the corresponding members for handling potential multiple clicks:
578 fLastClickOffset
= fCaretOffset
= fTrackingMouse
->clickOffset
;
580 fClickTime
= clickTime
;
582 // Deselect any previously selected text
583 Select(fTrackingMouse
->clickOffset
, fTrackingMouse
->clickOffset
);
586 if (fClickTime
== clickTime
) {
587 BMessage
message(_PING_
);
588 message
.AddInt64("clickTime", clickTime
);
591 BMessenger
messenger(this);
592 fClickRunner
= new (nothrow
) BMessageRunner(messenger
, &message
,
597 _StopMouseTracking();
601 int32 offset
= fSelStart
;
602 if (fTrackingMouse
->clickOffset
> fSelStart
)
605 fTrackingMouse
->anchor
= offset
;
607 MouseMoved(where
, B_INSIDE_VIEW
, NULL
);
612 BTextView::MouseUp(BPoint where
)
614 BView::MouseUp(where
);
615 _PerformMouseUp(where
);
623 BTextView::MouseMoved(BPoint where
, uint32 code
, const BMessage
* dragMessage
)
625 // check if it's a "click'n'move"
626 if (_PerformMouseMoved(where
, code
))
632 _TrackMouse(where
, dragMessage
, true);
637 if (Window()->IsActive() && dragMessage
== NULL
)
638 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT
);
642 BView::MouseMoved(where
, code
, dragMessage
);
648 BTextView::WindowActivated(bool active
)
650 BView::WindowActivated(active
);
652 if (active
&& IsFocus()) {
662 GetMouse(&where
, &buttons
, false);
664 if (Bounds().Contains(where
))
665 _TrackMouse(where
, NULL
);
670 BTextView::KeyDown(const char* bytes
, int32 numBytes
)
672 const char keyPressed
= bytes
[0];
675 // only arrow and page keys are allowed
676 // (no need to hide the cursor)
677 switch (keyPressed
) {
682 _HandleArrowKey(keyPressed
);
689 _HandlePageKey(keyPressed
);
693 BView::KeyDown(bytes
, numBytes
);
700 // hide the cursor and caret
702 be_app
->ObscureCursor();
705 switch (keyPressed
) {
714 _HandleArrowKey(keyPressed
);
725 _HandlePageKey(keyPressed
);
731 // ignore, pass it up to superclass
732 BView::KeyDown(bytes
, numBytes
);
736 // bail out if the character is not allowed
738 && fDisallowedChars
->HasItem(
739 reinterpret_cast<void*>((uint32
)keyPressed
))) {
744 _HandleAlphaKey(bytes
, numBytes
);
749 if (fSelStart
== fSelEnd
)
757 if (fActive
&& fEditable
&& fSelStart
== fSelEnd
) {
758 if (system_time() > (fCaretTime
+ 500000.0))
765 BTextView::FrameResized(float newWidth
, float newHeight
)
767 BView::FrameResized(newWidth
, newHeight
);
773 BTextView::MakeFocus(bool focus
)
775 BView::MakeFocus(focus
);
777 if (focus
&& Window() != NULL
&& Window()->IsActive()) {
788 BTextView::MessageReceived(BMessage
* message
)
790 // ToDo: block input if not editable (Andrew)
792 // was this message dropped?
793 if (message
->WasDropped()) {
795 BPoint dropPoint
= message
->DropPoint(&dropOffset
);
796 ConvertFromScreen(&dropPoint
);
797 ConvertFromScreen(&dropOffset
);
798 if (!_MessageDropped(message
, dropPoint
, dropOffset
))
799 BView::MessageReceived(message
);
804 switch (message
->what
) {
806 if (!IsTypingHidden())
813 if (!IsTypingHidden())
831 case B_INPUT_METHOD_EVENT
:
834 if (message
->FindInt32("be:opcode", &opcode
) == B_OK
) {
836 case B_INPUT_METHOD_STARTED
:
838 BMessenger messenger
;
839 if (message
->FindMessenger("be:reply_to", &messenger
)
841 ASSERT(fInline
== NULL
);
842 fInline
= new InlineInput(messenger
);
847 case B_INPUT_METHOD_STOPPED
:
852 case B_INPUT_METHOD_CHANGED
:
854 _HandleInputMethodChanged(message
);
857 case B_INPUT_METHOD_LOCATION_REQUEST
:
859 _HandleInputMethodLocationRequest();
871 case B_COUNT_PROPERTIES
:
873 BPropertyInfo
propInfo(sPropertyList
);
875 const char* property
;
877 if (message
->GetCurrentSpecifier(NULL
, &specifier
) < B_OK
878 || specifier
.FindString("property", &property
) < B_OK
)
881 if (propInfo
.FindMatch(message
, 0, &specifier
, specifier
.what
,
883 BView::MessageReceived(message
);
888 bool handled
= false;
889 switch(message
->what
) {
891 handled
= _GetProperty(&specifier
, specifier
.what
, property
,
896 handled
= _SetProperty(&specifier
, specifier
.what
, property
,
900 case B_COUNT_PROPERTIES
:
901 handled
= _CountProperties(&specifier
, specifier
.what
,
909 message
->SendReply(&reply
);
911 BView::MessageReceived(message
);
917 if (message
->HasInt64("clickTime")) {
919 message
->FindInt64("clickTime", &clickTime
);
920 if (clickTime
== fClickTime
) {
921 if (fSelStart
!= fSelEnd
&& fSelectable
) {
923 GetTextRegion(fSelStart
, fSelEnd
, ®ion
);
924 if (region
.Contains(fWhere
))
925 _TrackMouse(fWhere
, NULL
);
930 } else if (fTrackingMouse
) {
931 fTrackingMouse
->SimulateMouseMovement(this);
932 _PerformAutoScrolling();
942 case kMsgNavigateArrow
:
944 int32 key
= message
->GetInt32("key", 0);
945 int32 modifiers
= message
->GetInt32("modifiers", 0);
946 _HandleArrowKey(key
, modifiers
);
950 case kMsgNavigatePage
:
952 int32 key
= message
->GetInt32("key", 0);
953 int32 modifiers
= message
->GetInt32("modifiers", 0);
954 _HandlePageKey(key
, modifiers
);
959 BView::MessageReceived(message
);
966 BTextView::ResolveSpecifier(BMessage
* message
, int32 index
, BMessage
* specifier
,
967 int32 what
, const char* property
)
969 BPropertyInfo
propInfo(sPropertyList
);
970 BHandler
* target
= this;
972 if (propInfo
.FindMatch(message
, index
, specifier
, what
, property
) < B_OK
) {
973 target
= BView::ResolveSpecifier(message
, index
, specifier
, what
,
982 BTextView::GetSupportedSuites(BMessage
* data
)
987 status_t err
= data
->AddString("suites", "suite/vnd.Be-text-view");
991 BPropertyInfo
prop_info(sPropertyList
);
992 err
= data
->AddFlat("messages", &prop_info
);
996 return BView::GetSupportedSuites(data
);
1001 BTextView::Perform(perform_code code
, void* _data
)
1004 case PERFORM_CODE_MIN_SIZE
:
1005 ((perform_data_min_size
*)_data
)->return_value
1006 = BTextView::MinSize();
1008 case PERFORM_CODE_MAX_SIZE
:
1009 ((perform_data_max_size
*)_data
)->return_value
1010 = BTextView::MaxSize();
1012 case PERFORM_CODE_PREFERRED_SIZE
:
1013 ((perform_data_preferred_size
*)_data
)->return_value
1014 = BTextView::PreferredSize();
1016 case PERFORM_CODE_LAYOUT_ALIGNMENT
:
1017 ((perform_data_layout_alignment
*)_data
)->return_value
1018 = BTextView::LayoutAlignment();
1020 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH
:
1021 ((perform_data_has_height_for_width
*)_data
)->return_value
1022 = BTextView::HasHeightForWidth();
1024 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH
:
1026 perform_data_get_height_for_width
* data
1027 = (perform_data_get_height_for_width
*)_data
;
1028 BTextView::GetHeightForWidth(data
->width
, &data
->min
, &data
->max
,
1032 case PERFORM_CODE_SET_LAYOUT
:
1034 perform_data_set_layout
* data
= (perform_data_set_layout
*)_data
;
1035 BTextView::SetLayout(data
->layout
);
1038 case PERFORM_CODE_LAYOUT_INVALIDATED
:
1040 perform_data_layout_invalidated
* data
1041 = (perform_data_layout_invalidated
*)_data
;
1042 BTextView::LayoutInvalidated(data
->descendants
);
1045 case PERFORM_CODE_DO_LAYOUT
:
1047 BTextView::DoLayout();
1052 return BView::Perform(code
, _data
);
1057 BTextView::SetText(const char* text
, const text_run_array
* runs
)
1059 SetText(text
, text
? strlen(text
) : 0, runs
);
1064 BTextView::SetText(const char* text
, int32 length
, const text_run_array
* runs
)
1066 _CancelInputMethod();
1068 // hide the caret/unhighlight the selection
1070 if (fSelStart
!= fSelEnd
) {
1072 Highlight(fSelStart
, fSelEnd
);
1077 // remove data from buffer
1078 if (fText
->Length() > 0)
1079 DeleteText(0, fText
->Length());
1081 if (text
!= NULL
&& length
> 0)
1082 InsertText(text
, length
, 0, runs
);
1084 // recalculate line breaks and draw the text
1085 _Refresh(0, length
, false);
1086 fCaretOffset
= fSelStart
= fSelEnd
= 0;
1095 BTextView::SetText(BFile
* file
, int32 offset
, int32 length
,
1096 const text_run_array
* runs
)
1100 _CancelInputMethod();
1105 if (fText
->Length() > 0)
1106 DeleteText(0, fText
->Length());
1108 if (!fText
->InsertText(file
, offset
, length
, 0))
1111 // update the start offsets of each line below offset
1112 fLines
->BumpOffset(length
, _LineAt(offset
) + 1);
1114 // update the style runs
1115 fStyles
->BumpOffset(length
, fStyles
->OffsetToRun(offset
- 1) + 1);
1117 if (fStylable
&& runs
!= NULL
)
1118 SetRunArray(offset
, offset
+ length
, runs
);
1120 // apply null-style to inserted text
1121 _ApplyStyleRange(offset
, offset
+ length
);
1124 // recalculate line breaks and draw the text
1125 _Refresh(0, length
, false);
1126 fCaretOffset
= fSelStart
= fSelEnd
= 0;
1127 ScrollToOffset(fSelStart
);
1135 BTextView::Insert(const char* text
, const text_run_array
* runs
)
1138 _DoInsertText(text
, strlen(text
), fSelStart
, runs
);
1143 BTextView::Insert(const char* text
, int32 length
, const text_run_array
* runs
)
1145 if (text
!= NULL
&& length
> 0)
1146 _DoInsertText(text
, strnlen(text
, length
), fSelStart
, runs
);
1151 BTextView::Insert(int32 offset
, const char* text
, int32 length
,
1152 const text_run_array
* runs
)
1154 // pin offset at reasonable values
1157 else if (offset
> fText
->Length())
1158 offset
= fText
->Length();
1160 if (text
!= NULL
&& length
> 0)
1161 _DoInsertText(text
, strnlen(text
, length
), offset
, runs
);
1168 Delete(fSelStart
, fSelEnd
);
1173 BTextView::Delete(int32 startOffset
, int32 endOffset
)
1177 // pin offsets at reasonable values
1178 if (startOffset
< 0)
1180 else if (startOffset
> fText
->Length())
1181 startOffset
= fText
->Length();
1184 else if (endOffset
> fText
->Length())
1185 endOffset
= fText
->Length();
1187 // anything to delete?
1188 if (startOffset
== endOffset
)
1191 // hide the caret/unhighlight the selection
1193 if (fSelStart
!= fSelEnd
) {
1195 Highlight(fSelStart
, fSelEnd
);
1199 // remove data from buffer
1200 DeleteText(startOffset
, endOffset
);
1202 // check if the caret needs to be moved
1203 if (fCaretOffset
>= endOffset
)
1204 fCaretOffset
-= (endOffset
- startOffset
);
1205 else if (fCaretOffset
>= startOffset
&& fCaretOffset
< endOffset
)
1206 fCaretOffset
= startOffset
;
1208 fSelEnd
= fSelStart
= fCaretOffset
;
1210 // recalculate line breaks and draw what's left
1211 _Refresh(startOffset
, endOffset
, false);
1219 BTextView::Text() const
1221 return fText
->RealText();
1226 BTextView::TextLength() const
1228 return fText
->Length();
1233 BTextView::GetText(int32 offset
, int32 length
, char* buffer
) const
1236 fText
->GetString(offset
, length
, buffer
);
1241 BTextView::ByteAt(int32 offset
) const
1243 if (offset
< 0 || offset
>= fText
->Length())
1246 return fText
->RealCharAt(offset
);
1251 BTextView::CountLines() const
1253 return fLines
->NumLines();
1258 BTextView::CurrentLine() const
1260 return LineAt(fSelStart
);
1265 BTextView::GoToLine(int32 index
)
1267 _CancelInputMethod();
1269 fSelStart
= fSelEnd
= fCaretOffset
= OffsetAt(index
);
1275 BTextView::Cut(BClipboard
* clipboard
)
1277 _CancelInputMethod();
1282 fUndo
= new CutUndoBuffer(this);
1290 BTextView::Copy(BClipboard
* clipboard
)
1292 _CancelInputMethod();
1294 if (clipboard
->Lock()) {
1297 BMessage
* clip
= clipboard
->Data();
1299 int32 numBytes
= fSelEnd
- fSelStart
;
1300 const char* text
= fText
->GetString(fSelStart
, &numBytes
);
1301 clip
->AddData("text/plain", B_MIME_TYPE
, text
, numBytes
);
1305 text_run_array
* runArray
= RunArray(fSelStart
, fSelEnd
, &size
);
1306 clip
->AddData("application/x-vnd.Be-text_run_array",
1307 B_MIME_TYPE
, runArray
, size
);
1308 FreeRunArray(runArray
);
1310 clipboard
->Commit();
1312 clipboard
->Unlock();
1318 BTextView::Paste(BClipboard
* clipboard
)
1321 _CancelInputMethod();
1323 if (!fEditable
|| !clipboard
->Lock())
1326 BMessage
* clip
= clipboard
->Data();
1328 const char* text
= NULL
;
1331 if (clip
->FindData("text/plain", B_MIME_TYPE
,
1332 (const void**)&text
, &length
) == B_OK
) {
1333 text_run_array
* runArray
= NULL
;
1334 ssize_t runLength
= 0;
1337 clip
->FindData("application/x-vnd.Be-text_run_array",
1338 B_MIME_TYPE
, (const void**)&runArray
, &runLength
);
1341 _FilterDisallowedChars((char*)text
, length
, runArray
);
1345 clipboard
->Unlock();
1351 fUndo
= new PasteUndoBuffer(this, text
, length
, runArray
,
1355 if (fSelStart
!= fSelEnd
)
1358 Insert(text
, length
, runArray
);
1359 ScrollToOffset(fSelEnd
);
1363 clipboard
->Unlock();
1370 // We always check for fUndo != NULL (not only here),
1371 // because when fUndo is NULL, undo is deactivated.
1374 fUndo
= new ClearUndoBuffer(this);
1382 BTextView::AcceptsPaste(BClipboard
* clipboard
)
1384 bool result
= false;
1386 if (fEditable
&& clipboard
&& clipboard
->Lock()) {
1387 BMessage
* data
= clipboard
->Data();
1388 result
= data
&& data
->HasData("text/plain", B_MIME_TYPE
);
1389 clipboard
->Unlock();
1397 BTextView::AcceptsDrop(const BMessage
* message
)
1399 return fEditable
&& message
1400 && message
->HasData("text/plain", B_MIME_TYPE
);
1405 BTextView::Select(int32 startOffset
, int32 endOffset
)
1411 _CancelInputMethod();
1413 // pin offsets at reasonable values
1414 if (startOffset
< 0)
1416 else if (startOffset
> fText
->Length())
1417 startOffset
= fText
->Length();
1420 else if (endOffset
> fText
->Length())
1421 endOffset
= fText
->Length();
1423 // a negative selection?
1424 if (startOffset
> endOffset
)
1427 // is the new selection any different from the current selection?
1428 if (startOffset
== fSelStart
&& endOffset
== fSelEnd
)
1431 fStyles
->InvalidateNullStyle();
1435 if (startOffset
== endOffset
) {
1436 if (fSelStart
!= fSelEnd
) {
1437 // unhilite the selection
1439 Highlight(fSelStart
, fSelEnd
);
1441 fSelStart
= fSelEnd
= fCaretOffset
= startOffset
;
1445 // draw only those ranges that are different
1447 if (startOffset
!= fSelStart
) {
1448 // start of selection has changed
1449 if (startOffset
> fSelStart
) {
1453 start
= startOffset
;
1456 Highlight(start
, end
);
1459 if (endOffset
!= fSelEnd
) {
1460 // end of selection has changed
1461 if (endOffset
> fSelEnd
) {
1468 Highlight(start
, end
);
1471 fSelStart
= startOffset
;
1472 fSelEnd
= endOffset
;
1478 BTextView::SelectAll()
1480 Select(0, fText
->Length());
1485 BTextView::GetSelection(int32
* _start
, int32
* _end
) const
1504 BTextView::SetFontAndColor(const BFont
* font
, uint32 mode
,
1505 const rgb_color
* color
)
1507 SetFontAndColor(fSelStart
, fSelEnd
, font
, mode
, color
);
1512 BTextView::SetFontAndColor(int32 startOffset
, int32 endOffset
,
1513 const BFont
* font
, uint32 mode
, const rgb_color
* color
)
1519 const int32 textLength
= fText
->Length();
1522 // When the text view is not stylable, we always set the whole text's
1523 // style and ignore the offsets
1525 endOffset
= textLength
;
1527 // pin offsets at reasonable values
1528 if (startOffset
< 0)
1530 else if (startOffset
> textLength
)
1531 startOffset
= textLength
;
1535 else if (endOffset
> textLength
)
1536 endOffset
= textLength
;
1539 // apply the style to the style buffer
1540 fStyles
->InvalidateNullStyle();
1541 _ApplyStyleRange(startOffset
, endOffset
, mode
, font
, color
);
1543 if ((mode
& (B_FONT_FAMILY_AND_STYLE
| B_FONT_SIZE
)) != 0) {
1544 // ToDo: maybe only invalidate the layout (depending on
1545 // B_SUPPORTS_LAYOUT) and have it _Refresh() automatically?
1547 // recalc the line breaks and redraw with new style
1548 _Refresh(startOffset
, endOffset
, false);
1550 // the line breaks wont change, simply redraw
1551 _RequestDrawLines(_LineAt(startOffset
), _LineAt(endOffset
));
1559 BTextView::GetFontAndColor(int32 offset
, BFont
* _font
,
1560 rgb_color
* _color
) const
1562 fStyles
->GetStyle(offset
, _font
, _color
);
1567 BTextView::GetFontAndColor(BFont
* _font
, uint32
* _mode
,
1568 rgb_color
* _color
, bool* _sameColor
) const
1570 fStyles
->ContinuousGetStyle(_font
, _mode
, _color
, _sameColor
,
1571 fSelStart
, fSelEnd
);
1576 BTextView::SetRunArray(int32 startOffset
, int32 endOffset
,
1577 const text_run_array
* runs
)
1581 _CancelInputMethod();
1583 text_run_array oneRun
;
1586 // when the text view is not stylable, we always set the whole text's
1587 // style with the first run and ignore the offsets
1588 if (runs
->count
== 0)
1592 endOffset
= fText
->Length();
1594 oneRun
.runs
[0] = runs
->runs
[0];
1595 oneRun
.runs
[0].offset
= 0;
1598 // pin offsets at reasonable values
1599 if (startOffset
< 0)
1601 else if (startOffset
> fText
->Length())
1602 startOffset
= fText
->Length();
1606 else if (endOffset
> fText
->Length())
1607 endOffset
= fText
->Length();
1610 _SetRunArray(startOffset
, endOffset
, runs
);
1612 _Refresh(startOffset
, endOffset
, false);
1617 BTextView::RunArray(int32 startOffset
, int32 endOffset
, int32
* _size
) const
1619 // pin offsets at reasonable values
1620 if (startOffset
< 0)
1622 else if (startOffset
> fText
->Length())
1623 startOffset
= fText
->Length();
1627 else if (endOffset
> fText
->Length())
1628 endOffset
= fText
->Length();
1630 STEStyleRange
* styleRange
1631 = fStyles
->GetStyleRange(startOffset
, endOffset
- 1);
1632 if (styleRange
== NULL
)
1635 text_run_array
* runArray
= AllocRunArray(styleRange
->count
, _size
);
1636 if (runArray
!= NULL
) {
1637 for (int32 i
= 0; i
< runArray
->count
; i
++) {
1638 runArray
->runs
[i
].offset
= styleRange
->runs
[i
].offset
;
1639 runArray
->runs
[i
].font
= styleRange
->runs
[i
].style
.font
;
1640 runArray
->runs
[i
].color
= styleRange
->runs
[i
].style
.color
;
1651 BTextView::LineAt(int32 offset
) const
1653 // pin offset at reasonable values
1656 else if (offset
> fText
->Length())
1657 offset
= fText
->Length();
1659 int32 lineNum
= _LineAt(offset
);
1660 if (_IsOnEmptyLastLine(offset
))
1667 BTextView::LineAt(BPoint point
) const
1669 int32 lineNum
= _LineAt(point
);
1670 if ((*fLines
)[lineNum
+ 1]->origin
<= point
.y
- fTextRect
.top
)
1678 BTextView::PointAt(int32 offset
, float* _height
) const
1680 // pin offset at reasonable values
1683 else if (offset
> fText
->Length())
1684 offset
= fText
->Length();
1687 int32 lineNum
= _LineAt(offset
);
1688 STELine
* line
= (*fLines
)[lineNum
];
1693 result
.y
= line
->origin
+ fTextRect
.top
;
1695 bool onEmptyLastLine
= _IsOnEmptyLastLine(offset
);
1697 if (fStyles
->NumRuns() == 0) {
1698 // Handle the case where there is only one line (no text inserted)
1699 fStyles
->SyncNullStyle(0);
1700 height
= _NullStyleHeight();
1702 height
= (line
+ 1)->origin
- line
->origin
;
1704 if (onEmptyLastLine
) {
1705 // special case: go down one line if offset is at the newline
1706 // at the end of the buffer ...
1708 // ... and return the height of that (empty) line
1709 fStyles
->SyncNullStyle(offset
);
1710 height
= _NullStyleHeight();
1712 int32 length
= offset
- line
->offset
;
1713 result
.x
+= _TabExpandedStyledWidth(line
->offset
, length
);
1717 if (fAlignment
!= B_ALIGN_LEFT
) {
1718 float lineWidth
= onEmptyLastLine
? 0.0 : LineWidth(lineNum
);
1719 float alignmentOffset
= fTextRect
.Width() - lineWidth
;
1720 if (fAlignment
== B_ALIGN_CENTER
)
1721 alignmentOffset
/= 2;
1722 result
.x
+= alignmentOffset
;
1725 // convert from text rect coordinates
1726 result
.x
+= fTextRect
.left
;
1729 result
.x
= lroundf(result
.x
);
1730 result
.y
= lroundf(result
.y
);
1731 if (_height
!= NULL
)
1739 BTextView::OffsetAt(BPoint point
) const
1741 const int32 textLength
= fText
->Length();
1743 // should we even bother?
1744 if (point
.y
>= fTextRect
.bottom
)
1746 else if (point
.y
< fTextRect
.top
)
1749 int32 lineNum
= _LineAt(point
);
1750 STELine
* line
= (*fLines
)[lineNum
];
1752 #define COMPILE_PROBABLY_BAD_CODE 1
1754 #if COMPILE_PROBABLY_BAD_CODE
1755 // special case: if point is within the text rect and PixelToLine()
1756 // tells us that it's on the last line, but if point is actually
1757 // lower than the bottom of the last line, return the last offset
1758 // (can happen for newlines)
1759 if (lineNum
== (fLines
->NumLines() - 1)) {
1760 if (point
.y
>= ((line
+ 1)->origin
+ fTextRect
.top
))
1765 // convert to text rect coordinates
1766 if (fAlignment
!= B_ALIGN_LEFT
) {
1767 float alignmentOffset
= fTextRect
.Width() - LineWidth(lineNum
);
1768 if (fAlignment
== B_ALIGN_CENTER
)
1769 alignmentOffset
/= 2;
1770 point
.x
-= alignmentOffset
;
1773 point
.x
-= fTextRect
.left
;
1774 point
.x
= max_c(point
.x
, 0.0);
1776 // ToDo: The following code isn't very efficient, because it always starts
1777 // from the left end, so when the point is near the right end it's very
1779 int32 offset
= line
->offset
;
1780 const int32 limit
= (line
+ 1)->offset
;
1783 const int32 nextInitial
= _NextInitialByte(offset
);
1784 const int32 saveOffset
= offset
;
1786 if (ByteAt(offset
) == B_TAB
)
1787 width
= _ActualTabWidth(location
);
1789 width
= _StyledWidth(saveOffset
, nextInitial
- saveOffset
);
1790 if (location
+ width
> point
.x
) {
1791 if (fabs(location
+ width
- point
.x
) < fabs(location
- point
.x
))
1792 offset
= nextInitial
;
1797 offset
= nextInitial
;
1798 } while (offset
< limit
);
1800 if (offset
== (line
+ 1)->offset
) {
1801 // special case: newlines aren't visible
1802 // return the offset of the character preceding the newline
1803 if (ByteAt(offset
- 1) == B_ENTER
)
1806 // special case: return the offset preceding any spaces that
1807 // aren't at the end of the buffer
1808 if (offset
!= textLength
&& ByteAt(offset
- 1) == B_SPACE
)
1817 BTextView::OffsetAt(int32 line
) const
1822 if (line
> fLines
->NumLines())
1823 return fText
->Length();
1825 return (*fLines
)[line
]->offset
;
1830 BTextView::FindWord(int32 offset
, int32
* _fromOffset
, int32
* _toOffset
)
1842 if (offset
> fText
->Length()) {
1844 *_fromOffset
= fText
->Length();
1847 *_toOffset
= fText
->Length();
1853 *_fromOffset
= _PreviousWordBoundary(offset
);
1856 *_toOffset
= _NextWordBoundary(offset
);
1861 BTextView::CanEndLine(int32 offset
)
1863 if (offset
< 0 || offset
> fText
->Length())
1866 // TODO: This should be improved using the LocaleKit.
1867 uint32 classification
= _CharClassification(offset
);
1869 // wrapping is always allowed at end of text and at newlines
1870 if (classification
== CHAR_CLASS_END_OF_TEXT
|| ByteAt(offset
) == B_ENTER
)
1873 uint32 nextClassification
= _CharClassification(offset
+ 1);
1874 if (nextClassification
== CHAR_CLASS_END_OF_TEXT
)
1877 // never separate a punctuation char from its preceeding word
1878 if (classification
== CHAR_CLASS_DEFAULT
1879 && nextClassification
== CHAR_CLASS_PUNCTUATION
) {
1883 if ((classification
== CHAR_CLASS_WHITESPACE
1884 && nextClassification
!= CHAR_CLASS_WHITESPACE
)
1885 || (classification
!= CHAR_CLASS_WHITESPACE
1886 && nextClassification
== CHAR_CLASS_WHITESPACE
)) {
1890 // allow wrapping after whitespace, unless more whitespace (except for
1892 if (classification
== CHAR_CLASS_WHITESPACE
1893 && (nextClassification
!= CHAR_CLASS_WHITESPACE
1894 || ByteAt(offset
+ 1) == B_ENTER
)) {
1898 // allow wrapping after punctuation chars, unless more punctuation, closing
1899 // parens or quotes follow
1900 if (classification
== CHAR_CLASS_PUNCTUATION
1901 && nextClassification
!= CHAR_CLASS_PUNCTUATION
1902 && nextClassification
!= CHAR_CLASS_PARENS_CLOSE
1903 && nextClassification
!= CHAR_CLASS_QUOTE
) {
1907 // allow wrapping after quotes, graphical chars and closing parens only if
1908 // whitespace follows (not perfect, but seems to do the right thing most
1910 if ((classification
== CHAR_CLASS_QUOTE
1911 || classification
== CHAR_CLASS_GRAPHICAL
1912 || classification
== CHAR_CLASS_PARENS_CLOSE
)
1913 && nextClassification
== CHAR_CLASS_WHITESPACE
) {
1922 BTextView::LineWidth(int32 lineNumber
) const
1924 if (lineNumber
< 0 || lineNumber
>= fLines
->NumLines())
1927 STELine
* line
= (*fLines
)[lineNumber
];
1928 int32 length
= (line
+ 1)->offset
- line
->offset
;
1930 // skip newline at the end of the line, if any, as it does no contribute
1932 if (ByteAt((line
+ 1)->offset
- 1) == B_ENTER
)
1935 return _TabExpandedStyledWidth(line
->offset
, length
);
1940 BTextView::LineHeight(int32 lineNumber
) const
1942 float lineHeight
= TextHeight(lineNumber
, lineNumber
);
1943 if (lineHeight
== 0.0) {
1944 // We probably don't have text content yet. Take the initial
1945 // style's font height or fall back to the plain font.
1947 fStyles
->GetNullStyle(&font
, NULL
);
1949 font
= be_plain_font
;
1951 font_height fontHeight
;
1952 font
->GetHeight(&fontHeight
);
1953 // This is how the height is calculated in _RecalculateLineBreaks().
1954 lineHeight
= ceilf(fontHeight
.ascent
+ fontHeight
.descent
) + 1;
1962 BTextView::TextHeight(int32 startLine
, int32 endLine
) const
1964 const int32 numLines
= fLines
->NumLines();
1967 else if (startLine
> numLines
- 1)
1968 startLine
= numLines
- 1;
1972 else if (endLine
> numLines
- 1)
1973 endLine
= numLines
- 1;
1975 float height
= (*fLines
)[endLine
+ 1]->origin
1976 - (*fLines
)[startLine
]->origin
;
1978 if (startLine
!= endLine
&& endLine
== numLines
- 1
1979 && fText
->RealCharAt(fText
->Length() - 1) == B_ENTER
) {
1980 height
+= (*fLines
)[endLine
+ 1]->origin
- (*fLines
)[endLine
]->origin
;
1983 return ceilf(height
);
1988 BTextView::GetTextRegion(int32 startOffset
, int32 endOffset
,
1989 BRegion
* outRegion
) const
1994 outRegion
->MakeEmpty();
1996 // pin offsets at reasonable values
1997 if (startOffset
< 0)
1999 else if (startOffset
> fText
->Length())
2000 startOffset
= fText
->Length();
2003 else if (endOffset
> fText
->Length())
2004 endOffset
= fText
->Length();
2006 // return an empty region if the range is invalid
2007 if (startOffset
>= endOffset
)
2010 float startLineHeight
= 0.0;
2011 float endLineHeight
= 0.0;
2012 BPoint startPt
= PointAt(startOffset
, &startLineHeight
);
2013 BPoint endPt
= PointAt(endOffset
, &endLineHeight
);
2015 startLineHeight
= ceilf(startLineHeight
);
2016 endLineHeight
= ceilf(endLineHeight
);
2020 if (startPt
.y
== endPt
.y
) {
2021 // this is a one-line region
2022 selRect
.left
= max_c(startPt
.x
, fTextRect
.left
);
2023 selRect
.top
= startPt
.y
;
2024 selRect
.right
= endPt
.x
- 1.0;
2025 selRect
.bottom
= endPt
.y
+ endLineHeight
- 1.0;
2026 outRegion
->Include(selRect
);
2028 // more than one line in the specified offset range
2029 selRect
.left
= max_c(startPt
.x
, fTextRect
.left
);
2030 selRect
.top
= startPt
.y
;
2031 selRect
.right
= fTextRect
.right
;
2032 selRect
.bottom
= startPt
.y
+ startLineHeight
- 1.0;
2033 outRegion
->Include(selRect
);
2035 if (startPt
.y
+ startLineHeight
< endPt
.y
) {
2036 // more than two lines in the range
2037 selRect
.left
= fTextRect
.left
;
2038 selRect
.top
= startPt
.y
+ startLineHeight
;
2039 selRect
.right
= fTextRect
.right
;
2040 selRect
.bottom
= endPt
.y
- 1.0;
2041 outRegion
->Include(selRect
);
2044 selRect
.left
= fTextRect
.left
;
2045 selRect
.top
= endPt
.y
;
2046 selRect
.right
= endPt
.x
- 1.0;
2047 selRect
.bottom
= endPt
.y
+ endLineHeight
- 1.0;
2048 outRegion
->Include(selRect
);
2054 BTextView::ScrollToOffset(int32 offset
)
2056 // pin offset at reasonable values
2059 else if (offset
> fText
->Length())
2060 offset
= fText
->Length();
2062 BRect bounds
= Bounds();
2063 float lineHeight
= 0.0;
2066 BPoint point
= PointAt(offset
, &lineHeight
);
2069 float extraSpace
= fAlignment
== B_ALIGN_LEFT
?
2070 ceilf(bounds
.IntegerWidth() / 2) : 0.0;
2072 if (point
.x
< bounds
.left
)
2073 xDiff
= point
.x
- bounds
.left
- extraSpace
;
2074 else if (point
.x
> bounds
.right
)
2075 xDiff
= point
.x
- bounds
.right
+ extraSpace
;
2078 if (point
.y
< bounds
.top
)
2079 yDiff
= point
.y
- bounds
.top
;
2080 else if (point
.y
+ lineHeight
> bounds
.bottom
2081 && point
.y
- lineHeight
> bounds
.top
) {
2082 yDiff
= point
.y
+ lineHeight
- bounds
.bottom
;
2085 // prevent negative scroll offset
2086 if (bounds
.left
+ xDiff
< 0.0)
2087 xDiff
= -bounds
.left
;
2088 if (bounds
.top
+ yDiff
< 0.0)
2089 yDiff
= -bounds
.top
;
2091 ScrollBy(xDiff
, yDiff
);
2096 BTextView::ScrollToSelection()
2098 ScrollToOffset(fSelStart
);
2103 BTextView::Highlight(int32 startOffset
, int32 endOffset
)
2105 // pin offsets at reasonable values
2106 if (startOffset
< 0)
2108 else if (startOffset
> fText
->Length())
2109 startOffset
= fText
->Length();
2112 else if (endOffset
> fText
->Length())
2113 endOffset
= fText
->Length();
2115 if (startOffset
>= endOffset
)
2119 GetTextRegion(startOffset
, endOffset
, &selRegion
);
2121 SetDrawingMode(B_OP_INVERT
);
2122 FillRegion(&selRegion
, B_SOLID_HIGH
);
2123 SetDrawingMode(B_OP_COPY
);
2127 // #pragma mark - Configuration methods
2131 BTextView::SetTextRect(BRect rect
)
2133 if (rect
== fTextRect
)
2137 rect
.right
= Bounds().right
- fLayoutData
->rightInset
;
2138 rect
.bottom
= Bounds().bottom
- fLayoutData
->bottomInset
;
2141 fLayoutData
->UpdateInsets(Bounds().OffsetToCopy(B_ORIGIN
), rect
);
2148 BTextView::TextRect() const
2155 BTextView::_ResetTextRect()
2157 BRect
oldTextRect(fTextRect
);
2158 // reset text rect to bounds minus insets ...
2159 fTextRect
= Bounds().OffsetToCopy(B_ORIGIN
);
2160 fTextRect
.left
+= fLayoutData
->leftInset
;
2161 fTextRect
.top
+= fLayoutData
->topInset
;
2162 fTextRect
.right
-= fLayoutData
->rightInset
;
2163 fTextRect
.bottom
-= fLayoutData
->bottomInset
;
2165 // and rewrap (potentially adjusting the right and the bottom of the text
2167 _Refresh(0, TextLength(), false);
2169 // Make sure that the dirty area outside the text is redrawn too.
2170 BRegion
invalid(oldTextRect
| fTextRect
);
2171 invalid
.Exclude(fTextRect
);
2172 Invalidate(&invalid
);
2177 BTextView::SetInsets(float left
, float top
, float right
, float bottom
)
2179 if (fLayoutData
->leftInset
== left
2180 && fLayoutData
->topInset
== top
2181 && fLayoutData
->rightInset
== right
2182 && fLayoutData
->bottomInset
== bottom
)
2185 fLayoutData
->leftInset
= left
;
2186 fLayoutData
->topInset
= top
;
2187 fLayoutData
->rightInset
= right
;
2188 fLayoutData
->bottomInset
= bottom
;
2196 BTextView::GetInsets(float* _left
, float* _top
, float* _right
,
2197 float* _bottom
) const
2200 *_left
= fLayoutData
->leftInset
;
2202 *_top
= fLayoutData
->topInset
;
2204 *_right
= fLayoutData
->rightInset
;
2206 *_bottom
= fLayoutData
->bottomInset
;
2211 BTextView::SetStylable(bool stylable
)
2213 fStylable
= stylable
;
2218 BTextView::IsStylable() const
2225 BTextView::SetTabWidth(float width
)
2227 if (width
== fTabWidth
)
2232 if (Window() != NULL
)
2233 _Refresh(0, fText
->Length(), false);
2238 BTextView::TabWidth() const
2245 BTextView::MakeSelectable(bool selectable
)
2247 if (selectable
== fSelectable
)
2250 fSelectable
= selectable
;
2252 if (fActive
&& fSelStart
!= fSelEnd
&& Window() != NULL
)
2253 Highlight(fSelStart
, fSelEnd
);
2258 BTextView::IsSelectable() const
2265 BTextView::MakeEditable(bool editable
)
2267 if (editable
== fEditable
)
2270 fEditable
= editable
;
2271 // TextControls change the color of the text when
2272 // they are made editable, so we need to invalidate
2273 // the NULL style here
2274 // TODO: it works well, but it could be caused by a bug somewhere else
2276 fStyles
->InvalidateNullStyle();
2277 if (Window() != NULL
&& fActive
) {
2280 _CancelInputMethod();
2287 BTextView::IsEditable() const
2294 BTextView::SetWordWrap(bool wrap
)
2299 bool updateOnScreen
= fActive
&& Window() != NULL
;
2300 if (updateOnScreen
) {
2301 // hide the caret, unhilite the selection
2302 if (fSelStart
!= fSelEnd
) {
2304 Highlight(fSelStart
, fSelEnd
);
2312 _Refresh(0, fText
->Length(), false);
2314 if (updateOnScreen
) {
2315 // show the caret, hilite the selection
2316 if (fSelStart
!= fSelEnd
) {
2318 Highlight(fSelStart
, fSelEnd
);
2326 BTextView::DoesWordWrap() const
2333 BTextView::SetMaxBytes(int32 max
)
2335 const int32 textLength
= fText
->Length();
2338 if (fMaxBytes
< textLength
) {
2339 int32 offset
= fMaxBytes
;
2340 // Delete the text after fMaxBytes, but
2341 // respect multibyte characters boundaries.
2342 const int32 previousInitial
= _PreviousInitialByte(offset
);
2343 if (_NextInitialByte(previousInitial
) != offset
)
2344 offset
= previousInitial
;
2346 Delete(offset
, textLength
);
2352 BTextView::MaxBytes() const
2359 BTextView::DisallowChar(uint32 character
)
2361 if (fDisallowedChars
== NULL
)
2362 fDisallowedChars
= new BList
;
2363 if (!fDisallowedChars
->HasItem(reinterpret_cast<void*>(character
)))
2364 fDisallowedChars
->AddItem(reinterpret_cast<void*>(character
));
2369 BTextView::AllowChar(uint32 character
)
2371 if (fDisallowedChars
!= NULL
)
2372 fDisallowedChars
->RemoveItem(reinterpret_cast<void*>(character
));
2377 BTextView::SetAlignment(alignment align
)
2379 // Do a reality check
2380 if (fAlignment
!= align
&&
2381 (align
== B_ALIGN_LEFT
||
2382 align
== B_ALIGN_RIGHT
||
2383 align
== B_ALIGN_CENTER
)) {
2386 // After setting new alignment, update the view/window
2387 if (Window() != NULL
)
2394 BTextView::Alignment() const
2401 BTextView::SetAutoindent(bool state
)
2403 fAutoindent
= state
;
2408 BTextView::DoesAutoindent() const
2415 BTextView::SetColorSpace(color_space colors
)
2417 if (colors
!= fColorSpace
&& fOffscreen
) {
2418 fColorSpace
= colors
;
2426 BTextView::ColorSpace() const
2433 BTextView::MakeResizable(bool resize
, BView
* resizeView
)
2437 fContainerView
= resizeView
;
2439 // Wrapping mode and resizable mode can't live together
2443 if (fActive
&& Window() != NULL
) {
2444 if (fSelStart
!= fSelEnd
) {
2446 Highlight(fSelStart
, fSelEnd
);
2451 // We need to reset the right inset, as otherwise the auto-resize would
2452 // get confused about just how wide the textview needs to be.
2453 // This seems to be an artefact of how Tracker creates the textview
2454 // during a rename action.
2455 fLayoutData
->rightInset
= fLayoutData
->leftInset
;
2458 fContainerView
= NULL
;
2464 _Refresh(0, fText
->Length(), false);
2469 BTextView::IsResizable() const
2476 BTextView::SetDoesUndo(bool undo
)
2478 if (undo
&& fUndo
== NULL
)
2479 fUndo
= new UndoBuffer(this, B_UNDO_UNAVAILABLE
);
2480 else if (!undo
&& fUndo
!= NULL
) {
2488 BTextView::DoesUndo() const
2490 return fUndo
!= NULL
;
2495 BTextView::HideTyping(bool enabled
)
2498 Delete(0, fText
->Length());
2500 fText
->SetPasswordMode(enabled
);
2505 BTextView::IsTypingHidden() const
2507 return fText
->PasswordMode();
2511 // #pragma mark - Size methods
2515 BTextView::ResizeToPreferred()
2517 BView::ResizeToPreferred();
2522 BTextView::GetPreferredSize(float* _width
, float* _height
)
2526 _ValidateLayoutData();
2529 float width
= Bounds().Width();
2530 if (width
< fLayoutData
->min
.width
2531 || (Flags() & B_SUPPORTS_LAYOUT
) != 0) {
2532 width
= fLayoutData
->min
.width
;
2538 float height
= Bounds().Height();
2539 if (height
< fLayoutData
->min
.height
2540 || (Flags() & B_SUPPORTS_LAYOUT
) != 0) {
2541 height
= fLayoutData
->min
.height
;
2549 BTextView::MinSize()
2553 _ValidateLayoutData();
2554 return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData
->min
);
2559 BTextView::MaxSize()
2563 return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
2564 BSize(B_SIZE_UNLIMITED
, B_SIZE_UNLIMITED
));
2569 BTextView::PreferredSize()
2573 _ValidateLayoutData();
2574 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
2575 fLayoutData
->preferred
);
2580 BTextView::HasHeightForWidth()
2583 return BView::HasHeightForWidth();
2585 // When not editable, we assume that all text is supposed to be visible.
2591 BTextView::GetHeightForWidth(float width
, float* min
, float* max
,
2595 BView::GetHeightForWidth(width
, min
, max
, preferred
);
2599 // TODO: don't change the actual text rect!
2600 fTextRect
.right
= fTextRect
.left
+ width
;
2601 _Refresh(0, TextLength(), false);
2604 *min
= fTextRect
.Height();
2606 *max
= B_SIZE_UNLIMITED
;
2607 if (preferred
!= NULL
)
2608 *preferred
= fTextRect
.Height();
2612 // #pragma mark - Layout methods
2616 BTextView::LayoutInvalidated(bool descendants
)
2620 fLayoutData
->valid
= false;
2625 BTextView::DoLayout()
2627 // Bail out, if we shan't do layout.
2628 if (!(Flags() & B_SUPPORTS_LAYOUT
))
2633 // If the user set a layout, we let the base class version call its
2640 _ValidateLayoutData();
2642 // validate current size
2643 BSize
size(Bounds().Size());
2644 if (size
.width
< fLayoutData
->min
.width
)
2645 size
.width
= fLayoutData
->min
.width
;
2646 if (size
.height
< fLayoutData
->min
.height
)
2647 size
.height
= fLayoutData
->min
.height
;
2654 BTextView::_ValidateLayoutData()
2656 if (fLayoutData
->valid
)
2661 float lineHeight
= ceilf(LineHeight(0));
2662 TRACE("line height: %.2f\n", lineHeight
);
2664 // compute our minimal size
2665 BSize
min(lineHeight
* 3, lineHeight
);
2666 min
.width
+= fLayoutData
->leftInset
+ fLayoutData
->rightInset
;
2667 min
.height
+= fLayoutData
->topInset
+ fLayoutData
->bottomInset
;
2669 fLayoutData
->min
= min
;
2671 // compute our preferred size
2672 fLayoutData
->preferred
.height
= fTextRect
.Height()
2673 + fLayoutData
->topInset
+ fLayoutData
->bottomInset
;
2676 fLayoutData
->preferred
.width
= min
.width
+ 5 * lineHeight
;
2678 float maxWidth
= fLines
->MaxWidth();
2679 if (maxWidth
< min
.width
)
2680 maxWidth
= min
.width
;
2682 fLayoutData
->preferred
.width
2683 = maxWidth
+ fLayoutData
->leftInset
+ fLayoutData
->rightInset
;
2686 fLayoutData
->valid
= true;
2687 ResetLayoutInvalidation();
2689 TRACE("width: %.2f, height: %.2f\n", min
.width
, min
.height
);
2697 BTextView::AllAttached()
2699 BView::AllAttached();
2704 BTextView::AllDetached()
2706 BView::AllDetached();
2712 BTextView::AllocRunArray(int32 entryCount
, int32
* outSize
)
2714 int32 size
= sizeof(text_run_array
) + (entryCount
- 1) * sizeof(text_run
);
2716 text_run_array
* runArray
= (text_run_array
*)calloc(size
, 1);
2717 if (runArray
== NULL
) {
2718 if (outSize
!= NULL
)
2723 runArray
->count
= entryCount
;
2725 // Call constructors explicitly as the text_run_array
2726 // was allocated with malloc (and has to, for backwards
2728 for (int32 i
= 0; i
< runArray
->count
; i
++) {
2729 new (&runArray
->runs
[i
].font
) BFont
;
2732 if (outSize
!= NULL
)
2741 BTextView::CopyRunArray(const text_run_array
* orig
, int32 countDelta
)
2743 text_run_array
* copy
= AllocRunArray(countDelta
, NULL
);
2745 for (int32 i
= 0; i
< countDelta
; i
++) {
2746 copy
->runs
[i
].offset
= orig
->runs
[i
].offset
;
2747 copy
->runs
[i
].font
= orig
->runs
[i
].font
;
2748 copy
->runs
[i
].color
= orig
->runs
[i
].color
;
2757 BTextView::FreeRunArray(text_run_array
* array
)
2762 // Call destructors explicitly
2763 for (int32 i
= 0; i
< array
->count
; i
++)
2764 array
->runs
[i
].font
.~BFont();
2772 BTextView::FlattenRunArray(const text_run_array
* runArray
, int32
* _size
)
2775 int32 size
= sizeof(flattened_text_run_array
) + (runArray
->count
- 1)
2776 * sizeof(flattened_text_run
);
2778 flattened_text_run_array
* array
= (flattened_text_run_array
*)malloc(size
);
2779 if (array
== NULL
) {
2785 array
->magic
= B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayMagic
);
2786 array
->version
= B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayVersion
);
2787 array
->count
= B_HOST_TO_BENDIAN_INT32(runArray
->count
);
2789 for (int32 i
= 0; i
< runArray
->count
; i
++) {
2790 array
->styles
[i
].offset
= B_HOST_TO_BENDIAN_INT32(
2791 runArray
->runs
[i
].offset
);
2792 runArray
->runs
[i
].font
.GetFamilyAndStyle(&array
->styles
[i
].family
,
2793 &array
->styles
[i
].style
);
2794 array
->styles
[i
].size
= B_HOST_TO_BENDIAN_FLOAT(
2795 runArray
->runs
[i
].font
.Size());
2796 array
->styles
[i
].shear
= B_HOST_TO_BENDIAN_FLOAT(
2797 runArray
->runs
[i
].font
.Shear());
2798 array
->styles
[i
].face
= B_HOST_TO_BENDIAN_INT16(
2799 runArray
->runs
[i
].font
.Face());
2800 array
->styles
[i
].red
= runArray
->runs
[i
].color
.red
;
2801 array
->styles
[i
].green
= runArray
->runs
[i
].color
.green
;
2802 array
->styles
[i
].blue
= runArray
->runs
[i
].color
.blue
;
2803 array
->styles
[i
].alpha
= 255;
2804 array
->styles
[i
]._reserved_
= 0;
2816 BTextView::UnflattenRunArray(const void* data
, int32
* _size
)
2819 flattened_text_run_array
* array
= (flattened_text_run_array
*)data
;
2821 if (B_BENDIAN_TO_HOST_INT32(array
->magic
) != kFlattenedTextRunArrayMagic
2822 || B_BENDIAN_TO_HOST_INT32(array
->version
)
2823 != kFlattenedTextRunArrayVersion
) {
2830 int32 count
= B_BENDIAN_TO_HOST_INT32(array
->count
);
2832 text_run_array
* runArray
= AllocRunArray(count
, _size
);
2833 if (runArray
== NULL
)
2836 for (int32 i
= 0; i
< count
; i
++) {
2837 runArray
->runs
[i
].offset
= B_BENDIAN_TO_HOST_INT32(
2838 array
->styles
[i
].offset
);
2840 // Set family and style independently from each other, so that
2841 // even if the family doesn't exist, we try to preserve the style
2842 runArray
->runs
[i
].font
.SetFamilyAndStyle(array
->styles
[i
].family
, NULL
);
2843 runArray
->runs
[i
].font
.SetFamilyAndStyle(NULL
, array
->styles
[i
].style
);
2845 runArray
->runs
[i
].font
.SetSize(B_BENDIAN_TO_HOST_FLOAT(
2846 array
->styles
[i
].size
));
2847 runArray
->runs
[i
].font
.SetShear(B_BENDIAN_TO_HOST_FLOAT(
2848 array
->styles
[i
].shear
));
2850 uint16 face
= B_BENDIAN_TO_HOST_INT16(array
->styles
[i
].face
);
2851 if (face
!= B_REGULAR_FACE
) {
2852 // Be's version doesn't seem to set this correctly
2853 runArray
->runs
[i
].font
.SetFace(face
);
2856 runArray
->runs
[i
].color
.red
= array
->styles
[i
].red
;
2857 runArray
->runs
[i
].color
.green
= array
->styles
[i
].green
;
2858 runArray
->runs
[i
].color
.blue
= array
->styles
[i
].blue
;
2859 runArray
->runs
[i
].color
.alpha
= array
->styles
[i
].alpha
;
2867 BTextView::InsertText(const char* text
, int32 length
, int32 offset
,
2868 const text_run_array
* runs
)
2877 else if (offset
> fText
->Length())
2878 offset
= fText
->Length();
2881 // add the text to the buffer
2882 fText
->InsertText(text
, length
, offset
);
2884 // update the start offsets of each line below offset
2885 fLines
->BumpOffset(length
, _LineAt(offset
) + 1);
2887 // update the style runs
2888 fStyles
->BumpOffset(length
, fStyles
->OffsetToRun(offset
- 1) + 1);
2890 // offset the caret/selection, if the text was inserted before it
2891 if (offset
<= fSelEnd
) {
2892 fSelStart
+= length
;
2893 fCaretOffset
= fSelEnd
= fSelStart
;
2897 if (fStylable
&& runs
!= NULL
) {
2898 _SetRunArray(offset
, offset
+ length
, runs
);
2900 // apply null-style to inserted text
2901 _ApplyStyleRange(offset
, offset
+ length
);
2907 BTextView::DeleteText(int32 fromOffset
, int32 toOffset
)
2913 else if (fromOffset
> fText
->Length())
2914 fromOffset
= fText
->Length();
2918 else if (toOffset
> fText
->Length())
2919 toOffset
= fText
->Length();
2921 if (fromOffset
>= toOffset
)
2924 // set nullStyle to style at beginning of range
2925 fStyles
->InvalidateNullStyle();
2926 fStyles
->SyncNullStyle(fromOffset
);
2928 // remove from the text buffer
2929 fText
->RemoveRange(fromOffset
, toOffset
);
2931 // remove any lines that have been obliterated
2932 fLines
->RemoveLineRange(fromOffset
, toOffset
);
2934 // remove any style runs that have been obliterated
2935 fStyles
->RemoveStyleRange(fromOffset
, toOffset
);
2937 // adjust the selection accordingly, assumes fSelEnd >= fSelStart!
2938 int32 range
= toOffset
- fromOffset
;
2939 if (fSelStart
>= toOffset
) {
2940 // selection is behind the range that was removed
2943 } else if (fSelStart
>= fromOffset
&& fSelEnd
<= toOffset
) {
2944 // the selection is within the range that was removed
2945 fSelStart
= fSelEnd
= fromOffset
;
2946 } else if (fSelStart
>= fromOffset
&& fSelEnd
> toOffset
) {
2947 // the selection starts within and ends after the range
2948 // the remaining part is the part that was after the range
2949 fSelStart
= fromOffset
;
2950 fSelEnd
= fromOffset
+ fSelEnd
- toOffset
;
2951 } else if (fSelStart
< fromOffset
&& fSelEnd
< toOffset
) {
2952 // the selection starts before, but ends within the range
2953 fSelEnd
= fromOffset
;
2954 } else if (fSelStart
< fromOffset
&& fSelEnd
>= toOffset
) {
2955 // the selection starts before and ends after the range
2961 /*! Undoes the last changes.
2963 \param clipboard A \a clipboard to use for the undo operation.
2966 BTextView::Undo(BClipboard
* clipboard
)
2969 fUndo
->Undo(clipboard
);
2974 BTextView::UndoState(bool* isRedo
) const
2976 return fUndo
== NULL
? B_UNDO_UNAVAILABLE
: fUndo
->State(isRedo
);
2980 // #pragma mark - GetDragParameters() is protected
2984 BTextView::GetDragParameters(BMessage
* drag
, BBitmap
** bitmap
, BPoint
* point
,
2991 // Add originator and action
2992 drag
->AddPointer("be:originator", this);
2993 drag
->AddInt32("be_actions", B_TRASH_TARGET
);
2996 int32 numBytes
= fSelEnd
- fSelStart
;
2997 const char* text
= fText
->GetString(fSelStart
, &numBytes
);
2998 drag
->AddData("text/plain", B_MIME_TYPE
, text
, numBytes
);
3000 // add the corresponding styles
3002 text_run_array
* styles
= RunArray(fSelStart
, fSelEnd
, &size
);
3004 if (styles
!= NULL
) {
3005 drag
->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE
,
3008 FreeRunArray(styles
);
3014 if (handler
!= NULL
)
3019 // #pragma mark - FBC padding and forbidden methods
3022 void BTextView::_ReservedTextView3() {}
3023 void BTextView::_ReservedTextView4() {}
3024 void BTextView::_ReservedTextView5() {}
3025 void BTextView::_ReservedTextView6() {}
3026 void BTextView::_ReservedTextView7() {}
3027 void BTextView::_ReservedTextView8() {}
3028 void BTextView::_ReservedTextView9() {}
3029 void BTextView::_ReservedTextView10() {}
3030 void BTextView::_ReservedTextView11() {}
3031 void BTextView::_ReservedTextView12() {}
3034 // #pragma mark - Private methods
3037 /*! Inits the BTextView object.
3039 \param textRect The BTextView's text rect.
3040 \param initialFont The font which the BTextView will use.
3041 \param initialColor The initial color of the text.
3044 BTextView::_InitObject(BRect textRect
, const BFont
* initialFont
,
3045 const rgb_color
* initialColor
)
3048 if (initialFont
== NULL
)
3051 font
= *initialFont
;
3053 _NormalizeFont(&font
);
3055 rgb_color documentTextColor
= ui_color(B_DOCUMENT_TEXT_COLOR
);
3057 if (initialColor
== NULL
)
3058 initialColor
= &documentTextColor
;
3060 fText
= new BPrivate::TextGapBuffer
;
3061 fLines
= new LineBuffer
;
3062 fStyles
= new StyleBuffer(&font
, initialColor
);
3064 fInstalledNavigateCommandWordwiseShortcuts
= false;
3065 fInstalledNavigateOptionWordwiseShortcuts
= false;
3066 fInstalledNavigateOptionLinewiseShortcuts
= false;
3067 fInstalledNavigateHomeEndDocwiseShortcuts
= false;
3069 fInstalledSelectCommandWordwiseShortcuts
= false;
3070 fInstalledSelectOptionWordwiseShortcuts
= false;
3071 fInstalledSelectOptionLinewiseShortcuts
= false;
3072 fInstalledSelectHomeEndDocwiseShortcuts
= false;
3074 // We put these here instead of in the constructor initializer list
3075 // to have less code duplication, and a single place where to do changes
3077 fTextRect
= textRect
;
3078 // NOTE: The only places where text rect is changed:
3079 // * width is possibly adjusted in _AutoResize(),
3080 // * height is adjusted in _RecalculateLineBreaks().
3081 // When used within the layout management framework, the
3082 // text rect is changed to maintain constant insets.
3083 fMinTextRectWidth
= fTextRect
.Width();
3084 // see SetTextRect()
3085 fSelStart
= fSelEnd
= 0;
3086 fCaretVisible
= false;
3099 fMaxBytes
= INT32_MAX
;
3100 fDisallowedChars
= NULL
;
3101 fAlignment
= B_ALIGN_LEFT
;
3102 fAutoindent
= false;
3104 fColorSpace
= B_CMAP8
;
3106 fContainerView
= NULL
;
3110 fClickRunner
= NULL
;
3111 fTrackingMouse
= NULL
;
3113 fLayoutData
= new LayoutData
;
3114 fLayoutData
->UpdateInsets(Bounds().OffsetToCopy(B_ORIGIN
), fTextRect
);
3116 fLastClickOffset
= -1;
3119 SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR
);
3123 //! Handles when Backspace key is pressed.
3125 BTextView::_HandleBackspace()
3128 TypingUndoBuffer
* undoBuffer
= dynamic_cast<TypingUndoBuffer
*>(
3132 fUndo
= undoBuffer
= new TypingUndoBuffer(this);
3134 undoBuffer
->BackwardErase();
3137 if (fSelStart
== fSelEnd
) {
3141 fSelStart
= _PreviousInitialByte(fSelStart
);
3143 Highlight(fSelStart
, fSelEnd
);
3145 DeleteText(fSelStart
, fSelEnd
);
3146 fCaretOffset
= fSelEnd
= fSelStart
;
3148 _Refresh(fSelStart
, fSelEnd
, true);
3152 //! Handles when an arrow key is pressed.
3154 BTextView::_HandleArrowKey(uint32 arrowKey
, int32 modifiers
)
3156 // return if there's nowhere to go
3157 if (fText
->Length() == 0)
3160 int32 selStart
= fSelStart
;
3161 int32 selEnd
= fSelEnd
;
3163 if (modifiers
< 0) {
3164 BMessage
* currentMessage
= Window()->CurrentMessage();
3165 if (currentMessage
== NULL
3166 || currentMessage
->FindInt32("modifiers", &modifiers
) != B_OK
) {
3171 bool shiftKeyDown
= (modifiers
& B_SHIFT_KEY
) != 0;
3172 bool controlKeyDown
= (modifiers
& B_CONTROL_KEY
) != 0;
3173 bool optionKeyDown
= (modifiers
& B_OPTION_KEY
) != 0;
3174 bool commandKeyDown
= (modifiers
& B_COMMAND_KEY
) != 0;
3176 int32 lastClickOffset
= fCaretOffset
;
3181 _ScrollBy(-1 * kHorizontalScrollBarStep
, 0);
3182 else if (fSelStart
!= fSelEnd
&& !shiftKeyDown
)
3183 fCaretOffset
= fSelStart
;
3185 if ((commandKeyDown
|| optionKeyDown
) && !controlKeyDown
)
3186 fCaretOffset
= _PreviousWordStart(fCaretOffset
- 1);
3188 fCaretOffset
= _PreviousInitialByte(fCaretOffset
);
3190 if (shiftKeyDown
&& fCaretOffset
!= lastClickOffset
) {
3191 if (fCaretOffset
< fSelStart
) {
3192 // extend selection to the left
3193 selStart
= fCaretOffset
;
3194 if (lastClickOffset
> fSelStart
) {
3195 // caret has jumped across "anchor"
3199 // shrink selection from the right
3200 selEnd
= fCaretOffset
;
3208 _ScrollBy(kHorizontalScrollBarStep
, 0);
3209 else if (fSelStart
!= fSelEnd
&& !shiftKeyDown
)
3210 fCaretOffset
= fSelEnd
;
3212 if ((commandKeyDown
|| optionKeyDown
) && !controlKeyDown
)
3213 fCaretOffset
= _NextWordEnd(fCaretOffset
);
3215 fCaretOffset
= _NextInitialByte(fCaretOffset
);
3217 if (shiftKeyDown
&& fCaretOffset
!= lastClickOffset
) {
3218 if (fCaretOffset
> fSelEnd
) {
3219 // extend selection to the right
3220 selEnd
= fCaretOffset
;
3221 if (lastClickOffset
< fSelEnd
) {
3222 // caret has jumped across "anchor"
3226 // shrink selection from the left
3227 selStart
= fCaretOffset
;
3236 _ScrollBy(0, -1 * kVerticalScrollBarStep
);
3237 else if (fSelStart
!= fSelEnd
&& !shiftKeyDown
)
3238 fCaretOffset
= fSelStart
;
3240 if (optionKeyDown
&& !commandKeyDown
&& !controlKeyDown
)
3241 fCaretOffset
= _PreviousLineStart(fCaretOffset
);
3242 else if (commandKeyDown
&& !optionKeyDown
&& !controlKeyDown
) {
3247 BPoint point
= PointAt(fCaretOffset
, &height
);
3248 // find the caret position on the previous
3249 // line by gently stepping onto this line
3250 for (int i
= 1; i
<= height
; i
++) {
3252 int32 offset
= OffsetAt(point
);
3253 if (offset
< fCaretOffset
|| i
== height
) {
3254 fCaretOffset
= offset
;
3260 if (shiftKeyDown
&& fCaretOffset
!= lastClickOffset
) {
3261 if (fCaretOffset
< fSelStart
) {
3262 // extend selection to the top
3263 selStart
= fCaretOffset
;
3264 if (lastClickOffset
> fSelStart
) {
3265 // caret has jumped across "anchor"
3269 // shrink selection from the bottom
3270 selEnd
= fCaretOffset
;
3280 _ScrollBy(0, kVerticalScrollBarStep
);
3281 else if (fSelStart
!= fSelEnd
&& !shiftKeyDown
)
3282 fCaretOffset
= fSelEnd
;
3284 if (optionKeyDown
&& !commandKeyDown
&& !controlKeyDown
)
3285 fCaretOffset
= _NextLineEnd(fCaretOffset
);
3286 else if (commandKeyDown
&& !optionKeyDown
&& !controlKeyDown
) {
3287 _ScrollTo(0, fTextRect
.bottom
+ fLayoutData
->bottomInset
);
3288 fCaretOffset
= fText
->Length();
3291 BPoint point
= PointAt(fCaretOffset
, &height
);
3293 fCaretOffset
= OffsetAt(point
);
3296 if (shiftKeyDown
&& fCaretOffset
!= lastClickOffset
) {
3297 if (fCaretOffset
> fSelEnd
) {
3298 // extend selection to the bottom
3299 selEnd
= fCaretOffset
;
3300 if (lastClickOffset
< fSelEnd
) {
3301 // caret has jumped across "anchor"
3305 // shrink selection from the top
3306 selStart
= fCaretOffset
;
3314 fStyles
->InvalidateNullStyle();
3318 Select(selStart
, selEnd
);
3320 Select(fCaretOffset
, fCaretOffset
);
3323 ScrollToOffset(fCaretOffset
);
3328 //! Handles when the Delete key is pressed.
3330 BTextView::_HandleDelete()
3333 TypingUndoBuffer
* undoBuffer
= dynamic_cast<TypingUndoBuffer
*>(
3337 fUndo
= undoBuffer
= new TypingUndoBuffer(this);
3339 undoBuffer
->ForwardErase();
3342 if (fSelStart
== fSelEnd
) {
3343 if (fSelEnd
== fText
->Length())
3346 fSelEnd
= _NextInitialByte(fSelEnd
);
3348 Highlight(fSelStart
, fSelEnd
);
3350 DeleteText(fSelStart
, fSelEnd
);
3351 fCaretOffset
= fSelEnd
= fSelStart
;
3353 _Refresh(fSelStart
, fSelEnd
, true);
3357 //! Handles when the Page Up, Page Down, Home, or End key is pressed.
3359 BTextView::_HandlePageKey(uint32 pageKey
, int32 modifiers
)
3361 if (modifiers
< 0) {
3362 BMessage
* currentMessage
= Window()->CurrentMessage();
3363 if (currentMessage
== NULL
3364 || currentMessage
->FindInt32("modifiers", &modifiers
) != B_OK
) {
3369 bool shiftKeyDown
= (modifiers
& B_SHIFT_KEY
) != 0;
3370 bool controlKeyDown
= (modifiers
& B_CONTROL_KEY
) != 0;
3371 bool optionKeyDown
= (modifiers
& B_OPTION_KEY
) != 0;
3372 bool commandKeyDown
= (modifiers
& B_COMMAND_KEY
) != 0;
3374 STELine
* line
= NULL
;
3375 int32 selStart
= fSelStart
;
3376 int32 selEnd
= fSelEnd
;
3378 int32 lastClickOffset
= fCaretOffset
;
3386 if (commandKeyDown
&& !optionKeyDown
&& !controlKeyDown
) {
3390 // get the start of the last line if caret is on it
3391 line
= (*fLines
)[_LineAt(lastClickOffset
)];
3392 fCaretOffset
= line
->offset
;
3396 selStart
= selEnd
= fCaretOffset
;
3397 else if (fCaretOffset
!= lastClickOffset
) {
3398 if (fCaretOffset
< fSelStart
) {
3399 // extend selection to the left
3400 selStart
= fCaretOffset
;
3401 if (lastClickOffset
> fSelStart
) {
3402 // caret has jumped across "anchor"
3406 // shrink selection from the right
3407 selEnd
= fCaretOffset
;
3415 fCaretOffset
= fText
->Length();
3416 _ScrollTo(0, fTextRect
.bottom
+ fLayoutData
->bottomInset
);
3419 if (commandKeyDown
&& !optionKeyDown
&& !controlKeyDown
) {
3420 _ScrollTo(0, fTextRect
.bottom
+ fLayoutData
->bottomInset
);
3421 fCaretOffset
= fText
->Length();
3423 // If we are on the last line, just go to the last
3424 // character in the buffer, otherwise get the starting
3425 // offset of the next line, and go to the previous character
3426 int32 currentLine
= _LineAt(lastClickOffset
);
3427 if (currentLine
+ 1 < fLines
->NumLines()) {
3428 line
= (*fLines
)[currentLine
+ 1];
3429 fCaretOffset
= _PreviousInitialByte(line
->offset
);
3431 // This check is needed to avoid moving the cursor
3432 // when the cursor is on the last line, and that line
3434 if (fCaretOffset
!= fText
->Length()) {
3435 fCaretOffset
= fText
->Length();
3436 if (ByteAt(fCaretOffset
- 1) == B_ENTER
)
3443 selStart
= selEnd
= fCaretOffset
;
3444 else if (fCaretOffset
!= lastClickOffset
) {
3445 if (fCaretOffset
> fSelEnd
) {
3446 // extend selection to the right
3447 selEnd
= fCaretOffset
;
3448 if (lastClickOffset
< fSelEnd
) {
3449 // caret has jumped across "anchor"
3453 // shrink selection from the left
3454 selStart
= fCaretOffset
;
3463 BPoint currentPos
= PointAt(fCaretOffset
, &lineHeight
);
3464 BPoint
nextPos(currentPos
.x
,
3465 currentPos
.y
+ lineHeight
- Bounds().Height());
3466 fCaretOffset
= OffsetAt(nextPos
);
3467 nextPos
= PointAt(fCaretOffset
);
3468 _ScrollBy(0, nextPos
.y
- currentPos
.y
);
3474 selStart
= selEnd
= fCaretOffset
;
3475 else if (fCaretOffset
!= lastClickOffset
) {
3476 if (fCaretOffset
< fSelStart
) {
3477 // extend selection to the top
3478 selStart
= fCaretOffset
;
3479 if (lastClickOffset
> fSelStart
) {
3480 // caret has jumped across "anchor"
3484 // shrink selection from the bottom
3485 selEnd
= fCaretOffset
;
3494 BPoint currentPos
= PointAt(fCaretOffset
);
3495 BPoint
nextPos(currentPos
.x
, currentPos
.y
+ Bounds().Height());
3496 fCaretOffset
= OffsetAt(nextPos
);
3497 nextPos
= PointAt(fCaretOffset
);
3498 _ScrollBy(0, nextPos
.y
- currentPos
.y
);
3504 selStart
= selEnd
= fCaretOffset
;
3505 else if (fCaretOffset
!= lastClickOffset
) {
3506 if (fCaretOffset
> fSelEnd
) {
3507 // extend selection to the bottom
3508 selEnd
= fCaretOffset
;
3509 if (lastClickOffset
< fSelEnd
) {
3510 // caret has jumped across "anchor"
3514 // shrink selection from the top
3515 selStart
= fCaretOffset
;
3525 Select(selStart
, selEnd
);
3527 Select(fCaretOffset
, fCaretOffset
);
3529 ScrollToOffset(fCaretOffset
);
3534 /*! Handles when an alpha-numeric key is pressed.
3536 \param bytes The string or character associated with the key.
3537 \param numBytes The amount of bytes containes in "bytes".
3540 BTextView::_HandleAlphaKey(const char* bytes
, int32 numBytes
)
3542 // TODO: block input if not editable (Andrew)
3544 TypingUndoBuffer
* undoBuffer
= dynamic_cast<TypingUndoBuffer
*>(fUndo
);
3547 fUndo
= undoBuffer
= new TypingUndoBuffer(this);
3549 undoBuffer
->InputCharacter(numBytes
);
3552 if (fSelStart
!= fSelEnd
) {
3553 Highlight(fSelStart
, fSelEnd
);
3554 DeleteText(fSelStart
, fSelEnd
);
3557 if (fAutoindent
&& numBytes
== 1 && *bytes
== B_ENTER
) {
3558 int32 start
, offset
;
3559 start
= offset
= OffsetAt(_LineAt(fSelStart
));
3561 while (ByteAt(offset
) != '\0' &&
3562 (ByteAt(offset
) == B_TAB
|| ByteAt(offset
) == B_SPACE
)
3563 && offset
< fSelStart
)
3566 _DoInsertText(bytes
, numBytes
, fSelStart
, NULL
);
3567 if (start
!= offset
)
3568 _DoInsertText(Text() + start
, offset
- start
, fSelStart
, NULL
);
3570 _DoInsertText(bytes
, numBytes
, fSelStart
, NULL
);
3572 fCaretOffset
= fSelEnd
;
3574 ScrollToOffset(fCaretOffset
);
3578 /*! Redraw the text between the two given offsets, recalculating line-breaks
3581 \param fromOffset The offset from where to refresh.
3582 \param toOffset The offset where to refresh to.
3583 \param scroll If \c true, scroll the view to the end offset.
3586 BTextView::_Refresh(int32 fromOffset
, int32 toOffset
, bool scroll
)
3589 float saveHeight
= fTextRect
.Height();
3590 float saveWidth
= fTextRect
.Width();
3591 int32 fromLine
= _LineAt(fromOffset
);
3592 int32 toLine
= _LineAt(toOffset
);
3593 int32 saveFromLine
= fromLine
;
3594 int32 saveToLine
= toLine
;
3596 _RecalculateLineBreaks(&fromLine
, &toLine
);
3598 // TODO: Maybe there is still something we can do without a window...
3602 BRect bounds
= Bounds();
3603 float newHeight
= fTextRect
.Height();
3605 // if the line breaks have changed, force an erase
3606 if (fromLine
!= saveFromLine
|| toLine
!= saveToLine
3607 || newHeight
!= saveHeight
) {
3611 if (newHeight
!= saveHeight
) {
3612 // the text area has changed
3613 if (newHeight
< saveHeight
)
3614 toLine
= _LineAt(BPoint(0.0f
, saveHeight
+ fTextRect
.top
));
3616 toLine
= _LineAt(BPoint(0.0f
, newHeight
+ fTextRect
.top
));
3619 // draw only those lines that are visible
3620 int32 fromVisible
= _LineAt(BPoint(0.0f
, bounds
.top
));
3621 int32 toVisible
= _LineAt(BPoint(0.0f
, bounds
.bottom
));
3622 fromLine
= max_c(fromVisible
, fromLine
);
3623 toLine
= min_c(toLine
, toVisible
);
3627 _RequestDrawLines(fromLine
, toLine
);
3629 // erase the area below the text
3630 BRect eraseRect
= bounds
;
3631 eraseRect
.top
= fTextRect
.top
+ (*fLines
)[fLines
->NumLines()]->origin
;
3632 eraseRect
.bottom
= fTextRect
.top
+ saveHeight
;
3633 if (eraseRect
.bottom
> eraseRect
.top
&& eraseRect
.Intersects(bounds
)) {
3634 SetLowColor(ViewColor());
3635 FillRect(eraseRect
, B_SOLID_LOW
);
3638 // update the scroll bars if the text area has changed
3639 if (newHeight
!= saveHeight
|| fMinTextRectWidth
!= saveWidth
)
3640 _UpdateScrollbars();
3643 ScrollToOffset(fSelEnd
);
3649 /*! Recalculate line breaks between two lines.
3651 \param startLine The line number to start recalculating line breaks.
3652 \param endLine The line number to stop recalculating line breaks.
3655 BTextView::_RecalculateLineBreaks(int32
* startLine
, int32
* endLine
)
3660 *startLine
= (*startLine
< 0) ? 0 : *startLine
;
3661 *endLine
= (*endLine
> fLines
->NumLines() - 1) ? fLines
->NumLines() - 1
3664 int32 textLength
= fText
->Length();
3665 int32 lineIndex
= (*startLine
> 0) ? *startLine
- 1 : 0;
3666 int32 recalThreshold
= (*fLines
)[*endLine
+ 1]->offset
;
3667 float width
= max_c(fTextRect
.Width(), 10);
3668 // TODO: The minimum width of 10 is a work around for the following
3669 // problem: If the text rect is too small, we are not calculating any
3670 // line heights, not even for the first line. Maybe this is a bug
3671 // in the algorithm, but other places in the class rely on at least
3672 // the first line to return a valid height. Maybe "10" should really
3673 // be the width of the very first glyph instead.
3674 STELine
* curLine
= (*fLines
)[lineIndex
];
3675 STELine
* nextLine
= curLine
+ 1;
3678 float ascent
, descent
;
3679 int32 fromOffset
= curLine
->offset
;
3680 int32 toOffset
= _FindLineBreak(fromOffset
, &ascent
, &descent
, &width
);
3682 curLine
->ascent
= ascent
;
3683 curLine
->width
= width
;
3685 // we want to advance at least by one character
3686 int32 nextOffset
= _NextInitialByte(fromOffset
);
3687 if (toOffset
< nextOffset
&& fromOffset
< textLength
)
3688 toOffset
= nextOffset
;
3691 STELine saveLine
= *nextLine
;
3692 if (lineIndex
> fLines
->NumLines() || toOffset
< nextLine
->offset
) {
3693 // the new line comes before the old line start, add a line
3695 newLine
.offset
= toOffset
;
3696 newLine
.origin
= ceilf(curLine
->origin
+ ascent
+ descent
) + 1;
3698 fLines
->InsertLine(&newLine
, lineIndex
);
3700 // update the existing line
3701 nextLine
->offset
= toOffset
;
3702 nextLine
->origin
= ceilf(curLine
->origin
+ ascent
+ descent
) + 1;
3704 // remove any lines that start before the current line
3705 while (lineIndex
< fLines
->NumLines()
3706 && toOffset
>= ((*fLines
)[lineIndex
] + 1)->offset
) {
3707 fLines
->RemoveLines(lineIndex
+ 1);
3710 nextLine
= (*fLines
)[lineIndex
];
3711 if (nextLine
->offset
== saveLine
.offset
) {
3712 if (nextLine
->offset
>= recalThreshold
) {
3713 if (nextLine
->origin
!= saveLine
.origin
)
3714 fLines
->BumpOrigin(nextLine
->origin
- saveLine
.origin
,
3719 if (lineIndex
> 0 && lineIndex
== *startLine
)
3720 *startLine
= lineIndex
- 1;
3724 curLine
= (*fLines
)[lineIndex
];
3725 nextLine
= curLine
+ 1;
3726 } while (curLine
->offset
< textLength
);
3728 // make sure that the sentinel line (which starts at the end of the buffer)
3729 // has always a width of 0
3730 (*fLines
)[fLines
->NumLines()]->width
= 0;
3732 // update the text rect
3733 float newHeight
= TextHeight(0, fLines
->NumLines() - 1);
3734 fTextRect
.bottom
= fTextRect
.top
+ newHeight
;
3736 fMinTextRectWidth
= fLines
->MaxWidth();
3737 fTextRect
.right
= ceilf(fTextRect
.left
+ fMinTextRectWidth
);
3740 *endLine
= lineIndex
- 1;
3741 *startLine
= min_c(*startLine
, *endLine
);
3746 BTextView::_FindLineBreak(int32 fromOffset
, float* _ascent
, float* _descent
,
3752 const int32 limit
= fText
->Length();
3754 // is fromOffset at the end?
3755 if (fromOffset
>= limit
) {
3756 // try to return valid height info anyway
3757 if (fStyles
->NumRuns() > 0) {
3758 fStyles
->Iterate(fromOffset
, 1, fInline
, NULL
, NULL
, _ascent
,
3761 if (fStyles
->IsValidNullStyle()) {
3762 const BFont
* font
= NULL
;
3763 fStyles
->GetNullStyle(&font
, NULL
);
3766 font
->GetHeight(&fh
);
3767 *_ascent
= fh
.ascent
;
3768 *_descent
= fh
.descent
+ fh
.leading
;
3776 int32 offset
= fromOffset
;
3779 // Text wrapping is turned off.
3780 // Just find the offset of the first \n character
3781 offset
= limit
- fromOffset
;
3782 fText
->FindChar(B_ENTER
, fromOffset
, &offset
);
3783 offset
+= fromOffset
;
3784 int32 toOffset
= (offset
< limit
) ? offset
: limit
;
3786 *inOutWidth
= _TabExpandedStyledWidth(fromOffset
, toOffset
- fromOffset
,
3789 return offset
< limit
? offset
+ 1 : limit
;
3794 float descent
= 0.0;
3796 float deltaWidth
= 0.0;
3797 float strWidth
= 0.0;
3801 while (offset
< limit
&& !done
) {
3802 // find the next line break candidate
3803 for (; (offset
+ delta
) < limit
; delta
++) {
3804 if (CanEndLine(offset
+ delta
)) {
3805 theChar
= fText
->RealCharAt(offset
+ delta
);
3806 if (theChar
!= B_SPACE
&& theChar
!= B_TAB
3807 && theChar
!= B_ENTER
) {
3808 // we are scanning for trailing whitespace below, so we
3809 // have to skip non-whitespace characters, that can end
3817 int32 deltaBeforeWhitespace
= delta
;
3818 // now skip over trailing whitespace, if any
3819 for (; (offset
+ delta
) < limit
; delta
++) {
3820 theChar
= fText
->RealCharAt(offset
+ delta
);
3821 if (theChar
== B_ENTER
) {
3822 // found a newline, we're done!
3826 } else if (theChar
!= B_SPACE
&& theChar
!= B_TAB
) {
3827 // stop at anything else than trailing whitespace
3832 delta
= max_c(delta
, 1);
3834 // do not include B_ENTER-terminator into width & height calculations
3835 deltaWidth
= _TabExpandedStyledWidth(offset
,
3836 done
? delta
- 1 : delta
, &ascent
, &descent
);
3837 strWidth
+= deltaWidth
;
3839 if (strWidth
>= *inOutWidth
) {
3840 // we've found where the line will wrap
3843 // we have included trailing whitespace in the width computation
3844 // above, but that is not being shown anyway, so we try again
3845 // without the trailing whitespace
3846 if (delta
== deltaBeforeWhitespace
) {
3847 // there is no trailing whitespace, no point in trying
3851 // reset string width to start of current run ...
3852 strWidth
-= deltaWidth
;
3854 // ... and compute the resulting width (of visible characters)
3855 strWidth
+= _StyledWidth(offset
, deltaBeforeWhitespace
, NULL
, NULL
);
3856 if (strWidth
>= *inOutWidth
) {
3857 // width of visible characters exceeds line, we need to wrap
3858 // before the current "word"
3863 *_ascent
= max_c(ascent
, *_ascent
);
3864 *_descent
= max_c(descent
, *_descent
);
3870 if (offset
- fromOffset
< 1) {
3871 // there weren't any words that fit entirely in this line
3872 // force a break in the middle of a word
3877 int32 current
= fromOffset
;
3878 for (offset
= _NextInitialByte(current
); current
< limit
;
3879 current
= offset
, offset
= _NextInitialByte(offset
)) {
3880 strWidth
+= _StyledWidth(current
, offset
- current
, &ascent
,
3882 if (strWidth
>= *inOutWidth
) {
3883 offset
= _PreviousInitialByte(offset
);
3887 *_ascent
= max_c(ascent
, *_ascent
);
3888 *_descent
= max_c(descent
, *_descent
);
3892 return min_c(offset
, limit
);
3897 BTextView::_PreviousLineStart(int32 offset
)
3902 while (offset
> 0) {
3903 offset
= _PreviousInitialByte(offset
);
3904 if (_CharClassification(offset
) == CHAR_CLASS_WHITESPACE
3905 && ByteAt(offset
) == B_ENTER
) {
3915 BTextView::_NextLineEnd(int32 offset
)
3917 int32 textLen
= fText
->Length();
3918 if (offset
>= textLen
)
3921 while (offset
< textLen
) {
3922 if (_CharClassification(offset
) == CHAR_CLASS_WHITESPACE
3923 && ByteAt(offset
) == B_ENTER
) {
3926 offset
= _NextInitialByte(offset
);
3934 BTextView::_PreviousWordBoundary(int32 offset
)
3936 uint32 charType
= _CharClassification(offset
);
3938 while (offset
> 0) {
3939 previous
= _PreviousInitialByte(offset
);
3940 if (_CharClassification(previous
) != charType
)
3950 BTextView::_NextWordBoundary(int32 offset
)
3952 int32 textLen
= fText
->Length();
3953 uint32 charType
= _CharClassification(offset
);
3954 while (offset
< textLen
) {
3955 offset
= _NextInitialByte(offset
);
3956 if (_CharClassification(offset
) != charType
)
3965 BTextView::_PreviousWordStart(int32 offset
)
3971 // need to look at previous char
3972 if (_CharClassification(offset
) != CHAR_CLASS_DEFAULT
) {
3973 // skip non-word characters
3974 while (offset
> 0) {
3975 offset
= _PreviousInitialByte(offset
);
3976 if (_CharClassification(offset
) == CHAR_CLASS_DEFAULT
)
3980 while (offset
> 0) {
3981 // skip to start of word
3982 int32 previous
= _PreviousInitialByte(offset
);
3983 if (_CharClassification(previous
) != CHAR_CLASS_DEFAULT
)
3993 BTextView::_NextWordEnd(int32 offset
)
3995 int32 textLen
= fText
->Length();
3996 if (_CharClassification(offset
) != CHAR_CLASS_DEFAULT
) {
3997 // skip non-word characters
3998 while (offset
< textLen
) {
3999 offset
= _NextInitialByte(offset
);
4000 if (_CharClassification(offset
) == CHAR_CLASS_DEFAULT
)
4004 while (offset
< textLen
) {
4005 // skip to end of word
4006 offset
= _NextInitialByte(offset
);
4007 if (_CharClassification(offset
) != CHAR_CLASS_DEFAULT
)
4015 /*! Returns the width used by the characters starting at the given
4016 offset with the given length, expanding all tab characters as needed.
4019 BTextView::_TabExpandedStyledWidth(int32 offset
, int32 length
, float* _ascent
,
4020 float* _descent
) const
4023 float descent
= 0.0;
4024 float maxAscent
= 0.0;
4025 float maxDescent
= 0.0;
4028 int32 numBytes
= length
;
4029 bool foundTab
= false;
4031 foundTab
= fText
->FindChar(B_TAB
, offset
, &numBytes
);
4032 width
+= _StyledWidth(offset
, numBytes
, &ascent
, &descent
);
4034 if (maxAscent
< ascent
)
4036 if (maxDescent
< descent
)
4037 maxDescent
= descent
;
4040 width
+= _ActualTabWidth(width
);
4047 } while (foundTab
&& length
> 0);
4049 if (_ascent
!= NULL
)
4050 *_ascent
= maxAscent
;
4051 if (_descent
!= NULL
)
4052 *_descent
= maxDescent
;
4058 /*! Calculate the width of the text within the given limits.
4060 \param fromOffset The offset where to start.
4061 \param length The length of the text to examine.
4062 \param _ascent A pointer to a float which will contain the maximum ascent.
4063 \param _descent A pointer to a float which will contain the maximum descent.
4065 \return The width for the text within the given limits.
4068 BTextView::_StyledWidth(int32 fromOffset
, int32 length
, float* _ascent
,
4069 float* _descent
) const
4072 // determine height of char at given offset, but return empty width
4073 fStyles
->Iterate(fromOffset
, 1, fInline
, NULL
, NULL
, _ascent
,
4080 float descent
= 0.0;
4081 float maxAscent
= 0.0;
4082 float maxDescent
= 0.0;
4084 // iterate through the style runs
4085 const BFont
* font
= NULL
;
4087 while ((numBytes
= fStyles
->Iterate(fromOffset
, length
, fInline
, &font
,
4088 NULL
, &ascent
, &descent
)) != 0) {
4089 maxAscent
= max_c(ascent
, maxAscent
);
4090 maxDescent
= max_c(descent
, maxDescent
);
4093 // Use _BWidthBuffer_ if possible
4094 if (BPrivate::gWidthBuffer
!= NULL
) {
4095 result
+= BPrivate::gWidthBuffer
->StringWidth(*fText
, fromOffset
,
4099 const char* text
= fText
->GetString(fromOffset
, &numBytes
);
4100 result
+= font
->StringWidth(text
, numBytes
);
4106 fromOffset
+= numBytes
;
4110 if (_ascent
!= NULL
)
4111 *_ascent
= maxAscent
;
4112 if (_descent
!= NULL
)
4113 *_descent
= maxDescent
;
4119 //! Calculate the actual tab width for the given location.
4121 BTextView::_ActualTabWidth(float location
) const
4123 float tabWidth
= fTabWidth
- fmod(location
, fTabWidth
);
4124 if (round(tabWidth
) == 0)
4125 tabWidth
= fTabWidth
;
4132 BTextView::_DoInsertText(const char* text
, int32 length
, int32 offset
,
4133 const text_run_array
* runs
)
4135 _CancelInputMethod();
4137 if (TextLength() + length
> MaxBytes())
4140 if (fSelStart
!= fSelEnd
)
4141 Select(fSelStart
, fSelStart
);
4143 const int32 textLength
= TextLength();
4144 if (offset
> textLength
)
4145 offset
= textLength
;
4147 // copy data into buffer
4148 InsertText(text
, length
, offset
, runs
);
4150 // recalc line breaks and draw the text
4151 _Refresh(offset
, offset
+ length
, false);
4156 BTextView::_DoDeleteText(int32 fromOffset
, int32 toOffset
)
4163 BTextView::_DrawLine(BView
* view
, const int32
&lineNum
,
4164 const int32
&startOffset
, const bool &erase
, BRect
&eraseRect
,
4165 BRegion
&inputRegion
)
4167 STELine
* line
= (*fLines
)[lineNum
];
4168 float startLeft
= fTextRect
.left
;
4169 if (startOffset
!= -1) {
4170 if (ByteAt(startOffset
) == B_ENTER
) {
4171 // StartOffset is a newline
4172 startLeft
= PointAt(line
->offset
).x
;
4174 startLeft
= PointAt(startOffset
).x
;
4176 else if (fAlignment
!= B_ALIGN_LEFT
) {
4177 float alignmentOffset
= fTextRect
.Width() - LineWidth(lineNum
);
4178 if (fAlignment
== B_ALIGN_CENTER
)
4179 alignmentOffset
/= 2;
4180 startLeft
= fTextRect
.left
+ alignmentOffset
;
4183 int32 length
= (line
+ 1)->offset
;
4184 if (startOffset
!= -1)
4185 length
-= startOffset
;
4187 length
-= line
->offset
;
4189 // DrawString() chokes if you draw a newline
4190 if (ByteAt((line
+ 1)->offset
- 1) == B_ENTER
)
4193 view
->MovePenTo(startLeft
, line
->origin
+ line
->ascent
+ fTextRect
.top
+ 1);
4196 eraseRect
.top
= line
->origin
+ fTextRect
.top
;
4197 eraseRect
.bottom
= (line
+ 1)->origin
+ fTextRect
.top
;
4198 view
->FillRect(eraseRect
, B_SOLID_LOW
);
4201 // do we have any text to draw?
4205 bool foundTab
= false;
4208 int32 offset
= startOffset
!= -1 ? startOffset
: line
->offset
;
4209 const BFont
* font
= NULL
;
4210 const rgb_color
* color
= NULL
;
4212 drawing_mode defaultTextRenderingMode
= DrawingMode();
4213 // iterate through each style on this line
4214 while ((numBytes
= fStyles
->Iterate(offset
, length
, fInline
, &font
,
4216 view
->SetFont(font
);
4217 view
->SetHighColor(*color
);
4219 tabChars
= min_c(numBytes
, length
);
4221 foundTab
= fText
->FindChar(B_TAB
, offset
, &tabChars
);
4225 if (ByteAt(offset
+ tabChars
+ numTabs
) != B_TAB
)
4227 } while ((tabChars
+ numTabs
) < numBytes
);
4230 drawing_mode textRenderingMode
= defaultTextRenderingMode
;
4232 if (inputRegion
.CountRects() > 0
4233 && ((offset
<= fInline
->Offset()
4234 && fInline
->Offset() < offset
+ tabChars
)
4235 || (fInline
->Offset() <= offset
4236 && offset
< fInline
->Offset() + fInline
->Length()))) {
4238 textRenderingMode
= B_OP_OVER
;
4241 GetTextRegion(offset
, offset
+ length
, &textRegion
);
4243 textRegion
.IntersectWith(&inputRegion
);
4246 // Highlight in blue the inputted text
4247 view
->SetHighColor(kBlueInputColor
);
4248 view
->FillRect(textRegion
.Frame());
4250 // Highlight in red the selected part
4251 if (fInline
->SelectionLength() > 0) {
4252 BRegion selectedRegion
;
4253 GetTextRegion(fInline
->Offset()
4254 + fInline
->SelectionOffset(), fInline
->Offset()
4255 + fInline
->SelectionOffset()
4256 + fInline
->SelectionLength(), &selectedRegion
);
4258 textRegion
.IntersectWith(&selectedRegion
);
4260 view
->SetHighColor(kRedInputColor
);
4261 view
->FillRect(textRegion
.Frame());
4267 int32 returnedBytes
= tabChars
;
4268 const char* stringToDraw
= fText
->GetString(offset
, &returnedBytes
);
4269 view
->SetDrawingMode(textRenderingMode
);
4270 view
->DrawString(stringToDraw
, returnedBytes
);
4272 float penPos
= PenLocation().x
- fTextRect
.left
;
4273 float tabWidth
= _ActualTabWidth(penPos
);
4275 tabWidth
+= ((numTabs
- 1) * fTabWidth
);
4277 view
->MovePenBy(tabWidth
, 0.0);
4278 tabChars
+= numTabs
;
4283 numBytes
-= tabChars
;
4284 tabChars
= min_c(numBytes
, length
);
4286 } while (foundTab
&& tabChars
> 0);
4292 BTextView::_DrawLines(int32 startLine
, int32 endLine
, int32 startOffset
,
4299 BRect
textRect(fTextRect
);
4301 = Bounds().Width() - fLayoutData
->leftInset
- fLayoutData
->rightInset
;
4302 if (textRect
.Width() < minWidth
)
4303 textRect
.right
= textRect
.left
+ minWidth
;
4304 BRect clipRect
= Bounds() & textRect
;
4305 clipRect
.InsetBy(-1, -1);
4308 newClip
.Set(clipRect
);
4309 ConstrainClippingRegion(&newClip
);
4311 // set the low color to the view color so that
4312 // drawing to a non-white background will work
4313 SetLowColor(ViewColor());
4316 if (fOffscreen
== NULL
)
4320 view
= fOffscreen
->ChildAt(0);
4321 view
->SetLowColor(ViewColor());
4322 view
->FillRect(view
->Bounds(), B_SOLID_LOW
);
4325 long maxLine
= fLines
->NumLines() - 1;
4328 if (endLine
> maxLine
)
4331 // TODO: See if we can avoid this
4332 if (fAlignment
!= B_ALIGN_LEFT
)
4335 BRect eraseRect
= clipRect
;
4336 int32 startEraseLine
= startLine
;
4337 STELine
* line
= (*fLines
)[startLine
];
4339 if (erase
&& startOffset
!= -1 && fAlignment
== B_ALIGN_LEFT
) {
4340 // erase only to the right of startOffset
4342 int32 startErase
= startOffset
;
4344 BPoint erasePoint
= PointAt(startErase
);
4345 eraseRect
.left
= erasePoint
.x
;
4346 eraseRect
.top
= erasePoint
.y
;
4347 eraseRect
.bottom
= (line
+ 1)->origin
+ fTextRect
.top
;
4349 view
->FillRect(eraseRect
, B_SOLID_LOW
);
4351 eraseRect
= clipRect
;
4354 BRegion inputRegion
;
4355 if (fInline
!= NULL
&& fInline
->IsActive()) {
4356 GetTextRegion(fInline
->Offset(), fInline
->Offset() + fInline
->Length(),
4360 //BPoint leftTop(startLeft, line->origin);
4361 for (int32 lineNum
= startLine
; lineNum
<= endLine
; lineNum
++) {
4362 const bool eraseThisLine
= erase
&& lineNum
>= startEraseLine
;
4363 _DrawLine(view
, lineNum
, startOffset
, eraseThisLine
, eraseRect
,
4366 // Set this to -1 so the next iteration will use the line offset
4369 // draw the caret/hilite the selection
4371 if (fSelStart
!= fSelEnd
) {
4373 Highlight(fSelStart
, fSelEnd
);
4376 _DrawCaret(fSelStart
, true);
4380 if (fOffscreen
!= NULL
) {
4382 /*BPoint penLocation = view->PenLocation();
4383 BRect drawRect(leftTop.x, leftTop.y, penLocation.x, penLocation.y);
4384 DrawBitmap(fOffscreen, drawRect, drawRect);*/
4385 fOffscreen
->Unlock();
4388 ConstrainClippingRegion(NULL
);
4393 BTextView::_RequestDrawLines(int32 startLine
, int32 endLine
)
4398 long maxLine
= fLines
->NumLines() - 1;
4400 STELine
* from
= (*fLines
)[startLine
];
4401 STELine
* to
= endLine
== maxLine
? NULL
: (*fLines
)[endLine
+ 1];
4402 BRect
invalidRect(Bounds().left
, from
->origin
+ fTextRect
.top
,
4404 to
!= NULL
? to
->origin
+ fTextRect
.top
: fTextRect
.bottom
);
4405 Invalidate(invalidRect
);
4406 Window()->UpdateIfNeeded();
4411 BTextView::_DrawCaret(int32 offset
, bool visible
)
4414 BPoint caretPoint
= PointAt(offset
, &lineHeight
);
4415 caretPoint
.x
= min_c(caretPoint
.x
, fTextRect
.right
);
4418 caretRect
.left
= caretRect
.right
= caretPoint
.x
;
4419 caretRect
.top
= caretPoint
.y
;
4420 caretRect
.bottom
= caretPoint
.y
+ lineHeight
- 1;
4423 InvertRect(caretRect
);
4425 Invalidate(caretRect
);
4430 BTextView::_ShowCaret()
4432 if (fActive
&& !fCaretVisible
&& fEditable
&& fSelStart
== fSelEnd
)
4438 BTextView::_HideCaret()
4440 if (fCaretVisible
&& fSelStart
== fSelEnd
)
4445 //! Hides the caret if it is being shown, and if it's hidden, shows it.
4447 BTextView::_InvertCaret()
4449 fCaretVisible
= !fCaretVisible
;
4450 _DrawCaret(fSelStart
, fCaretVisible
);
4451 fCaretTime
= system_time();
4455 /*! Place the dragging caret at the given offset.
4457 \param offset The offset (zero based within the object's text) where to
4458 place the dragging caret. If it's -1, hide the caret.
4461 BTextView::_DragCaret(int32 offset
)
4463 // does the caret need to move?
4464 if (offset
== fDragOffset
)
4467 // hide the previous drag caret
4468 if (fDragOffset
!= -1)
4469 _DrawCaret(fDragOffset
, false);
4471 // do we have a new location?
4474 // ignore if offset is within active selection
4475 if (offset
>= fSelStart
&& offset
<= fSelEnd
) {
4481 _DrawCaret(offset
, true);
4484 fDragOffset
= offset
;
4489 BTextView::_StopMouseTracking()
4491 delete fTrackingMouse
;
4492 fTrackingMouse
= NULL
;
4497 BTextView::_PerformMouseUp(BPoint where
)
4499 if (fTrackingMouse
== NULL
)
4502 if (fTrackingMouse
->selectionRect
.IsValid())
4503 Select(fTrackingMouse
->clickOffset
, fTrackingMouse
->clickOffset
);
4505 _StopMouseTracking();
4506 // adjust cursor if necessary
4507 _TrackMouse(where
, NULL
, true);
4514 BTextView::_PerformMouseMoved(BPoint where
, uint32 code
)
4518 if (fTrackingMouse
== NULL
)
4521 int32 currentOffset
= OffsetAt(where
);
4522 if (fTrackingMouse
->selectionRect
.IsValid()) {
4523 // we are tracking the mouse for drag action, if the mouse has moved
4524 // to another index or more than three pixels from where it was clicked,
4525 // we initiate a drag now:
4526 if (currentOffset
!= fTrackingMouse
->clickOffset
4527 || fabs(fTrackingMouse
->where
.x
- where
.x
) > 3
4528 || fabs(fTrackingMouse
->where
.y
- where
.y
) > 3) {
4529 _StopMouseTracking();
4536 switch (fClickCount
) {
4538 // triple click, extend selection linewise
4539 if (currentOffset
<= fTrackingMouse
->anchor
) {
4540 fTrackingMouse
->selStart
4541 = (*fLines
)[_LineAt(currentOffset
)]->offset
;
4542 fTrackingMouse
->selEnd
= fTrackingMouse
->shiftDown
4544 : (*fLines
)[_LineAt(fTrackingMouse
->anchor
) + 1]->offset
;
4546 fTrackingMouse
->selStart
4547 = fTrackingMouse
->shiftDown
4549 : (*fLines
)[_LineAt(fTrackingMouse
->anchor
)]->offset
;
4550 fTrackingMouse
->selEnd
4551 = (*fLines
)[_LineAt(currentOffset
) + 1]->offset
;
4556 // double click, extend selection wordwise
4557 if (currentOffset
<= fTrackingMouse
->anchor
) {
4558 fTrackingMouse
->selStart
= _PreviousWordBoundary(currentOffset
);
4559 fTrackingMouse
->selEnd
4560 = fTrackingMouse
->shiftDown
4562 : _NextWordBoundary(fTrackingMouse
->anchor
);
4564 fTrackingMouse
->selStart
4565 = fTrackingMouse
->shiftDown
4567 : _PreviousWordBoundary(fTrackingMouse
->anchor
);
4568 fTrackingMouse
->selEnd
= _NextWordBoundary(currentOffset
);
4573 // new click, extend selection char by char
4574 if (currentOffset
<= fTrackingMouse
->anchor
) {
4575 fTrackingMouse
->selStart
= currentOffset
;
4576 fTrackingMouse
->selEnd
4577 = fTrackingMouse
->shiftDown
4578 ? fSelEnd
: fTrackingMouse
->anchor
;
4580 fTrackingMouse
->selStart
4581 = fTrackingMouse
->shiftDown
4582 ? fSelStart
: fTrackingMouse
->anchor
;
4583 fTrackingMouse
->selEnd
= currentOffset
;
4588 // position caret to follow the direction of the selection
4589 if (fTrackingMouse
->selEnd
!= fSelEnd
)
4590 fCaretOffset
= fTrackingMouse
->selEnd
;
4591 else if (fTrackingMouse
->selStart
!= fSelStart
)
4592 fCaretOffset
= fTrackingMouse
->selStart
;
4594 Select(fTrackingMouse
->selStart
, fTrackingMouse
->selEnd
);
4595 _TrackMouse(where
, NULL
);
4601 /*! Tracks the mouse position, doing special actions like changing the
4604 \param where The point where the mouse has moved.
4605 \param message The dragging message, if there is any.
4606 \param force Passed as second parameter of SetViewCursor()
4609 BTextView::_TrackMouse(BPoint where
, const BMessage
* message
, bool force
)
4612 GetTextRegion(fSelStart
, fSelEnd
, &textRegion
);
4614 if (message
&& AcceptsDrop(message
))
4616 else if ((fSelectable
|| fEditable
)
4617 && (fTrackingMouse
!= NULL
|| !textRegion
.Contains(where
))) {
4618 SetViewCursor(B_CURSOR_I_BEAM
, force
);
4620 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT
, force
);
4624 //! Tracks the mouse position when the user is dragging some data.
4626 BTextView::_TrackDrag(BPoint where
)
4629 if (Bounds().Contains(where
))
4630 _DragCaret(OffsetAt(where
));
4634 //! Initiates a drag operation.
4636 BTextView::_InitiateDrag()
4638 BMessage
dragMessage(B_MIME_DATA
);
4639 BBitmap
* dragBitmap
= NULL
;
4641 BHandler
* dragHandler
= NULL
;
4643 GetDragParameters(&dragMessage
, &dragBitmap
, &bitmapPoint
, &dragHandler
);
4644 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT
);
4646 if (dragBitmap
!= NULL
)
4647 DragMessage(&dragMessage
, dragBitmap
, bitmapPoint
, dragHandler
);
4650 GetTextRegion(fSelStart
, fSelEnd
, ®ion
);
4651 BRect bounds
= Bounds();
4652 BRect dragRect
= region
.Frame();
4653 if (!bounds
.Contains(dragRect
))
4654 dragRect
= bounds
& dragRect
;
4656 DragMessage(&dragMessage
, dragRect
, dragHandler
);
4659 BMessenger
messenger(this);
4660 BMessage
message(_DISPOSE_DRAG_
);
4661 fDragRunner
= new (nothrow
) BMessageRunner(messenger
, &message
, 100000);
4665 //! Handles when some data is dropped on the view.
4667 BTextView::_MessageDropped(BMessage
* message
, BPoint where
, BPoint offset
)
4672 bool internalDrop
= false;
4673 if (message
->FindPointer("be:originator", &from
) == B_OK
4674 && from
== this && fSelEnd
!= fSelStart
)
4675 internalDrop
= true;
4682 _TrackMouse(where
, NULL
);
4684 // are we sure we like this message?
4685 if (!AcceptsDrop(message
))
4688 int32 dropOffset
= OffsetAt(where
);
4689 if (dropOffset
> TextLength())
4690 dropOffset
= TextLength();
4692 // if this view initiated the drag, move instead of copy
4694 // dropping onto itself?
4695 if (dropOffset
>= fSelStart
&& dropOffset
<= fSelEnd
)
4699 ssize_t dataLength
= 0;
4700 const char* text
= NULL
;
4702 if (message
->FindData("text/plain", B_MIME_TYPE
, (const void**)&text
,
4703 &dataLength
) == B_OK
) {
4704 text_run_array
* runArray
= NULL
;
4705 ssize_t runLength
= 0;
4707 message
->FindData("application/x-vnd.Be-text_run_array",
4708 B_MIME_TYPE
, (const void**)&runArray
, &runLength
);
4711 _FilterDisallowedChars((char*)text
, dataLength
, runArray
);
4713 if (dataLength
< 1) {
4720 fUndo
= new DropUndoBuffer(this, text
, dataLength
, runArray
,
4721 runLength
, dropOffset
, internalDrop
);
4725 if (dropOffset
> fSelEnd
)
4726 dropOffset
-= dataLength
;
4730 Insert(dropOffset
, text
, dataLength
, runArray
);
4738 BTextView::_PerformAutoScrolling()
4740 // Scroll the view a bit if mouse is outside the view bounds
4741 BRect bounds
= Bounds();
4742 BPoint
scrollBy(B_ORIGIN
);
4744 // R5 does a pretty soft auto-scroll, we try to do the same by
4745 // simply scrolling the distance between cursor and border
4746 if (fWhere
.x
> bounds
.right
) {
4747 scrollBy
.x
= fWhere
.x
- bounds
.right
;
4748 } else if (fWhere
.x
< bounds
.left
) {
4749 scrollBy
.x
= fWhere
.x
- bounds
.left
; // negative value
4752 // prevent from scrolling out of view
4753 if (scrollBy
.x
!= 0.0) {
4754 float rightMax
= floorf(fTextRect
.right
+ fLayoutData
->rightInset
);
4755 if (bounds
.right
+ scrollBy
.x
> rightMax
)
4756 scrollBy
.x
= rightMax
- bounds
.right
;
4757 if (bounds
.left
+ scrollBy
.x
< 0)
4758 scrollBy
.x
= -bounds
.left
;
4761 if (CountLines() > 1) {
4762 // scroll in Y only if multiple lines!
4763 if (fWhere
.y
> bounds
.bottom
) {
4764 scrollBy
.y
= fWhere
.y
- bounds
.bottom
;
4765 } else if (fWhere
.y
< bounds
.top
) {
4766 scrollBy
.y
= fWhere
.y
- bounds
.top
; // negative value
4769 // prevent from scrolling out of view
4770 if (scrollBy
.y
!= 0.0) {
4771 float bottomMax
= floorf(fTextRect
.bottom
4772 + fLayoutData
->bottomInset
);
4773 if (bounds
.bottom
+ scrollBy
.y
> bottomMax
)
4774 scrollBy
.y
= bottomMax
- bounds
.bottom
;
4775 if (bounds
.top
+ scrollBy
.y
< 0)
4776 scrollBy
.y
= -bounds
.top
;
4780 if (scrollBy
!= B_ORIGIN
)
4781 ScrollBy(scrollBy
.x
, scrollBy
.y
);
4785 //! Updates the scrollbars associated with the object (if any).
4787 BTextView::_UpdateScrollbars()
4789 BRect
bounds(Bounds());
4790 BScrollBar
* horizontalScrollBar
= ScrollBar(B_HORIZONTAL
);
4791 BScrollBar
* verticalScrollBar
= ScrollBar(B_VERTICAL
);
4793 // do we have a horizontal scroll bar?
4794 if (horizontalScrollBar
!= NULL
) {
4795 long viewWidth
= bounds
.IntegerWidth();
4796 long dataWidth
= (long)ceilf(fTextRect
.IntegerWidth()
4797 + fLayoutData
->leftInset
+ fLayoutData
->rightInset
);
4799 long maxRange
= dataWidth
- viewWidth
;
4800 maxRange
= max_c(maxRange
, 0);
4802 horizontalScrollBar
->SetRange(0, (float)maxRange
);
4803 horizontalScrollBar
->SetProportion((float)viewWidth
/ (float)dataWidth
);
4804 horizontalScrollBar
->SetSteps(kHorizontalScrollBarStep
, dataWidth
/ 10);
4807 // how about a vertical scroll bar?
4808 if (verticalScrollBar
!= NULL
) {
4809 long viewHeight
= bounds
.IntegerHeight();
4810 long dataHeight
= (long)ceilf(fTextRect
.IntegerHeight()
4811 + fLayoutData
->topInset
+ fLayoutData
->bottomInset
);
4813 long maxRange
= dataHeight
- viewHeight
;
4814 maxRange
= max_c(maxRange
, 0);
4816 verticalScrollBar
->SetRange(0, maxRange
);
4817 verticalScrollBar
->SetProportion((float)viewHeight
/ (float)dataHeight
);
4818 verticalScrollBar
->SetSteps(kVerticalScrollBarStep
, viewHeight
);
4823 //! Scrolls by the given offsets
4825 BTextView::_ScrollBy(float horizontal
, float vertical
)
4827 BRect bounds
= Bounds();
4828 _ScrollTo(bounds
.left
+ horizontal
, bounds
.top
+ vertical
);
4832 //! Scrolls to the given position, making sure not to scroll out of bounds.
4834 BTextView::_ScrollTo(float x
, float y
)
4836 BRect bounds
= Bounds();
4837 long viewWidth
= bounds
.IntegerWidth();
4838 long viewHeight
= bounds
.IntegerHeight();
4840 if (x
> fTextRect
.right
- viewWidth
)
4841 x
= fTextRect
.right
- viewWidth
;
4845 if (y
> fTextRect
.bottom
+ fLayoutData
->bottomInset
- viewHeight
)
4846 y
= fTextRect
.bottom
+ fLayoutData
->bottomInset
- viewHeight
;
4854 //! Autoresizes the view to fit the contained text.
4856 BTextView::_AutoResize(bool redraw
)
4861 BRect bounds
= Bounds();
4862 float oldWidth
= bounds
.Width();
4863 float newWidth
= ceilf(fLayoutData
->leftInset
+ fTextRect
.Width()
4864 + fLayoutData
->rightInset
);
4866 if (fContainerView
!= NULL
) {
4867 // NOTE: This container view thing is only used by Tracker.
4868 // move container view if not left aligned
4869 if (fAlignment
== B_ALIGN_CENTER
) {
4870 if (fmod(ceilf(newWidth
- oldWidth
), 2.0) != 0.0)
4872 fContainerView
->MoveBy(ceilf(oldWidth
- newWidth
) / 2, 0);
4873 } else if (fAlignment
== B_ALIGN_RIGHT
) {
4874 fContainerView
->MoveBy(ceilf(oldWidth
- newWidth
), 0);
4876 // resize container view
4877 fContainerView
->ResizeBy(ceilf(newWidth
- oldWidth
), 0);
4882 _RequestDrawLines(0, 0);
4884 // erase any potential left over outside the text rect
4885 // (can only be on right hand side)
4886 BRect
dirty(fTextRect
.right
+ 1, fTextRect
.top
, bounds
.right
,
4888 if (dirty
.IsValid()) {
4889 SetLowColor(ViewColor());
4890 FillRect(dirty
, B_SOLID_LOW
);
4895 //! Creates a new offscreen BBitmap with an associated BView.
4897 BTextView::_NewOffscreen(float padding
)
4899 if (fOffscreen
!= NULL
)
4902 #if USE_DOUBLEBUFFERING
4903 BRect
bitmapRect(0, 0, fTextRect
.Width() + padding
, fTextRect
.Height());
4904 fOffscreen
= new BBitmap(bitmapRect
, fColorSpace
, true, false);
4905 if (fOffscreen
!= NULL
&& fOffscreen
->Lock()) {
4906 BView
* bufferView
= new BView(bitmapRect
, "drawing view", 0, 0);
4907 fOffscreen
->AddChild(bufferView
);
4908 fOffscreen
->Unlock();
4914 //! Deletes the textview's offscreen bitmap, if any.
4916 BTextView::_DeleteOffscreen()
4918 if (fOffscreen
!= NULL
&& fOffscreen
->Lock()) {
4925 /*! Creates a new offscreen bitmap, highlight the selection, and set the
4926 cursor to \c B_CURSOR_I_BEAM.
4929 BTextView::_Activate()
4933 // Create a new offscreen BBitmap
4936 if (fSelStart
!= fSelEnd
) {
4938 Highlight(fSelStart
, fSelEnd
);
4944 GetMouse(&where
, &buttons
, false);
4945 if (Bounds().Contains(where
))
4946 _TrackMouse(where
, NULL
);
4948 if (Window() != NULL
) {
4951 if (!Window()->HasShortcut(B_LEFT_ARROW
, B_COMMAND_KEY
)
4952 && !Window()->HasShortcut(B_RIGHT_ARROW
, B_COMMAND_KEY
)) {
4953 message
= new BMessage(kMsgNavigateArrow
);
4954 message
->AddInt32("key", B_LEFT_ARROW
);
4955 message
->AddInt32("modifiers", B_COMMAND_KEY
);
4956 Window()->AddShortcut(B_LEFT_ARROW
, B_COMMAND_KEY
, message
, this);
4958 message
= new BMessage(kMsgNavigateArrow
);
4959 message
->AddInt32("key", B_RIGHT_ARROW
);
4960 message
->AddInt32("modifiers", B_COMMAND_KEY
);
4961 Window()->AddShortcut(B_RIGHT_ARROW
, B_COMMAND_KEY
, message
, this);
4963 fInstalledNavigateCommandWordwiseShortcuts
= true;
4965 if (!Window()->HasShortcut(B_LEFT_ARROW
, B_COMMAND_KEY
| B_SHIFT_KEY
)
4966 && !Window()->HasShortcut(B_RIGHT_ARROW
,
4967 B_COMMAND_KEY
| B_SHIFT_KEY
)) {
4968 message
= new BMessage(kMsgNavigateArrow
);
4969 message
->AddInt32("key", B_LEFT_ARROW
);
4970 message
->AddInt32("modifiers", B_COMMAND_KEY
| B_SHIFT_KEY
);
4971 Window()->AddShortcut(B_LEFT_ARROW
, B_COMMAND_KEY
| B_SHIFT_KEY
,
4974 message
= new BMessage(kMsgNavigateArrow
);
4975 message
->AddInt32("key", B_RIGHT_ARROW
);
4976 message
->AddInt32("modifiers", B_COMMAND_KEY
| B_SHIFT_KEY
);
4977 Window()->AddShortcut(B_RIGHT_ARROW
, B_COMMAND_KEY
| B_SHIFT_KEY
,
4980 fInstalledSelectCommandWordwiseShortcuts
= true;
4983 if (!Window()->HasShortcut(B_LEFT_ARROW
, B_OPTION_KEY
)
4984 && !Window()->HasShortcut(B_RIGHT_ARROW
, B_OPTION_KEY
)) {
4985 message
= new BMessage(kMsgNavigateArrow
);
4986 message
->AddInt32("key", B_LEFT_ARROW
);
4987 message
->AddInt32("modifiers", B_OPTION_KEY
);
4988 Window()->AddShortcut(B_LEFT_ARROW
, B_OPTION_KEY
, message
, this);
4990 message
= new BMessage(kMsgNavigateArrow
);
4991 message
->AddInt32("key", B_RIGHT_ARROW
);
4992 message
->AddInt32("modifiers", B_OPTION_KEY
);
4993 Window()->AddShortcut(B_RIGHT_ARROW
, B_OPTION_KEY
, message
, this);
4995 fInstalledNavigateOptionWordwiseShortcuts
= true;
4997 if (!Window()->HasShortcut(B_LEFT_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
)
4998 && !Window()->HasShortcut(B_RIGHT_ARROW
,
4999 B_OPTION_KEY
| B_SHIFT_KEY
)) {
5000 message
= new BMessage(kMsgNavigateArrow
);
5001 message
->AddInt32("key", B_LEFT_ARROW
);
5002 message
->AddInt32("modifiers", B_OPTION_KEY
| B_SHIFT_KEY
);
5003 Window()->AddShortcut(B_LEFT_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
,
5006 message
= new BMessage(kMsgNavigateArrow
);
5007 message
->AddInt32("key", B_RIGHT_ARROW
);
5008 message
->AddInt32("modifiers", B_OPTION_KEY
| B_SHIFT_KEY
);
5009 Window()->AddShortcut(B_RIGHT_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
,
5012 fInstalledSelectOptionWordwiseShortcuts
= true;
5015 if (!Window()->HasShortcut(B_UP_ARROW
, B_OPTION_KEY
)
5016 && !Window()->HasShortcut(B_DOWN_ARROW
, B_OPTION_KEY
)) {
5017 message
= new BMessage(kMsgNavigateArrow
);
5018 message
->AddInt32("key", B_UP_ARROW
);
5019 message
->AddInt32("modifiers", B_OPTION_KEY
);
5020 Window()->AddShortcut(B_UP_ARROW
, B_OPTION_KEY
, message
, this);
5022 message
= new BMessage(kMsgNavigateArrow
);
5023 message
->AddInt32("key", B_DOWN_ARROW
);
5024 message
->AddInt32("modifiers", B_OPTION_KEY
);
5025 Window()->AddShortcut(B_DOWN_ARROW
, B_OPTION_KEY
, message
, this);
5027 fInstalledNavigateOptionLinewiseShortcuts
= true;
5029 if (!Window()->HasShortcut(B_UP_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
)
5030 && !Window()->HasShortcut(B_DOWN_ARROW
,
5031 B_OPTION_KEY
| B_SHIFT_KEY
)) {
5032 message
= new BMessage(kMsgNavigateArrow
);
5033 message
->AddInt32("key", B_UP_ARROW
);
5034 message
->AddInt32("modifiers", B_OPTION_KEY
| B_SHIFT_KEY
);
5035 Window()->AddShortcut(B_UP_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
,
5038 message
= new BMessage(kMsgNavigateArrow
);
5039 message
->AddInt32("key", B_DOWN_ARROW
);
5040 message
->AddInt32("modifiers", B_OPTION_KEY
| B_SHIFT_KEY
);
5041 Window()->AddShortcut(B_DOWN_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
,
5044 fInstalledSelectOptionLinewiseShortcuts
= true;
5047 if (!Window()->HasShortcut(B_HOME
, B_COMMAND_KEY
)
5048 && !Window()->HasShortcut(B_END
, B_COMMAND_KEY
)) {
5049 message
= new BMessage(kMsgNavigatePage
);
5050 message
->AddInt32("key", B_HOME
);
5051 message
->AddInt32("modifiers", B_COMMAND_KEY
);
5052 Window()->AddShortcut(B_HOME
, B_COMMAND_KEY
, message
, this);
5054 message
= new BMessage(kMsgNavigatePage
);
5055 message
->AddInt32("key", B_END
);
5056 message
->AddInt32("modifiers", B_COMMAND_KEY
);
5057 Window()->AddShortcut(B_END
, B_COMMAND_KEY
, message
, this);
5059 fInstalledNavigateHomeEndDocwiseShortcuts
= true;
5061 if (!Window()->HasShortcut(B_HOME
, B_COMMAND_KEY
| B_SHIFT_KEY
)
5062 && !Window()->HasShortcut(B_END
, B_COMMAND_KEY
| B_SHIFT_KEY
)) {
5063 message
= new BMessage(kMsgNavigatePage
);
5064 message
->AddInt32("key", B_HOME
);
5065 message
->AddInt32("modifiers", B_COMMAND_KEY
| B_SHIFT_KEY
);
5066 Window()->AddShortcut(B_HOME
, B_COMMAND_KEY
| B_SHIFT_KEY
,
5069 message
= new BMessage(kMsgNavigatePage
);
5070 message
->AddInt32("key", B_END
);
5071 message
->AddInt32("modifiers", B_COMMAND_KEY
| B_SHIFT_KEY
);
5072 Window()->AddShortcut(B_END
, B_COMMAND_KEY
| B_SHIFT_KEY
,
5075 fInstalledSelectHomeEndDocwiseShortcuts
= true;
5081 //! Unhilights the selection, set the cursor to \c B_CURSOR_SYSTEM_DEFAULT.
5083 BTextView::_Deactivate()
5087 _CancelInputMethod();
5090 if (fSelStart
!= fSelEnd
) {
5092 Highlight(fSelStart
, fSelEnd
);
5096 if (Window() != NULL
) {
5097 if (fInstalledNavigateCommandWordwiseShortcuts
) {
5098 Window()->RemoveShortcut(B_LEFT_ARROW
, B_COMMAND_KEY
);
5099 Window()->RemoveShortcut(B_RIGHT_ARROW
, B_COMMAND_KEY
);
5100 fInstalledNavigateCommandWordwiseShortcuts
= false;
5102 if (fInstalledSelectCommandWordwiseShortcuts
) {
5103 Window()->RemoveShortcut(B_LEFT_ARROW
, B_COMMAND_KEY
| B_SHIFT_KEY
);
5104 Window()->RemoveShortcut(B_RIGHT_ARROW
,
5105 B_COMMAND_KEY
| B_SHIFT_KEY
);
5106 fInstalledSelectCommandWordwiseShortcuts
= false;
5109 if (fInstalledNavigateOptionWordwiseShortcuts
) {
5110 Window()->RemoveShortcut(B_LEFT_ARROW
, B_OPTION_KEY
);
5111 Window()->RemoveShortcut(B_RIGHT_ARROW
, B_OPTION_KEY
);
5112 fInstalledNavigateOptionWordwiseShortcuts
= false;
5114 if (fInstalledSelectOptionWordwiseShortcuts
) {
5115 Window()->RemoveShortcut(B_LEFT_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
);
5116 Window()->RemoveShortcut(B_RIGHT_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
);
5117 fInstalledSelectOptionWordwiseShortcuts
= false;
5120 if (fInstalledNavigateOptionLinewiseShortcuts
) {
5121 Window()->RemoveShortcut(B_UP_ARROW
, B_OPTION_KEY
);
5122 Window()->RemoveShortcut(B_DOWN_ARROW
, B_OPTION_KEY
);
5123 fInstalledNavigateOptionLinewiseShortcuts
= false;
5125 if (fInstalledSelectOptionLinewiseShortcuts
) {
5126 Window()->RemoveShortcut(B_UP_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
);
5127 Window()->RemoveShortcut(B_DOWN_ARROW
, B_OPTION_KEY
| B_SHIFT_KEY
);
5128 fInstalledSelectOptionLinewiseShortcuts
= false;
5131 if (fInstalledNavigateHomeEndDocwiseShortcuts
) {
5132 Window()->RemoveShortcut(B_HOME
, B_COMMAND_KEY
);
5133 Window()->RemoveShortcut(B_END
, B_COMMAND_KEY
);
5134 fInstalledNavigateHomeEndDocwiseShortcuts
= false;
5136 if (fInstalledSelectHomeEndDocwiseShortcuts
) {
5137 Window()->RemoveShortcut(B_HOME
, B_COMMAND_KEY
| B_SHIFT_KEY
);
5138 Window()->RemoveShortcut(B_END
, B_COMMAND_KEY
| B_SHIFT_KEY
);
5139 fInstalledSelectHomeEndDocwiseShortcuts
= false;
5145 /*! Changes the passed in font to be displayable by the object.
5147 Set font rotation to 0, removes any font flag, set font spacing
5148 to \c B_BITMAP_SPACING and font encoding to \c B_UNICODE_UTF8.
5151 BTextView::_NormalizeFont(BFont
* font
)
5154 font
->SetRotation(0.0f
);
5156 font
->SetSpacing(B_BITMAP_SPACING
);
5157 font
->SetEncoding(B_UNICODE_UTF8
);
5163 BTextView::_SetRunArray(int32 startOffset
, int32 endOffset
,
5164 const text_run_array
* runs
)
5166 const int32 numStyles
= runs
->count
;
5167 if (numStyles
> 0) {
5168 const text_run
* theRun
= &runs
->runs
[0];
5169 for (int32 index
= 0; index
< numStyles
; index
++) {
5170 int32 fromOffset
= theRun
->offset
+ startOffset
;
5171 int32 toOffset
= endOffset
;
5172 if (index
+ 1 < numStyles
) {
5173 toOffset
= (theRun
+ 1)->offset
+ startOffset
;
5174 toOffset
= (toOffset
> endOffset
) ? endOffset
: toOffset
;
5177 _ApplyStyleRange(fromOffset
, toOffset
, B_FONT_ALL
, &theRun
->font
,
5178 &theRun
->color
, false);
5182 fStyles
->InvalidateNullStyle();
5187 /*! Returns the character class of the character at the given offset.
5189 \param offset The offset where the wanted character can be found.
5191 \return A value which represents the character's classification.
5194 BTextView::_CharClassification(int32 offset
) const
5196 // TODO: Should check against a list of characters containing also
5197 // japanese word breakers.
5198 // And what about other languages ? Isn't there a better way to check
5199 // for separator characters ?
5200 // Andrew suggested to have a look at UnicodeBlockObject.h
5201 switch (fText
->RealCharAt(offset
)) {
5203 return CHAR_CLASS_END_OF_TEXT
;
5208 return CHAR_CLASS_WHITESPACE
;
5225 return CHAR_CLASS_GRAPHICAL
;
5229 return CHAR_CLASS_QUOTE
;
5238 return CHAR_CLASS_PUNCTUATION
;
5243 return CHAR_CLASS_PARENS_OPEN
;
5248 return CHAR_CLASS_PARENS_CLOSE
;
5251 return CHAR_CLASS_DEFAULT
;
5256 /*! Returns the offset of the next UTF-8 character.
5258 \param offset The offset where to start looking.
5260 \return The offset of the next UTF-8 character.
5263 BTextView::_NextInitialByte(int32 offset
) const
5265 if (offset
>= fText
->Length())
5268 for (++offset
; (ByteAt(offset
) & 0xC0) == 0x80; ++offset
)
5275 /*! Returns the offset of the previous UTF-8 character.
5277 \param offset The offset where to start looking.
5279 \return The offset of the previous UTF-8 character.
5282 BTextView::_PreviousInitialByte(int32 offset
) const
5289 for (--offset
; offset
> 0 && count
; --offset
, --count
) {
5290 if ((ByteAt(offset
) & 0xC0) != 0x80)
5294 return count
? offset
: 0;
5299 BTextView::_GetProperty(BMessage
* specifier
, int32 form
, const char* property
,
5303 if (strcmp(property
, "selection") == 0) {
5304 reply
->what
= B_REPLY
;
5305 reply
->AddInt32("result", fSelStart
);
5306 reply
->AddInt32("result", fSelEnd
);
5307 reply
->AddInt32("error", B_OK
);
5310 } else if (strcmp(property
, "Text") == 0) {
5311 if (IsTypingHidden()) {
5312 // Do not allow stealing passwords via scripting
5318 specifier
->FindInt32("index", &index
);
5319 specifier
->FindInt32("range", &range
);
5321 char* buffer
= new char[range
+ 1];
5322 GetText(index
, range
, buffer
);
5324 reply
->what
= B_REPLY
;
5325 reply
->AddString("result", buffer
);
5326 reply
->AddInt32("error", B_OK
);
5331 } else if (strcmp(property
, "text_run_array") == 0)
5339 BTextView::_SetProperty(BMessage
* specifier
, int32 form
, const char* property
,
5343 if (strcmp(property
, "selection") == 0) {
5346 specifier
->FindInt32("index", &index
);
5347 specifier
->FindInt32("range", &range
);
5349 Select(index
, index
+ range
);
5351 reply
->what
= B_REPLY
;
5352 reply
->AddInt32("error", B_OK
);
5355 } else if (strcmp(property
, "Text") == 0) {
5357 specifier
->FindInt32("index", &index
);
5358 specifier
->FindInt32("range", &range
);
5360 const char* buffer
= NULL
;
5361 if (specifier
->FindString("data", &buffer
) == B_OK
)
5362 InsertText(buffer
, range
, index
, NULL
);
5364 DeleteText(index
, range
);
5366 reply
->what
= B_REPLY
;
5367 reply
->AddInt32("error", B_OK
);
5370 } else if (strcmp(property
, "text_run_array") == 0)
5378 BTextView::_CountProperties(BMessage
* specifier
, int32 form
,
5379 const char* property
, BMessage
* reply
)
5382 if (strcmp(property
, "Text") == 0) {
5383 reply
->what
= B_REPLY
;
5384 reply
->AddInt32("result", TextLength());
5385 reply
->AddInt32("error", B_OK
);
5393 //! Called when the object receives a \c B_INPUT_METHOD_CHANGED message.
5395 BTextView::_HandleInputMethodChanged(BMessage
* message
)
5397 // TODO: block input if not editable (Andrew)
5398 ASSERT(fInline
!= NULL
);
5400 const char* string
= NULL
;
5401 if (message
->FindString("be:string", &string
) < B_OK
|| string
== NULL
)
5407 be_app
->ObscureCursor();
5409 // If we find the "be:confirmed" boolean (and the boolean is true),
5410 // it means it's over for now, so the current InlineInput object
5411 // should become inactive. We will probably receive a
5412 // B_INPUT_METHOD_STOPPED message after this one.
5414 if (message
->FindBool("be:confirmed", &confirmed
) != B_OK
)
5417 // Delete the previously inserted text (if any)
5418 if (fInline
->IsActive()) {
5419 const int32 oldOffset
= fInline
->Offset();
5420 DeleteText(oldOffset
, oldOffset
+ fInline
->Length());
5422 fInline
->SetActive(false);
5423 fCaretOffset
= fSelStart
= fSelEnd
= oldOffset
;
5426 const int32 stringLen
= strlen(string
);
5428 fInline
->SetOffset(fSelStart
);
5429 fInline
->SetLength(stringLen
);
5430 fInline
->ResetClauses();
5432 if (!confirmed
&& !fInline
->IsActive())
5433 fInline
->SetActive(true);
5435 // Get the clauses, and pass them to the InlineInput object
5436 // TODO: Find out if what we did it's ok, currently we don't consider
5437 // clauses at all, while the bebook says we should; though the visual
5438 // effect we obtained seems correct. Weird.
5439 int32 clauseCount
= 0;
5442 while (message
->FindInt32("be:clause_start", clauseCount
, &clauseStart
)
5444 && message
->FindInt32("be:clause_end", clauseCount
, &clauseEnd
)
5446 if (!fInline
->AddClause(clauseStart
, clauseEnd
))
5452 _Refresh(fSelStart
, fSelEnd
, true);
5455 // now we need to feed ourselves the individual characters as if the
5456 // user would have pressed them now - this lets KeyDown() pick out all
5457 // the special characters like B_BACKSPACE, cursor keys and the like:
5458 const char* currPos
= string
;
5459 const char* prevPos
= currPos
;
5460 while (*currPos
!= '\0') {
5461 if ((*currPos
& 0xC0) == 0xC0) {
5462 // found the start of an UTF-8 char, we collect while it lasts
5464 while ((*currPos
& 0xC0) == 0x80)
5466 } else if ((*currPos
& 0xC0) == 0x80) {
5467 // illegal: character starts with utf-8 intermediate byte,
5469 prevPos
= ++currPos
;
5471 // single byte character/code, just feed that
5474 KeyDown(prevPos
, currPos
- prevPos
);
5478 _Refresh(fSelStart
, fSelEnd
, true);
5480 // temporarily show transient state of inline input
5481 int32 selectionStart
= 0;
5482 int32 selectionEnd
= 0;
5483 message
->FindInt32("be:selection", 0, &selectionStart
);
5484 message
->FindInt32("be:selection", 1, &selectionEnd
);
5486 fInline
->SetSelectionOffset(selectionStart
);
5487 fInline
->SetSelectionLength(selectionEnd
- selectionStart
);
5489 const int32 inlineOffset
= fInline
->Offset();
5490 InsertText(string
, stringLen
, fSelStart
, NULL
);
5492 _Refresh(inlineOffset
, fSelEnd
, true);
5499 /*! Called when the object receives a \c B_INPUT_METHOD_LOCATION_REQUEST
5503 BTextView::_HandleInputMethodLocationRequest()
5505 ASSERT(fInline
!= NULL
);
5507 int32 offset
= fInline
->Offset();
5508 const int32 limit
= offset
+ fInline
->Length();
5510 BMessage
message(B_INPUT_METHOD_EVENT
);
5511 message
.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST
);
5513 // Add the location of the UTF8 characters
5514 while (offset
< limit
) {
5516 BPoint where
= PointAt(offset
, &height
);
5517 ConvertToScreen(&where
);
5519 message
.AddPoint("be:location_reply", where
);
5520 message
.AddFloat("be:height_reply", height
);
5522 offset
= _NextInitialByte(offset
);
5525 fInline
->Method()->SendMessage(&message
);
5529 //! Tells the Input Server method add-on to stop the current transaction.
5531 BTextView::_CancelInputMethod()
5536 InlineInput
* inlineInput
= fInline
;
5539 if (inlineInput
->IsActive() && Window()) {
5540 _Refresh(inlineInput
->Offset(), fText
->Length() - inlineInput
->Offset(),
5543 BMessage
message(B_INPUT_METHOD_EVENT
);
5544 message
.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED
);
5545 inlineInput
->Method()->SendMessage(&message
);
5552 /*! Returns the line number of the character at the given \a offset.
5554 \note This will never return the last line (use LineAt() if you
5555 need to be correct about that.) N.B.
5557 \param offset The offset of the wanted character.
5559 \return The line number of the character at the given \a offset as an int32.
5562 BTextView::_LineAt(int32 offset
) const
5564 return fLines
->OffsetToLine(offset
);
5568 /*! Returns the line number that the given \a point is on.
5570 \note This will never return the last line (use LineAt() if you
5571 need to be correct about that.) N.B.
5573 \param point The \a point the get the line number of.
5575 \return The line number of the given \a point as an int32.
5578 BTextView::_LineAt(const BPoint
& point
) const
5580 return fLines
->PixelToLine(point
.y
- fTextRect
.top
);
5584 /*! Returns whether or not the given \a offset is on the empty line at the end
5588 BTextView::_IsOnEmptyLastLine(int32 offset
) const
5590 return (offset
== TextLength() && offset
> 0
5591 && fText
->RealCharAt(offset
- 1) == B_ENTER
);
5596 BTextView::_ApplyStyleRange(int32 fromOffset
, int32 toOffset
, uint32 mode
,
5597 const BFont
* font
, const rgb_color
* color
, bool syncNullStyle
)
5600 // if a font has been given, normalize it
5601 BFont normalized
= *font
;
5602 _NormalizeFont(&normalized
);
5607 // always apply font and color to full range for non-stylable textviews
5609 toOffset
= fText
->Length();
5613 fStyles
->SyncNullStyle(fromOffset
);
5615 fStyles
->SetStyleRange(fromOffset
, toOffset
, fText
->Length(), mode
,
5621 BTextView::_NullStyleHeight() const
5623 const BFont
* font
= NULL
;
5624 fStyles
->GetNullStyle(&font
, NULL
);
5626 font_height fontHeight
;
5627 font
->GetHeight(&fontHeight
);
5628 return ceilf(fontHeight
.ascent
+ fontHeight
.descent
+ 1);
5633 BTextView::_ShowContextMenu(BPoint where
)
5636 undo_state state
= UndoState(&isRedo
);
5637 bool isUndo
= state
!= B_UNDO_UNAVAILABLE
&& !isRedo
;
5641 GetSelection(&start
, &finish
);
5643 bool canEdit
= IsEditable();
5644 int32 length
= TextLength();
5646 BPopUpMenu
* menu
= new BPopUpMenu(B_EMPTY_STRING
, false, false);
5648 BLayoutBuilder::Menu
<>(menu
)
5649 .AddItem(TRANSLATE("Undo"), B_UNDO
/*, 'Z'*/)
5650 .SetEnabled(canEdit
&& isUndo
)
5651 .AddItem(TRANSLATE("Redo"), B_UNDO
/*, 'Z', B_SHIFT_KEY*/)
5652 .SetEnabled(canEdit
&& isRedo
)
5654 .AddItem(TRANSLATE("Cut"), B_CUT
, 'X')
5655 .SetEnabled(canEdit
&& start
!= finish
)
5656 .AddItem(TRANSLATE("Copy"), B_COPY
, 'C')
5657 .SetEnabled(start
!= finish
)
5658 .AddItem(TRANSLATE("Paste"), B_PASTE
, 'V')
5659 .SetEnabled(canEdit
&& be_clipboard
->SystemCount() > 0)
5661 .AddItem(TRANSLATE("Select all"), B_SELECT_ALL
, 'A')
5662 .SetEnabled(!(start
== 0 && finish
== length
))
5665 menu
->SetTargetForItems(this);
5666 ConvertToScreen(&where
);
5667 menu
->Go(where
, true, true, true);
5672 BTextView::_FilterDisallowedChars(char* text
, ssize_t
& length
,
5673 text_run_array
* runArray
)
5675 if (!fDisallowedChars
)
5678 if (fDisallowedChars
->IsEmpty() || !text
)
5681 ssize_t stringIndex
= 0;
5683 ssize_t remNext
= 0;
5685 for (int i
= 0; i
< runArray
->count
; i
++) {
5686 runArray
->runs
[i
].offset
-= remNext
;
5687 while (stringIndex
< runArray
->runs
[i
].offset
5688 && stringIndex
< length
) {
5689 if (fDisallowedChars
->HasItem(
5690 reinterpret_cast<void*>(text
[stringIndex
]))) {
5691 memmove(text
+ stringIndex
, text
+ stringIndex
+ 1,
5692 length
- stringIndex
- 1);
5694 runArray
->runs
[i
].offset
--;
5702 while (stringIndex
< length
) {
5703 if (fDisallowedChars
->HasItem(
5704 reinterpret_cast<void*>(text
[stringIndex
]))) {
5705 memmove(text
+ stringIndex
, text
+ stringIndex
+ 1,
5706 length
- stringIndex
- 1);
5714 // #pragma mark - BTextView::TextTrackState
5717 BTextView::TextTrackState::TextTrackState(BMessenger messenger
)
5726 BMessage
message(_PING_
);
5727 const bigtime_t scrollSpeed
= 25 * 1000; // 40 scroll steps per second
5728 fRunner
= new (nothrow
) BMessageRunner(messenger
, &message
, scrollSpeed
);
5732 BTextView::TextTrackState::~TextTrackState()
5739 BTextView::TextTrackState::SimulateMouseMovement(BTextView
* textView
)
5743 // When the mouse cursor is still and outside the textview,
5744 // no B_MOUSE_MOVED message are sent, obviously. But scrolling
5745 // has to work neverthless, so we "fake" a MouseMoved() call here.
5746 textView
->GetMouse(&where
, &buttons
);
5747 textView
->_PerformMouseMoved(where
, B_INSIDE_VIEW
);
5751 // #pragma mark - Binary ABI compat
5755 B_IF_GCC_2(InvalidateLayout__9BTextViewb
, _ZN9BTextView16InvalidateLayoutEb
)(
5756 BTextView
* view
, bool descendants
)
5758 perform_data_layout_invalidated data
;
5759 data
.descendants
= descendants
;
5761 view
->Perform(PERFORM_CODE_LAYOUT_INVALIDATED
, &data
);