2 * Copyright 2013-2015, Stephan Aßmus <superstippi@gmx.de>.
3 * All rights reserved. Distributed under the terms of the MIT License.
6 #include "TextDocumentView.h"
11 #include <Clipboard.h>
13 #include <MessageRunner.h>
14 #include <ScrollBar.h>
20 MSG_BLINK_CARET
= 'blnk',
24 TextDocumentView::TextDocumentView(const char* name
)
26 BView(name
, B_WILL_DRAW
| B_FULL_UPDATE_ON_RESIZE
| B_FRAME_EVENTS
),
35 fSelectionEnabled(true),
39 fTextDocumentLayout
.SetWidth(_TextLayoutWidth(Bounds().Width()));
41 // Set default TextEditor
42 SetTextEditor(TextEditorRef(new(std::nothrow
) TextEditor(), true));
44 SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
45 SetLowUIColor(ViewUIColor());
49 TextDocumentView::~TextDocumentView()
51 // Don't forget to remove listeners
52 SetTextEditor(TextEditorRef());
58 TextDocumentView::MessageReceived(BMessage
* message
)
60 switch (message
->what
) {
71 if (message
->FindInt32("token", &token
) == B_OK
72 && token
== fCaretBlinkToken
) {
79 BView::MessageReceived(message
);
85 TextDocumentView::Draw(BRect updateRect
)
87 FillRect(updateRect
, B_SOLID_LOW
);
89 fTextDocumentLayout
.SetWidth(_TextLayoutWidth(Bounds().Width()));
90 fTextDocumentLayout
.Draw(this, BPoint(fInsetLeft
, fInsetTop
), updateRect
);
92 if (!fSelectionEnabled
|| fTextEditor
.Get() == NULL
)
95 bool isCaret
= fTextEditor
->SelectionLength() == 0;
98 if (fShowCaret
&& fTextEditor
->IsEditingEnabled())
99 _DrawCaret(fTextEditor
->CaretOffset());
107 TextDocumentView::AttachedToWindow()
114 TextDocumentView::FrameResized(float width
, float height
)
116 fTextDocumentLayout
.SetWidth(width
);
122 TextDocumentView::WindowActivated(bool active
)
129 TextDocumentView::MakeFocus(bool focus
)
131 if (focus
!= IsFocus())
133 BView::MakeFocus(focus
);
138 TextDocumentView::MouseDown(BPoint where
)
140 if (!fSelectionEnabled
)
146 if (Window() != NULL
&& Window()->CurrentMessage() != NULL
)
147 Window()->CurrentMessage()->FindInt32("modifiers", &modifiers
);
150 SetMouseEventMask(B_POINTER_EVENTS
, B_LOCK_WINDOW_FOCUS
);
152 bool extendSelection
= (modifiers
& B_SHIFT_KEY
) != 0;
153 SetCaret(where
, extendSelection
);
158 TextDocumentView::MouseUp(BPoint where
)
165 TextDocumentView::MouseMoved(BPoint where
, uint32 transit
,
166 const BMessage
* dragMessage
)
168 if (!fSelectionEnabled
)
171 BCursor
iBeamCursor(B_CURSOR_ID_I_BEAM
);
172 SetViewCursor(&iBeamCursor
);
175 SetCaret(where
, true);
180 TextDocumentView::KeyDown(const char* bytes
, int32 numBytes
)
182 if (fTextEditor
.Get() == NULL
)
187 event
.length
= numBytes
;
189 event
.modifiers
= modifiers();
191 if (Window() != NULL
&& Window()->CurrentMessage() != NULL
) {
192 BMessage
* message
= Window()->CurrentMessage();
193 message
->FindInt32("raw_char", &event
.key
);
194 message
->FindInt32("modifiers", &event
.modifiers
);
197 fTextEditor
->KeyDown(event
);
199 // TODO: It is necessary to invalidate all, since neither the caret bounds
200 // are updated in a way that would work here, nor is the text updated
201 // correcty which has been edited.
207 TextDocumentView::KeyUp(const char* bytes
, int32 numBytes
)
213 TextDocumentView::MinSize()
215 return BSize(fInsetLeft
+ fInsetRight
+ 50.0f
, fInsetTop
+ fInsetBottom
);
220 TextDocumentView::MaxSize()
222 return BSize(B_SIZE_UNLIMITED
, B_SIZE_UNLIMITED
);
227 TextDocumentView::PreferredSize()
229 return BSize(B_SIZE_UNLIMITED
, B_SIZE_UNLIMITED
);
234 TextDocumentView::HasHeightForWidth()
241 TextDocumentView::GetHeightForWidth(float width
, float* min
, float* max
,
244 TextDocumentLayout
layout(fTextDocumentLayout
);
245 layout
.SetWidth(_TextLayoutWidth(width
));
247 float height
= layout
.Height() + 1 + fInsetTop
+ fInsetBottom
;
253 if (preferred
!= NULL
)
262 TextDocumentView::SetTextDocument(const TextDocumentRef
& document
)
264 fTextDocument
= document
;
265 fTextDocumentLayout
.SetTextDocument(fTextDocument
);
266 if (fTextEditor
.Get() != NULL
)
267 fTextEditor
->SetDocument(document
);
276 TextDocumentView::SetEditingEnabled(bool enabled
)
278 if (fTextEditor
.Get() != NULL
)
279 fTextEditor
->SetEditingEnabled(enabled
);
284 TextDocumentView::SetTextEditor(const TextEditorRef
& editor
)
286 if (fTextEditor
== editor
)
289 if (fTextEditor
.Get() != NULL
) {
290 fTextEditor
->SetDocument(TextDocumentRef());
291 fTextEditor
->SetLayout(TextDocumentLayoutRef());
292 // TODO: Probably has to remove listeners
295 fTextEditor
= editor
;
297 if (fTextEditor
.Get() != NULL
) {
298 fTextEditor
->SetDocument(fTextDocument
);
299 fTextEditor
->SetLayout(TextDocumentLayoutRef(
300 &fTextDocumentLayout
));
301 // TODO: Probably has to add listeners
307 TextDocumentView::SetInsets(float inset
)
309 SetInsets(inset
, inset
, inset
, inset
);
314 TextDocumentView::SetInsets(float horizontal
, float vertical
)
316 SetInsets(horizontal
, vertical
, horizontal
, vertical
);
321 TextDocumentView::SetInsets(float left
, float top
, float right
, float bottom
)
323 if (fInsetLeft
== left
&& fInsetTop
== top
324 && fInsetRight
== right
&& fInsetBottom
== bottom
) {
331 fInsetBottom
= bottom
;
339 TextDocumentView::SetSelectionEnabled(bool enabled
)
341 if (fSelectionEnabled
== enabled
)
343 fSelectionEnabled
= enabled
;
350 TextDocumentView::SetCaret(BPoint location
, bool extendSelection
)
352 if (!fSelectionEnabled
|| fTextEditor
.Get() == NULL
)
355 location
.x
-= fInsetLeft
;
356 location
.y
-= fInsetTop
;
358 fTextEditor
->SetCaret(location
, extendSelection
);
359 _ShowCaret(!extendSelection
);
365 TextDocumentView::SelectAll()
367 if (!fSelectionEnabled
|| fTextEditor
.Get() == NULL
)
370 fTextEditor
->SelectAll();
377 TextDocumentView::HasSelection() const
379 return fTextEditor
.Get() != NULL
&& fTextEditor
->HasSelection();
384 TextDocumentView::GetSelection(int32
& start
, int32
& end
) const
386 if (fTextEditor
.Get() != NULL
) {
387 start
= fTextEditor
->SelectionStart();
388 end
= fTextEditor
->SelectionEnd();
394 TextDocumentView::Copy(BClipboard
* clipboard
)
396 if (!HasSelection() || fTextDocument
.Get() == NULL
) {
397 // Nothing to copy, don't clear clipboard contents for now reason.
401 if (clipboard
== NULL
|| !clipboard
->Lock())
406 BMessage
* clip
= clipboard
->Data();
410 GetSelection(start
, end
);
412 BString text
= fTextDocument
->Text(start
, end
- start
);
413 clip
->AddData("text/plain", B_MIME_TYPE
, text
.String(),
416 // TODO: Support for "application/x-vnd.Be-text_run_array"
425 // #pragma mark - private
429 TextDocumentView::_TextLayoutWidth(float viewWidth
) const
431 return viewWidth
- (fInsetLeft
+ fInsetRight
);
435 static const float kHorizontalScrollBarStep
= 10.0f
;
436 static const float kVerticalScrollBarStep
= 12.0f
;
440 TextDocumentView::_UpdateScrollBars()
442 BRect
bounds(Bounds());
444 BScrollBar
* horizontalScrollBar
= ScrollBar(B_HORIZONTAL
);
445 if (horizontalScrollBar
!= NULL
) {
446 long viewWidth
= bounds
.IntegerWidth();
447 long dataWidth
= (long)ceilf(
448 fTextDocumentLayout
.Width() + fInsetLeft
+ fInsetRight
);
450 long maxRange
= dataWidth
- viewWidth
;
451 maxRange
= std::max(maxRange
, 0L);
453 horizontalScrollBar
->SetRange(0, (float)maxRange
);
454 horizontalScrollBar
->SetProportion((float)viewWidth
/ dataWidth
);
455 horizontalScrollBar
->SetSteps(kHorizontalScrollBarStep
, dataWidth
/ 10);
458 BScrollBar
* verticalScrollBar
= ScrollBar(B_VERTICAL
);
459 if (verticalScrollBar
!= NULL
) {
460 long viewHeight
= bounds
.IntegerHeight();
461 long dataHeight
= (long)ceilf(
462 fTextDocumentLayout
.Height() + fInsetTop
+ fInsetBottom
);
464 long maxRange
= dataHeight
- viewHeight
;
465 maxRange
= std::max(maxRange
, 0L);
467 verticalScrollBar
->SetRange(0, maxRange
);
468 verticalScrollBar
->SetProportion((float)viewHeight
/ dataHeight
);
469 verticalScrollBar
->SetSteps(kVerticalScrollBarStep
, viewHeight
);
475 TextDocumentView::_ShowCaret(bool show
)
478 if (fCaretBounds
.IsValid())
479 Invalidate(fCaretBounds
);
482 // Cancel previous blinker, increment blink token so we only accept
483 // the message from the blinker we just created
485 BMessage
message(MSG_BLINK_CARET
);
486 message
.AddInt32("token", fCaretBlinkToken
);
487 delete fCaretBlinker
;
488 fCaretBlinker
= new BMessageRunner(BMessenger(this), &message
,
494 TextDocumentView::_BlinkCaret()
496 if (!fSelectionEnabled
|| fTextEditor
.Get() == NULL
)
499 _ShowCaret(!fShowCaret
);
504 TextDocumentView::_DrawCaret(int32 textOffset
)
506 if (!IsFocus() || Window() == NULL
|| !Window()->IsActive())
514 fTextDocumentLayout
.GetTextBounds(textOffset
, x1
, y1
, x2
, y2
);
517 fCaretBounds
= BRect(x1
, y1
, x2
, y2
);
518 fCaretBounds
.OffsetBy(fInsetLeft
, fInsetTop
);
520 SetDrawingMode(B_OP_INVERT
);
521 FillRect(fCaretBounds
);
526 TextDocumentView::_DrawSelection()
530 GetSelection(start
, end
);
533 _GetSelectionShape(shape
, start
, end
);
535 SetDrawingMode(B_OP_SUBTRACT
);
537 SetLineMode(B_ROUND_CAP
, B_ROUND_JOIN
);
538 MovePenTo(fInsetLeft
- 0.5f
, fInsetTop
- 0.5f
);
540 if (IsFocus() && Window() != NULL
&& Window()->IsActive()) {
541 SetHighColor(30, 30, 30);
545 SetHighColor(40, 40, 40);
551 TextDocumentView::_GetSelectionShape(BShape
& shape
, int32 start
, int32 end
)
557 fTextDocumentLayout
.GetTextBounds(start
, startX1
, startY1
, startX2
,
560 startX1
= floorf(startX1
);
561 startY1
= floorf(startY1
);
562 startX2
= ceilf(startX2
);
563 startY2
= ceilf(startY2
);
569 fTextDocumentLayout
.GetTextBounds(end
, endX1
, endY1
, endX2
, endY2
);
571 endX1
= floorf(endX1
);
572 endY1
= floorf(endY1
);
573 endX2
= ceilf(endX2
);
574 endY2
= ceilf(endY2
);
576 int32 startLineIndex
= fTextDocumentLayout
.LineIndexForOffset(start
);
577 int32 endLineIndex
= fTextDocumentLayout
.LineIndexForOffset(end
);
579 if (startLineIndex
== endLineIndex
) {
580 // Selection on one line
581 BPoint
lt(startX1
, startY1
);
582 BPoint
rt(endX1
, endY1
);
583 BPoint
rb(endX1
, endY2
);
584 BPoint
lb(startX1
, startY2
);
591 } else if (startLineIndex
== endLineIndex
- 1 && endX1
<= startX1
) {
592 // Selection on two lines, with gap:
597 float width
= ceilf(fTextDocumentLayout
.Width());
599 BPoint
lt(startX1
, startY1
);
600 BPoint
rt(width
, startY1
);
601 BPoint
rb(width
, startY2
);
602 BPoint
lb(startX1
, startY2
);
610 lt
= BPoint(0, endY1
);
611 rt
= BPoint(endX1
, endY1
);
612 rb
= BPoint(endX1
, endY2
);
613 lb
= BPoint(0, endY2
);
621 // Selection over multiple lines
622 float width
= ceilf(fTextDocumentLayout
.Width());
624 shape
.MoveTo(BPoint(startX1
, startY1
));
625 shape
.LineTo(BPoint(width
, startY1
));
626 shape
.LineTo(BPoint(width
, endY1
));
627 shape
.LineTo(BPoint(endX1
, endY1
));
628 shape
.LineTo(BPoint(endX1
, endY2
));
629 shape
.LineTo(BPoint(0, endY2
));
630 shape
.LineTo(BPoint(0, startY2
));
631 shape
.LineTo(BPoint(startX1
, startY2
));