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