xref: /haiku/src/apps/serialconnect/TermView.cpp (revision 220c5364ab445eb20376f964ade2f7d3a09e163a)
1 /*
2  * Copyright 2012-2015, Adrien Destugues, pulkomandy@gmail.com
3  * Distributed under the terms of the MIT licence.
4  */
5 
6 
7 #include "TermView.h"
8 
9 #include <stdio.h>
10 
11 #include <Entry.h>
12 #include <File.h>
13 #include <Font.h>
14 #include <Layout.h>
15 #include <ScrollBar.h>
16 
17 #include "SerialApp.h"
18 
19 
20 struct ScrollBufferItem {
21 	int cols;
22 	VTermScreenCell cells[];
23 };
24 
25 
26 TermView::TermView()
27 	:
28 	BView("TermView", B_WILL_DRAW | B_FRAME_EVENTS)
29 {
30 	_Init();
31 }
32 
33 
34 TermView::TermView(BRect r)
35 	:
36 	BView(r, "TermView", B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_FRAME_EVENTS)
37 {
38 	_Init();
39 }
40 
41 
42 TermView::~TermView()
43 {
44 	vterm_free(fTerm);
45 }
46 
47 
48 void
49 TermView::AttachedToWindow()
50 {
51 	BView::AttachedToWindow();
52 	MakeFocus();
53 }
54 
55 
56 void
57 TermView::Draw(BRect updateRect)
58 {
59 	VTermRect updatedChars = _PixelsToGlyphs(updateRect);
60 
61 	VTermPos pos;
62 	font_height height;
63 	GetFontHeight(&height);
64 
65 	VTermPos cursorPos;
66 	vterm_state_get_cursorpos(vterm_obtain_state(fTerm), &cursorPos);
67 
68 	for (pos.row = updatedChars.start_row; pos.row <= updatedChars.end_row;
69 			pos.row++) {
70 		int x = updatedChars.start_col * fFontWidth + kBorderSpacing;
71 		int y = pos.row * fFontHeight + (int)ceil(height.ascent)
72 			+ kBorderSpacing;
73 		MovePenTo(x, y);
74 
75 		for (pos.col = updatedChars.start_col;
76 				pos.col <= updatedChars.end_col;) {
77 			VTermScreenCell cell;
78 
79 			_GetCell(pos, cell);
80 
81 			rgb_color foreground, background;
82 			foreground.red = cell.fg.red;
83 			foreground.green = cell.fg.green;
84 			foreground.blue = cell.fg.blue;
85 			foreground.alpha = 255;
86 			background.red = cell.bg.red;
87 			background.green = cell.bg.green;
88 			background.blue = cell.bg.blue;
89 			background.alpha = 255;
90 
91 			// Draw the cursor by swapping foreground and background colors
92 			if ((pos.col == cursorPos.col && pos.row == cursorPos.row)) {
93 				SetLowColor(foreground);
94 				SetViewColor(foreground);
95 				SetHighColor(background);
96 			} else {
97 				SetLowColor(background);
98 				SetViewColor(background);
99 				SetHighColor(foreground);
100 			}
101 
102 			FillRect(BRect(x, y - ceil(height.ascent) + 1,
103 				x + cell.width * fFontWidth - 1,
104 				y + ceil(height.descent) + ceil(height.leading)),
105 				B_SOLID_LOW);
106 
107 			BFont font = be_fixed_font;
108 			if (cell.attrs.bold)
109 				font.SetFace(B_BOLD_FACE);
110 			if (cell.attrs.underline)
111 				font.SetFace(B_UNDERSCORE_FACE);
112 			if (cell.attrs.italic)
113 				font.SetFace(B_ITALIC_FACE);
114 			if (cell.attrs.blink) // FIXME make it actually blink
115 				font.SetFace(B_OUTLINED_FACE);
116 			if (cell.attrs.reverse)
117 				font.SetFace(B_NEGATIVE_FACE);
118 			if (cell.attrs.strike)
119 				font.SetFace(B_STRIKEOUT_FACE);
120 
121 			// TODO handle "font" (alternate fonts), dwl and dhl (double size)
122 
123 			SetFont(&font);
124 
125 			if (cell.chars[0] == 0) {
126 				DrawString(" ");
127 				pos.col ++;
128 				x += fFontWidth;
129 			} else {
130 				char buffer[VTERM_MAX_CHARS_PER_CELL];
131 				wcstombs(buffer, (wchar_t*)cell.chars,
132 						VTERM_MAX_CHARS_PER_CELL);
133 
134 				DrawString(buffer);
135 				x += (int)ceil(StringWidth(buffer));
136 				pos.col += cell.width;
137 			}
138 		}
139 	}
140 }
141 
142 
143 void
144 TermView::FrameResized(float width, float height)
145 {
146 	VTermRect newSize = _PixelsToGlyphs(BRect(0, 0, width - 2 * kBorderSpacing,
147 				height - 2 * kBorderSpacing));
148 	vterm_set_size(fTerm, newSize.end_row, newSize.end_col);
149 }
150 
151 
152 void
153 TermView::GetPreferredSize(float* width, float* height)
154 {
155 	if (width != NULL)
156 		*width = kDefaultWidth * fFontWidth + 2 * kBorderSpacing - 1;
157 	if (height != NULL)
158 		*height = kDefaultHeight * fFontHeight + 2 * kBorderSpacing - 1;
159 }
160 
161 
162 void
163 TermView::KeyDown(const char* bytes, int32 numBytes)
164 {
165 	// Translate some keys to more usual VT100 escape codes
166 	switch (bytes[0]) {
167 		case B_UP_ARROW:
168 			numBytes = 3;
169 			bytes = "\x1B[A";
170 			break;
171 		case B_DOWN_ARROW:
172 			numBytes = 3;
173 			bytes = "\x1B[B";
174 			break;
175 		case B_RIGHT_ARROW:
176 			numBytes = 3;
177 			bytes = "\x1B[C";
178 			break;
179 		case B_LEFT_ARROW:
180 			numBytes = 3;
181 			bytes = "\x1B[D";
182 			break;
183 		case B_BACKSPACE:
184 			numBytes = 1;
185 			bytes = "\x7F";
186 			break;
187 		case '\n':
188 			numBytes = fLineTerminator.Length();
189 			bytes = fLineTerminator.String();
190 			break;
191 	}
192 
193 	// Send the bytes to the serial port
194 	BMessage* keyEvent = new BMessage(kMsgDataWrite);
195 	keyEvent->AddData("data", B_RAW_TYPE, bytes, numBytes);
196 	be_app_messenger.SendMessage(keyEvent);
197 }
198 
199 
200 void
201 TermView::MessageReceived(BMessage* message)
202 {
203 	switch(message->what)
204 	{
205 		case 'DATA':
206 		{
207 			entry_ref ref;
208 			if (message->FindRef("refs", &ref) == B_OK)
209 			{
210 				// The user just dropped a file on us
211 				// TODO send it by XMODEM or so
212 			}
213 			break;
214 		}
215 		default:
216 			BView::MessageReceived(message);
217 	}
218 }
219 
220 
221 void
222 TermView::SetLineTerminator(BString terminator)
223 {
224 	fLineTerminator = terminator;
225 }
226 
227 
228 void
229 TermView::PushBytes(const char* bytes, size_t length)
230 {
231 	vterm_push_bytes(fTerm, bytes, length);
232 }
233 
234 
235 // #pragma mark -
236 
237 
238 void
239 TermView::_Init()
240 {
241 	SetFont(be_fixed_font);
242 
243 	font_height height;
244 	GetFontHeight(&height);
245 	fFontHeight = (int)(ceilf(height.ascent) + ceilf(height.descent)
246 		+ ceilf(height.leading));
247 	fFontWidth = (int)be_fixed_font->StringWidth("X");
248 	fTerm = vterm_new(kDefaultHeight, kDefaultWidth);
249 
250 	fTermScreen = vterm_obtain_screen(fTerm);
251 	vterm_screen_set_callbacks(fTermScreen, &sScreenCallbacks, this);
252 	vterm_screen_reset(fTermScreen, 1);
253 
254 	vterm_parser_set_utf8(fTerm, 1);
255 
256 	VTermScreenCell cell;
257 	VTermPos firstPos;
258 	firstPos.row = 0;
259 	firstPos.col = 0;
260 	_GetCell(firstPos, cell);
261 
262 	rgb_color background;
263 	background.red = cell.bg.red;
264 	background.green = cell.bg.green;
265 	background.blue = cell.bg.blue;
266 	background.alpha = 255;
267 
268 	SetViewColor(background);
269 	SetLineTerminator("\n");
270 }
271 
272 
273 VTermRect
274 TermView::_PixelsToGlyphs(BRect pixels) const
275 {
276 	pixels.OffsetBy(-kBorderSpacing, -kBorderSpacing);
277 
278 	VTermRect rect;
279 	rect.start_col = (int)floor(pixels.left / fFontWidth);
280 	rect.end_col = (int)ceil(pixels.right / fFontWidth);
281 	rect.start_row = (int)floor(pixels.top / fFontHeight);
282 	rect.end_row = (int)ceil(pixels.bottom / fFontHeight);
283 #if 0
284 	printf(
285 		"TOP %d ch < %f px\n"
286 		"BTM %d ch < %f px\n"
287 		"LFT %d ch < %f px\n"
288 		"RGH %d ch < %f px\n",
289 		rect.start_row, pixels.top,
290 		rect.end_row, pixels.bottom,
291 		rect.start_col, pixels.left,
292 		rect.end_col, pixels.right
293 	);
294 #endif
295 	return rect;
296 }
297 
298 
299 BRect TermView::_GlyphsToPixels(const VTermRect& glyphs) const
300 {
301 	BRect rect;
302 	rect.top = glyphs.start_row * fFontHeight;
303 	rect.bottom = glyphs.end_row * fFontHeight;
304 	rect.left = glyphs.start_col * fFontWidth;
305 	rect.right = glyphs.end_col * fFontWidth;
306 
307 	rect.OffsetBy(kBorderSpacing, kBorderSpacing);
308 #if 0
309 	printf(
310 		"TOP %d ch > %f px (%f)\n"
311 		"BTM %d ch > %f px\n"
312 		"LFT %d ch > %f px (%f)\n"
313 		"RGH %d ch > %f px\n",
314 		glyphs.start_row, rect.top, fFontHeight,
315 		glyphs.end_row, rect.bottom,
316 		glyphs.start_col, rect.left, fFontWidth,
317 		glyphs.end_col, rect.right
318 	);
319 #endif
320 	return rect;
321 }
322 
323 
324 BRect
325 TermView::_GlyphsToPixels(const int width, const int height) const
326 {
327 	VTermRect rect;
328 	rect.start_row = 0;
329 	rect.start_col = 0;
330 	rect.end_row = height;
331 	rect.end_col = width;
332 	return _GlyphsToPixels(rect);
333 }
334 
335 
336 void
337 TermView::_GetCell(VTermPos pos, VTermScreenCell& cell)
338 {
339 	// First handle cells from the normal screen
340 	if (vterm_screen_get_cell(fTermScreen, pos, &cell) != 0)
341 		return;
342 
343 	// Try the scroll-back buffer
344 	if (pos.row < 0 && pos.col >= 0) {
345 		int offset = - pos.row - 1;
346 		ScrollBufferItem* line
347 			= (ScrollBufferItem*)fScrollBuffer.ItemAt(offset);
348 		if (line != NULL && pos.col < line->cols) {
349 			cell = line->cells[pos.col];
350 			return;
351 		}
352 	}
353 
354 	// All cells outside the used terminal area are drawn with the same
355 	// background color as the top-left one.
356 	// TODO should they use the attributes of the closest neighbor instead?
357 	VTermPos firstPos;
358 	firstPos.row = 0;
359 	firstPos.col = 0;
360 	vterm_screen_get_cell(fTermScreen, firstPos, &cell);
361 	cell.chars[0] = 0;
362 	cell.width = 1;
363 }
364 
365 
366 void
367 TermView::_Damage(VTermRect rect)
368 {
369 	Invalidate(_GlyphsToPixels(rect));
370 }
371 
372 
373 void
374 TermView::_MoveCursor(VTermPos pos, VTermPos oldPos, int visible)
375 {
376 	VTermRect r;
377 	r.start_row = pos.row;
378 	r.start_col = pos.col;
379 	r.end_col = pos.col + 1;
380 	r.end_row = pos.row + 1;
381 	Invalidate(_GlyphsToPixels(r));
382 
383 	r.start_row = oldPos.row;
384 	r.start_col = oldPos.col;
385 	r.end_col = oldPos.col + 1;
386 	r.end_row = oldPos.row + 1;
387 	Invalidate(_GlyphsToPixels(r));
388 }
389 
390 
391 void
392 TermView::_PushLine(int cols, const VTermScreenCell* cells)
393 {
394 	ScrollBufferItem* item = (ScrollBufferItem*)malloc(sizeof(int)
395 		+ cols * sizeof(VTermScreenCell));
396 	item->cols = cols;
397 	memcpy(item->cells, cells, cols * sizeof(VTermScreenCell));
398 
399 	fScrollBuffer.AddItem(item, 0);
400 
401 	free(fScrollBuffer.RemoveItem(kScrollBackSize));
402 
403 	int availableRows, availableCols;
404 	vterm_get_size(fTerm, &availableRows, &availableCols);
405 
406 	VTermRect dirty;
407 	dirty.start_col = 0;
408 	dirty.end_col = availableCols;
409 	dirty.end_row = 0;
410 	dirty.start_row = -fScrollBuffer.CountItems();
411 	// FIXME we should rather use CopyRect if possible, and only invalidate the
412 	// newly exposed area here.
413 	Invalidate(_GlyphsToPixels(dirty));
414 
415 	BScrollBar* scrollBar = ScrollBar(B_VERTICAL);
416 	if (scrollBar != NULL) {
417 		float range = (fScrollBuffer.CountItems() + availableRows)
418 			* fFontHeight;
419 		scrollBar->SetRange(availableRows * fFontHeight - range, 0.0f);
420 		// TODO we need to adjust this in FrameResized, as availableRows can
421 		// change
422 		scrollBar->SetProportion(availableRows * fFontHeight / range);
423 		scrollBar->SetSteps(fFontHeight, fFontHeight * 3);
424 	}
425 }
426 
427 
428 /* static */ int
429 TermView::_Damage(VTermRect rect, void* user)
430 {
431 	TermView* view = (TermView*)user;
432 	view->_Damage(rect);
433 
434 	return 0;
435 }
436 
437 
438 /* static */ int
439 TermView::_MoveCursor(VTermPos pos, VTermPos oldPos, int visible, void* user)
440 {
441 	TermView* view = (TermView*)user;
442 	view->_MoveCursor(pos, oldPos, visible);
443 
444 	return 0;
445 }
446 
447 
448 /* static */ int
449 TermView::_PushLine(int cols, const VTermScreenCell* cells, void* user)
450 {
451 	TermView* view = (TermView*)user;
452 	view->_PushLine(cols, cells);
453 
454 	return 0;
455 }
456 
457 
458 const
459 VTermScreenCallbacks TermView::sScreenCallbacks = {
460 	&TermView::_Damage,
461 	/*.moverect =*/ NULL,
462 	&TermView::_MoveCursor,
463 	/*.settermprop =*/ NULL,
464 	/*.setmousefunc =*/ NULL,
465 	/*.bell =*/ NULL,
466 	/*.resize =*/ NULL,
467 	&TermView::_PushLine,
468 	/*.sb_popline =*/ NULL,
469 };
470