2 * Copyright 2012-2015, Adrien Destugues, pulkomandy@gmail.com
3 * Distributed under the terms of the MIT licence.
15 #include <ScrollBar.h>
17 #include "SerialApp.h"
18 #include "libvterm/src/vterm_internal.h"
21 struct ScrollBufferItem
{
23 VTermScreenCell cells
[];
29 BView("TermView", B_WILL_DRAW
| B_FRAME_EVENTS
)
35 TermView::TermView(BRect r
)
37 BView(r
, "TermView", B_FOLLOW_ALL_SIDES
, B_WILL_DRAW
| B_FRAME_EVENTS
)
50 TermView::AttachedToWindow()
52 BView::AttachedToWindow();
58 TermView::Draw(BRect updateRect
)
60 VTermRect updatedChars
= _PixelsToGlyphs(updateRect
);
64 GetFontHeight(&height
);
67 vterm_state_get_cursorpos(vterm_obtain_state(fTerm
), &cursorPos
);
69 for (pos
.row
= updatedChars
.start_row
; pos
.row
<= updatedChars
.end_row
;
71 int x
= updatedChars
.start_col
* fFontWidth
+ kBorderSpacing
;
72 int y
= pos
.row
* fFontHeight
+ (int)ceil(height
.ascent
)
79 bool isCursor
= false;
81 pos
.col
= updatedChars
.start_col
;
84 for (pos
.col
= updatedChars
.start_col
;
85 pos
.col
<= updatedChars
.end_col
;) {
87 VTermScreenCell newCell
;
88 _GetCell(pos
, newCell
);
90 // We need to start a new extent if:
91 // - The attributes change
92 // - The colors change
93 // - The end of line is reached
94 // - The current cell is under the cursor
95 // - The current cell is right of the cursor
96 if (*(uint32_t*)&cell
.attrs
!= *(uint32_t*)&newCell
.attrs
97 || !vterm_color_equal(cell
.fg
, newCell
.fg
)
98 || !vterm_color_equal(cell
.bg
, newCell
.bg
)
99 || pos
.col
>= updatedChars
.end_col
100 || (pos
.col
== cursorPos
.col
&& pos
.row
== cursorPos
.row
)
101 || (pos
.col
== cursorPos
.col
+ 1 && pos
.row
== cursorPos
.row
)) {
103 rgb_color foreground
, background
;
104 foreground
.red
= cell
.fg
.red
;
105 foreground
.green
= cell
.fg
.green
;
106 foreground
.blue
= cell
.fg
.blue
;
107 foreground
.alpha
= 255;
108 background
.red
= cell
.bg
.red
;
109 background
.green
= cell
.bg
.green
;
110 background
.blue
= cell
.bg
.blue
;
111 background
.alpha
= 255;
113 // Draw the cursor by swapping foreground and background colors
115 SetLowColor(foreground
);
116 SetViewColor(foreground
);
117 SetHighColor(background
);
119 SetLowColor(background
);
120 SetViewColor(background
);
121 SetHighColor(foreground
);
124 FillRect(BRect(x
, y
- ceil(height
.ascent
) + 1,
125 x
+ width
* fFontWidth
- 1,
126 y
+ ceil(height
.descent
) + ceil(height
.leading
)),
129 BFont font
= be_fixed_font
;
131 font
.SetFace(B_BOLD_FACE
);
132 if (cell
.attrs
.underline
)
133 font
.SetFace(B_UNDERSCORE_FACE
);
134 if (cell
.attrs
.italic
)
135 font
.SetFace(B_ITALIC_FACE
);
136 if (cell
.attrs
.blink
) // FIXME make it actually blink
137 font
.SetFace(B_OUTLINED_FACE
);
138 if (cell
.attrs
.reverse
)
139 font
.SetFace(B_NEGATIVE_FACE
);
140 if (cell
.attrs
.strike
)
141 font
.SetFace(B_STRIKEOUT_FACE
);
143 // TODO handle "font" (alternate fonts), dwl and dhl (double size)
147 x
+= width
* fFontWidth
;
149 // Prepare for next cell
155 if (pos
.col
== cursorPos
.col
&& pos
.row
== cursorPos
.row
)
160 if (newCell
.chars
[0] == 0) {
165 char buffer
[VTERM_MAX_CHARS_PER_CELL
];
166 wcstombs(buffer
, (wchar_t*)newCell
.chars
,
167 VTERM_MAX_CHARS_PER_CELL
);
169 width
+= newCell
.width
;
170 pos
.col
+= newCell
.width
;
178 TermView::FrameResized(float width
, float height
)
180 VTermRect newSize
= _PixelsToGlyphs(BRect(0, 0, width
- 2 * kBorderSpacing
,
181 height
- 2 * kBorderSpacing
));
182 vterm_set_size(fTerm
, newSize
.end_row
, newSize
.end_col
);
187 TermView::GetPreferredSize(float* width
, float* height
)
190 *width
= kDefaultWidth
* fFontWidth
+ 2 * kBorderSpacing
- 1;
192 *height
= kDefaultHeight
* fFontHeight
+ 2 * kBorderSpacing
- 1;
197 TermView::KeyDown(const char* bytes
, int32 numBytes
)
199 // Translate some keys to more usual VT100 escape codes
222 numBytes
= fLineTerminator
.Length();
223 bytes
= fLineTerminator
.String();
227 // Send the bytes to the serial port
228 BMessage
* keyEvent
= new BMessage(kMsgDataWrite
);
229 keyEvent
->AddData("data", B_RAW_TYPE
, bytes
, numBytes
);
230 be_app_messenger
.SendMessage(keyEvent
);
235 TermView::MessageReceived(BMessage
* message
)
237 switch(message
->what
)
242 if (message
->FindRef("refs", &ref
) == B_OK
)
244 // The user just dropped a file on us
245 // TODO send it by XMODEM or so
250 BView::MessageReceived(message
);
256 TermView::SetLineTerminator(BString terminator
)
258 fLineTerminator
= terminator
;
263 TermView::PushBytes(const char* bytes
, size_t length
)
265 vterm_push_bytes(fTerm
, bytes
, length
);
275 SetFont(be_fixed_font
);
278 GetFontHeight(&height
);
279 fFontHeight
= (int)(ceilf(height
.ascent
) + ceilf(height
.descent
)
280 + ceilf(height
.leading
));
281 fFontWidth
= (int)be_fixed_font
->StringWidth("X");
282 fTerm
= vterm_new(kDefaultHeight
, kDefaultWidth
);
284 fTermScreen
= vterm_obtain_screen(fTerm
);
285 vterm_screen_set_callbacks(fTermScreen
, &sScreenCallbacks
, this);
286 vterm_screen_reset(fTermScreen
, 1);
288 vterm_parser_set_utf8(fTerm
, 1);
290 VTermScreenCell cell
;
294 _GetCell(firstPos
, cell
);
296 rgb_color background
;
297 background
.red
= cell
.bg
.red
;
298 background
.green
= cell
.bg
.green
;
299 background
.blue
= cell
.bg
.blue
;
300 background
.alpha
= 255;
302 SetViewColor(background
);
303 SetLineTerminator("\n");
308 TermView::_PixelsToGlyphs(BRect pixels
) const
310 pixels
.OffsetBy(-kBorderSpacing
, -kBorderSpacing
);
313 rect
.start_col
= (int)floor(pixels
.left
/ fFontWidth
);
314 rect
.end_col
= (int)ceil(pixels
.right
/ fFontWidth
);
315 rect
.start_row
= (int)floor(pixels
.top
/ fFontHeight
);
316 rect
.end_row
= (int)ceil(pixels
.bottom
/ fFontHeight
);
319 "TOP %d ch < %f px\n"
320 "BTM %d ch < %f px\n"
321 "LFT %d ch < %f px\n"
322 "RGH %d ch < %f px\n",
323 rect
.start_row
, pixels
.top
,
324 rect
.end_row
, pixels
.bottom
,
325 rect
.start_col
, pixels
.left
,
326 rect
.end_col
, pixels
.right
333 BRect
TermView::_GlyphsToPixels(const VTermRect
& glyphs
) const
336 rect
.top
= glyphs
.start_row
* fFontHeight
;
337 rect
.bottom
= glyphs
.end_row
* fFontHeight
;
338 rect
.left
= glyphs
.start_col
* fFontWidth
;
339 rect
.right
= glyphs
.end_col
* fFontWidth
;
341 rect
.OffsetBy(kBorderSpacing
, kBorderSpacing
);
344 "TOP %d ch > %f px (%f)\n"
345 "BTM %d ch > %f px\n"
346 "LFT %d ch > %f px (%f)\n"
347 "RGH %d ch > %f px\n",
348 glyphs
.start_row
, rect
.top
, fFontHeight
,
349 glyphs
.end_row
, rect
.bottom
,
350 glyphs
.start_col
, rect
.left
, fFontWidth
,
351 glyphs
.end_col
, rect
.right
359 TermView::_GlyphsToPixels(const int width
, const int height
) const
364 rect
.end_row
= height
;
365 rect
.end_col
= width
;
366 return _GlyphsToPixels(rect
);
371 TermView::_GetCell(VTermPos pos
, VTermScreenCell
& cell
)
373 // First handle cells from the normal screen
374 if (vterm_screen_get_cell(fTermScreen
, pos
, &cell
) != 0)
377 // Try the scroll-back buffer
378 if (pos
.row
< 0 && pos
.col
>= 0) {
379 int offset
= - pos
.row
- 1;
380 ScrollBufferItem
* line
381 = (ScrollBufferItem
*)fScrollBuffer
.ItemAt(offset
);
382 if (line
!= NULL
&& pos
.col
< line
->cols
) {
383 cell
= line
->cells
[pos
.col
];
388 // All cells outside the used terminal area are drawn with the same
389 // background color as the top-left one.
390 // TODO should they use the attributes of the closest neighbor instead?
394 vterm_screen_get_cell(fTermScreen
, firstPos
, &cell
);
401 TermView::_Damage(VTermRect rect
)
403 Invalidate(_GlyphsToPixels(rect
));
408 TermView::_MoveCursor(VTermPos pos
, VTermPos oldPos
, int visible
)
412 // We need to erase the cursor from its old position
413 r
.start_row
= oldPos
.row
;
414 r
.start_col
= oldPos
.col
;
415 r
.end_col
= oldPos
.col
+ 1;
416 r
.end_row
= oldPos
.row
+ 1;
417 Invalidate(_GlyphsToPixels(r
));
419 // And we need to draw it at the new one
420 r
.start_row
= pos
.row
;
421 r
.start_col
= pos
.col
;
422 r
.end_col
= pos
.col
+ 1;
423 r
.end_row
= pos
.row
+ 1;
424 Invalidate(_GlyphsToPixels(r
));
429 TermView::_PushLine(int cols
, const VTermScreenCell
* cells
)
431 ScrollBufferItem
* item
= (ScrollBufferItem
*)malloc(sizeof(int)
432 + cols
* sizeof(VTermScreenCell
));
434 memcpy(item
->cells
, cells
, cols
* sizeof(VTermScreenCell
));
436 fScrollBuffer
.AddItem(item
, 0);
438 free(fScrollBuffer
.RemoveItem(kScrollBackSize
));
440 int availableRows
, availableCols
;
441 vterm_get_size(fTerm
, &availableRows
, &availableCols
);
445 dirty
.end_col
= availableCols
;
447 dirty
.start_row
= -fScrollBuffer
.CountItems();
448 // FIXME we should rather use CopyRect if possible, and only invalidate the
449 // newly exposed area here.
450 Invalidate(_GlyphsToPixels(dirty
));
452 BScrollBar
* scrollBar
= ScrollBar(B_VERTICAL
);
453 if (scrollBar
!= NULL
) {
454 float range
= (fScrollBuffer
.CountItems() + availableRows
)
456 scrollBar
->SetRange(availableRows
* fFontHeight
- range
, 0.0f
);
457 // TODO we need to adjust this in FrameResized, as availableRows can
459 scrollBar
->SetProportion(availableRows
* fFontHeight
/ range
);
460 scrollBar
->SetSteps(fFontHeight
, fFontHeight
* 3);
466 TermView::_Damage(VTermRect rect
, void* user
)
468 TermView
* view
= (TermView
*)user
;
476 TermView::_MoveCursor(VTermPos pos
, VTermPos oldPos
, int visible
, void* user
)
478 TermView
* view
= (TermView
*)user
;
479 view
->_MoveCursor(pos
, oldPos
, visible
);
486 TermView::_PushLine(int cols
, const VTermScreenCell
* cells
, void* user
)
488 TermView
* view
= (TermView
*)user
;
489 view
->_PushLine(cols
, cells
);
496 VTermScreenCallbacks
TermView::sScreenCallbacks
= {
498 /*.moverect =*/ NULL
,
499 &TermView::_MoveCursor
,
500 /*.settermprop =*/ NULL
,
501 /*.setmousefunc =*/ NULL
,
504 &TermView::_PushLine
,
505 /*.sb_popline =*/ NULL
,