xref: /haiku/src/apps/serialconnect/TermView.cpp (revision 91c0454716f24a7454b20f7a54cfe32e288e3710)
1 /*
2  * Copyright 2012-2014, 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 	}
188 
189 	// Send the bytes to the serial port
190 	BMessage* keyEvent = new BMessage(kMsgDataWrite);
191 	keyEvent->AddData("data", B_RAW_TYPE, bytes, numBytes);
192 	be_app_messenger.SendMessage(keyEvent);
193 }
194 
195 
196 void
197 TermView::MessageReceived(BMessage* message)
198 {
199 	switch(message->what)
200 	{
201 		case 'DATA':
202 		{
203 			entry_ref ref;
204 			if (message->FindRef("refs", &ref) == B_OK)
205 			{
206 				// The user just dropped a file on us
207 				// TODO send it by XMODEM or so
208 			}
209 			break;
210 		}
211 		default:
212 			BView::MessageReceived(message);
213 	}
214 }
215 
216 
217 void
218 TermView::PushBytes(const char* bytes, size_t length)
219 {
220 	vterm_push_bytes(fTerm, bytes, length);
221 }
222 
223 
224 // #pragma mark -
225 
226 
227 void
228 TermView::_Init()
229 {
230 	SetFont(be_fixed_font);
231 
232 	font_height height;
233 	GetFontHeight(&height);
234 	fFontHeight = (int)(ceilf(height.ascent) + ceilf(height.descent)
235 		+ ceilf(height.leading));
236 	fFontWidth = (int)be_fixed_font->StringWidth("X");
237 	fTerm = vterm_new(kDefaultHeight, kDefaultWidth);
238 
239 	fTermScreen = vterm_obtain_screen(fTerm);
240 	vterm_screen_set_callbacks(fTermScreen, &sScreenCallbacks, this);
241 	vterm_screen_reset(fTermScreen, 1);
242 
243 	vterm_parser_set_utf8(fTerm, 1);
244 
245 	VTermScreenCell cell;
246 	VTermPos firstPos;
247 	firstPos.row = 0;
248 	firstPos.col = 0;
249 	_GetCell(firstPos, cell);
250 
251 	rgb_color background;
252 	background.red = cell.bg.red;
253 	background.green = cell.bg.green;
254 	background.blue = cell.bg.blue;
255 	background.alpha = 255;
256 
257 	SetViewColor(background);
258 }
259 
260 
261 VTermRect
262 TermView::_PixelsToGlyphs(BRect pixels) const
263 {
264 	pixels.OffsetBy(-kBorderSpacing, -kBorderSpacing);
265 
266 	VTermRect rect;
267 	rect.start_col = (int)floor(pixels.left / fFontWidth);
268 	rect.end_col = (int)ceil(pixels.right / fFontWidth);
269 	rect.start_row = (int)floor(pixels.top / fFontHeight);
270 	rect.end_row = (int)ceil(pixels.bottom / fFontHeight);
271 #if 0
272 	printf(
273 		"TOP %d ch < %f px\n"
274 		"BTM %d ch < %f px\n"
275 		"LFT %d ch < %f px\n"
276 		"RGH %d ch < %f px\n",
277 		rect.start_row, pixels.top,
278 		rect.end_row, pixels.bottom,
279 		rect.start_col, pixels.left,
280 		rect.end_col, pixels.right
281 	);
282 #endif
283 	return rect;
284 }
285 
286 
287 BRect TermView::_GlyphsToPixels(const VTermRect& glyphs) const
288 {
289 	BRect rect;
290 	rect.top = glyphs.start_row * fFontHeight;
291 	rect.bottom = glyphs.end_row * fFontHeight;
292 	rect.left = glyphs.start_col * fFontWidth;
293 	rect.right = glyphs.end_col * fFontWidth;
294 
295 	rect.OffsetBy(kBorderSpacing, kBorderSpacing);
296 #if 0
297 	printf(
298 		"TOP %d ch > %f px (%f)\n"
299 		"BTM %d ch > %f px\n"
300 		"LFT %d ch > %f px (%f)\n"
301 		"RGH %d ch > %f px\n",
302 		glyphs.start_row, rect.top, fFontHeight,
303 		glyphs.end_row, rect.bottom,
304 		glyphs.start_col, rect.left, fFontWidth,
305 		glyphs.end_col, rect.right
306 	);
307 #endif
308 	return rect;
309 }
310 
311 
312 BRect
313 TermView::_GlyphsToPixels(const int width, const int height) const
314 {
315 	VTermRect rect;
316 	rect.start_row = 0;
317 	rect.start_col = 0;
318 	rect.end_row = height;
319 	rect.end_col = width;
320 	return _GlyphsToPixels(rect);
321 }
322 
323 
324 void
325 TermView::_GetCell(VTermPos pos, VTermScreenCell& cell)
326 {
327 	// First handle cells from the normal screen
328 	if (vterm_screen_get_cell(fTermScreen, pos, &cell) != 0)
329 		return;
330 
331 	// Try the scroll-back buffer
332 	if (pos.row < 0 && pos.col >= 0) {
333 		int offset = - pos.row - 1;
334 		ScrollBufferItem* line
335 			= (ScrollBufferItem*)fScrollBuffer.ItemAt(offset);
336 		if (line != NULL && pos.col < line->cols) {
337 			cell = line->cells[pos.col];
338 			return;
339 		}
340 	}
341 
342 	// All cells outside the used terminal area are drawn with the same
343 	// background color as the top-left one.
344 	// TODO should they use the attributes of the closest neighbor instead?
345 	VTermPos firstPos;
346 	firstPos.row = 0;
347 	firstPos.col = 0;
348 	vterm_screen_get_cell(fTermScreen, firstPos, &cell);
349 	cell.chars[0] = 0;
350 	cell.width = 1;
351 }
352 
353 
354 void
355 TermView::_Damage(VTermRect rect)
356 {
357 	Invalidate(_GlyphsToPixels(rect));
358 }
359 
360 
361 void
362 TermView::_MoveCursor(VTermPos pos, VTermPos oldPos, int visible)
363 {
364 	VTermRect r;
365 	r.start_row = pos.row;
366 	r.start_col = pos.col;
367 	r.end_col = pos.col + 1;
368 	r.end_row = pos.row + 1;
369 	Invalidate(_GlyphsToPixels(r));
370 
371 	r.start_row = oldPos.row;
372 	r.start_col = oldPos.col;
373 	r.end_col = oldPos.col + 1;
374 	r.end_row = oldPos.row + 1;
375 	Invalidate(_GlyphsToPixels(r));
376 }
377 
378 
379 void
380 TermView::_PushLine(int cols, const VTermScreenCell* cells)
381 {
382 	ScrollBufferItem* item = (ScrollBufferItem*)malloc(sizeof(int)
383 		+ cols * sizeof(VTermScreenCell));
384 	item->cols = cols;
385 	memcpy(item->cells, cells, cols * sizeof(VTermScreenCell));
386 
387 	fScrollBuffer.AddItem(item, 0);
388 
389 	free(fScrollBuffer.RemoveItem(kScrollBackSize));
390 
391 	int availableRows, availableCols;
392 	vterm_get_size(fTerm, &availableRows, &availableCols);
393 
394 	VTermRect dirty;
395 	dirty.start_col = 0;
396 	dirty.end_col = availableCols;
397 	dirty.end_row = 0;
398 	dirty.start_row = -fScrollBuffer.CountItems();
399 	// FIXME we should rather use CopyRect if possible, and only invalidate the
400 	// newly exposed area here.
401 	Invalidate(_GlyphsToPixels(dirty));
402 
403 	BScrollBar* scrollBar = ScrollBar(B_VERTICAL);
404 	if (scrollBar != NULL) {
405 		float range = (fScrollBuffer.CountItems() + availableRows)
406 			* fFontHeight;
407 		scrollBar->SetRange(availableRows * fFontHeight - range, 0.0f);
408 		// TODO we need to adjust this in FrameResized, as availableRows can
409 		// change
410 		scrollBar->SetProportion(availableRows * fFontHeight / range);
411 		scrollBar->SetSteps(fFontHeight, fFontHeight * 3);
412 	}
413 }
414 
415 
416 /* static */ int
417 TermView::_Damage(VTermRect rect, void* user)
418 {
419 	TermView* view = (TermView*)user;
420 	view->_Damage(rect);
421 
422 	return 0;
423 }
424 
425 
426 /* static */ int
427 TermView::_MoveCursor(VTermPos pos, VTermPos oldPos, int visible, void* user)
428 {
429 	TermView* view = (TermView*)user;
430 	view->_MoveCursor(pos, oldPos, visible);
431 
432 	return 0;
433 }
434 
435 
436 /* static */ int
437 TermView::_PushLine(int cols, const VTermScreenCell* cells, void* user)
438 {
439 	TermView* view = (TermView*)user;
440 	view->_PushLine(cols, cells);
441 
442 	return 0;
443 }
444 
445 
446 const
447 VTermScreenCallbacks TermView::sScreenCallbacks = {
448 	&TermView::_Damage,
449 	/*.moverect =*/ NULL,
450 	&TermView::_MoveCursor,
451 	/*.settermprop =*/ NULL,
452 	/*.setmousefunc =*/ NULL,
453 	/*.bell =*/ NULL,
454 	/*.resize =*/ NULL,
455 	&TermView::_PushLine,
456 	/*.sb_popline =*/ NULL,
457 };
458