repository_infos: Enable automatic updates on the main Haiku repostiory.
[haiku.git] / src / apps / haikudepot / textview / TextDocumentView.cpp
blob7eba3e681f54ccfcf01eae4bd5aeba0b3472d133
1 /*
2 * Copyright 2013-2015, Stephan Aßmus <superstippi@gmx.de>.
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
6 #include "TextDocumentView.h"
8 #include <algorithm>
9 #include <stdio.h>
11 #include <Clipboard.h>
12 #include <Cursor.h>
13 #include <MessageRunner.h>
14 #include <ScrollBar.h>
15 #include <Shape.h>
16 #include <Window.h>
19 enum {
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),
27 fInsetLeft(0.0f),
28 fInsetTop(0.0f),
29 fInsetRight(0.0f),
30 fInsetBottom(0.0f),
32 fCaretBounds(),
33 fCaretBlinker(NULL),
34 fCaretBlinkToken(0),
35 fSelectionEnabled(true),
36 fShowCaret(false),
37 fMouseDown(false)
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());
53 delete fCaretBlinker;
57 void
58 TextDocumentView::MessageReceived(BMessage* message)
60 switch (message->what) {
61 case B_COPY:
62 Copy(be_clipboard);
63 break;
64 case B_SELECT_ALL:
65 SelectAll();
66 break;
68 case MSG_BLINK_CARET:
70 int32 token;
71 if (message->FindInt32("token", &token) == B_OK
72 && token == fCaretBlinkToken) {
73 _BlinkCaret();
75 break;
78 default:
79 BView::MessageReceived(message);
84 void
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)
93 return;
95 bool isCaret = fTextEditor->SelectionLength() == 0;
97 if (isCaret) {
98 if (fShowCaret && fTextEditor->IsEditingEnabled())
99 _DrawCaret(fTextEditor->CaretOffset());
100 } else {
101 _DrawSelection();
106 void
107 TextDocumentView::AttachedToWindow()
109 _UpdateScrollBars();
113 void
114 TextDocumentView::FrameResized(float width, float height)
116 fTextDocumentLayout.SetWidth(width);
117 _UpdateScrollBars();
121 void
122 TextDocumentView::WindowActivated(bool active)
124 Invalidate();
128 void
129 TextDocumentView::MakeFocus(bool focus)
131 if (focus != IsFocus())
132 Invalidate();
133 BView::MakeFocus(focus);
137 void
138 TextDocumentView::MouseDown(BPoint where)
140 if (!fSelectionEnabled)
141 return;
143 MakeFocus();
145 int32 modifiers = 0;
146 if (Window() != NULL && Window()->CurrentMessage() != NULL)
147 Window()->CurrentMessage()->FindInt32("modifiers", &modifiers);
149 fMouseDown = true;
150 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
152 bool extendSelection = (modifiers & B_SHIFT_KEY) != 0;
153 SetCaret(where, extendSelection);
157 void
158 TextDocumentView::MouseUp(BPoint where)
160 fMouseDown = false;
164 void
165 TextDocumentView::MouseMoved(BPoint where, uint32 transit,
166 const BMessage* dragMessage)
168 if (!fSelectionEnabled)
169 return;
171 BCursor iBeamCursor(B_CURSOR_ID_I_BEAM);
172 SetViewCursor(&iBeamCursor);
174 if (fMouseDown)
175 SetCaret(where, true);
179 void
180 TextDocumentView::KeyDown(const char* bytes, int32 numBytes)
182 if (fTextEditor.Get() == NULL)
183 return;
185 KeyEvent event;
186 event.bytes = bytes;
187 event.length = numBytes;
188 event.key = 0;
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);
198 _ShowCaret(true);
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.
202 Invalidate();
206 void
207 TextDocumentView::KeyUp(const char* bytes, int32 numBytes)
212 BSize
213 TextDocumentView::MinSize()
215 return BSize(fInsetLeft + fInsetRight + 50.0f, fInsetTop + fInsetBottom);
219 BSize
220 TextDocumentView::MaxSize()
222 return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
226 BSize
227 TextDocumentView::PreferredSize()
229 return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
233 bool
234 TextDocumentView::HasHeightForWidth()
236 return true;
240 void
241 TextDocumentView::GetHeightForWidth(float width, float* min, float* max,
242 float* preferred)
244 TextDocumentLayout layout(fTextDocumentLayout);
245 layout.SetWidth(_TextLayoutWidth(width));
247 float height = layout.Height() + 1 + fInsetTop + fInsetBottom;
249 if (min != NULL)
250 *min = height;
251 if (max != NULL)
252 *max = height;
253 if (preferred != NULL)
254 *preferred = height;
258 // #pragma mark -
261 void
262 TextDocumentView::SetTextDocument(const TextDocumentRef& document)
264 fTextDocument = document;
265 fTextDocumentLayout.SetTextDocument(fTextDocument);
266 if (fTextEditor.Get() != NULL)
267 fTextEditor->SetDocument(document);
269 InvalidateLayout();
270 Invalidate();
271 _UpdateScrollBars();
275 void
276 TextDocumentView::SetEditingEnabled(bool enabled)
278 if (fTextEditor.Get() != NULL)
279 fTextEditor->SetEditingEnabled(enabled);
283 void
284 TextDocumentView::SetTextEditor(const TextEditorRef& editor)
286 if (fTextEditor == editor)
287 return;
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
306 void
307 TextDocumentView::SetInsets(float inset)
309 SetInsets(inset, inset, inset, inset);
313 void
314 TextDocumentView::SetInsets(float horizontal, float vertical)
316 SetInsets(horizontal, vertical, horizontal, vertical);
320 void
321 TextDocumentView::SetInsets(float left, float top, float right, float bottom)
323 if (fInsetLeft == left && fInsetTop == top
324 && fInsetRight == right && fInsetBottom == bottom) {
325 return;
328 fInsetLeft = left;
329 fInsetTop = top;
330 fInsetRight = right;
331 fInsetBottom = bottom;
333 InvalidateLayout();
334 Invalidate();
338 void
339 TextDocumentView::SetSelectionEnabled(bool enabled)
341 if (fSelectionEnabled == enabled)
342 return;
343 fSelectionEnabled = enabled;
344 Invalidate();
345 // TODO: Deselect
349 void
350 TextDocumentView::SetCaret(BPoint location, bool extendSelection)
352 if (!fSelectionEnabled || fTextEditor.Get() == NULL)
353 return;
355 location.x -= fInsetLeft;
356 location.y -= fInsetTop;
358 fTextEditor->SetCaret(location, extendSelection);
359 _ShowCaret(!extendSelection);
360 Invalidate();
364 void
365 TextDocumentView::SelectAll()
367 if (!fSelectionEnabled || fTextEditor.Get() == NULL)
368 return;
370 fTextEditor->SelectAll();
371 _ShowCaret(false);
372 Invalidate();
376 bool
377 TextDocumentView::HasSelection() const
379 return fTextEditor.Get() != NULL && fTextEditor->HasSelection();
383 void
384 TextDocumentView::GetSelection(int32& start, int32& end) const
386 if (fTextEditor.Get() != NULL) {
387 start = fTextEditor->SelectionStart();
388 end = fTextEditor->SelectionEnd();
393 void
394 TextDocumentView::Copy(BClipboard* clipboard)
396 if (!HasSelection() || fTextDocument.Get() == NULL) {
397 // Nothing to copy, don't clear clipboard contents for now reason.
398 return;
401 if (clipboard == NULL || !clipboard->Lock())
402 return;
404 clipboard->Clear();
406 BMessage* clip = clipboard->Data();
407 if (clip != NULL) {
408 int32 start;
409 int32 end;
410 GetSelection(start, end);
412 BString text = fTextDocument->Text(start, end - start);
413 clip->AddData("text/plain", B_MIME_TYPE, text.String(),
414 text.Length());
416 // TODO: Support for "application/x-vnd.Be-text_run_array"
418 clipboard->Commit();
421 clipboard->Unlock();
425 // #pragma mark - private
428 float
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;
439 void
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);
474 void
475 TextDocumentView::_ShowCaret(bool show)
477 fShowCaret = show;
478 if (fCaretBounds.IsValid())
479 Invalidate(fCaretBounds);
480 else
481 Invalidate();
482 // Cancel previous blinker, increment blink token so we only accept
483 // the message from the blinker we just created
484 fCaretBlinkToken++;
485 BMessage message(MSG_BLINK_CARET);
486 message.AddInt32("token", fCaretBlinkToken);
487 delete fCaretBlinker;
488 fCaretBlinker = new BMessageRunner(BMessenger(this), &message,
489 500000, 1);
493 void
494 TextDocumentView::_BlinkCaret()
496 if (!fSelectionEnabled || fTextEditor.Get() == NULL)
497 return;
499 _ShowCaret(!fShowCaret);
503 void
504 TextDocumentView::_DrawCaret(int32 textOffset)
506 if (!IsFocus() || Window() == NULL || !Window()->IsActive())
507 return;
509 float x1;
510 float y1;
511 float x2;
512 float y2;
514 fTextDocumentLayout.GetTextBounds(textOffset, x1, y1, x2, y2);
515 x2 = x1 + 1;
517 fCaretBounds = BRect(x1, y1, x2, y2);
518 fCaretBounds.OffsetBy(fInsetLeft, fInsetTop);
520 SetDrawingMode(B_OP_INVERT);
521 FillRect(fCaretBounds);
525 void
526 TextDocumentView::_DrawSelection()
528 int32 start;
529 int32 end;
530 GetSelection(start, end);
532 BShape shape;
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);
542 FillShape(&shape);
545 SetHighColor(40, 40, 40);
546 StrokeShape(&shape);
550 void
551 TextDocumentView::_GetSelectionShape(BShape& shape, int32 start, int32 end)
553 float startX1;
554 float startY1;
555 float startX2;
556 float startY2;
557 fTextDocumentLayout.GetTextBounds(start, startX1, startY1, startX2,
558 startY2);
560 startX1 = floorf(startX1);
561 startY1 = floorf(startY1);
562 startX2 = ceilf(startX2);
563 startY2 = ceilf(startY2);
565 float endX1;
566 float endY1;
567 float endX2;
568 float endY2;
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);
586 shape.MoveTo(lt);
587 shape.LineTo(rt);
588 shape.LineTo(rb);
589 shape.LineTo(lb);
590 shape.Close();
591 } else if (startLineIndex == endLineIndex - 1 && endX1 <= startX1) {
592 // Selection on two lines, with gap:
593 // ---------
594 // ------###
595 // ##-------
596 // ---------
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);
604 shape.MoveTo(lt);
605 shape.LineTo(rt);
606 shape.LineTo(rb);
607 shape.LineTo(lb);
608 shape.Close();
610 lt = BPoint(0, endY1);
611 rt = BPoint(endX1, endY1);
612 rb = BPoint(endX1, endY2);
613 lb = BPoint(0, endY2);
615 shape.MoveTo(lt);
616 shape.LineTo(rt);
617 shape.LineTo(rb);
618 shape.LineTo(lb);
619 shape.Close();
620 } else {
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));
632 shape.Close();