1// This file is part of SmallBASIC
2//
3// Copyright(C) 2001-2019 Chris Warren-Smith.
4//
5// This program is distributed under the terms of the GPL v2.0 or later
6// Download the GNU Public License (GPL) from www.gnu.org
7//
8
9#include <string.h>
10
11#include "ui/screen.h"
12
13#define WHITE 15
14#define SCROLL_IND 4
15#define MAX_HEIGHT 10000
16#define TEXT_ROWS 1000
17
18#define DRAW_SHAPE \
19 Shape *rect = (*it); \
20 if (rect->_y >= _scrollY && \
21 rect->_y <= _scrollY + _height) \
22 rect->draw(_x + rect->_x, _y + rect->_y - _scrollY, w(), h(), _charWidth)
23
24int compareZIndex(const void *p1, const void *p2) {
25 ImageDisplay **i1 = (ImageDisplay **)p1;
26 ImageDisplay **i2 = (ImageDisplay **)p2;
27 return (*i1)->_zIndex < (*i2)->_zIndex ? -1 : (*i1)->_zIndex == (*i2)->_zIndex ? 0 : 1;
28}
29
30bool Shape::isFullScreen() const {
31 MAExtent screenSize = maGetScrSize();
32 return _width == EXTENT_X(screenSize) && _height == EXTENT_Y(screenSize);
33}
34
35Screen::Screen(int x, int y, int width, int height, int fontSize) :
36 Shape(x, y, width, height),
37 _font(0),
38 _fontSize(fontSize),
39 _fontStyle(0),
40 _charWidth(0),
41 _charHeight(0),
42 _scrollX(0),
43 _scrollY(0),
44 _bg(0),
45 _fg(0),
46 _curX(INITXY),
47 _curY(INITXY),
48 _dirty(0),
49 _linePadding(0) {
50}
51
52Screen::~Screen() {
53 if (_font) {
54 maFontDelete(_font);
55 }
56 _images.removeAll();
57}
58
59// converts ANSI colors to MoSync colors
60int Screen::ansiToMosync(long c) {
61 int result;
62 if (c < 0) {
63 result = -c;
64 } else {
65 result = (c > 15) ? colors[WHITE] : colors[c];
66 }
67 return result;
68}
69
70void Screen::add(Shape *button) {
71 _shapes.add(button);
72 if (button->_x + button->_width > _curX) {
73 _curX = button->_x + button->_width;
74 }
75 if (button->_y > _curY) {
76 _curY = button->_y;
77 }
78}
79
80void Screen::addImage(ImageDisplay &image) {
81 bool exists = false;
82 List_each(ImageDisplay *, it, _images) {
83 ImageDisplay *next = (*it);
84 if (next->_id == image._id) {
85 exists = true;
86 next->copyImage(image);
87 break;
88 }
89 }
90 if (!exists) {
91 _images.add(new ImageDisplay(image));
92 }
93 _images.sort(compareZIndex);
94 setDirty();
95}
96
97void Screen::clear() {
98 _curX = INITXY;
99 _curY = INITXY;
100 _scrollX = 0;
101 _scrollY = 0;
102
103 // cleanup any shapes
104 _shapes.removeAll();
105 _inputs.removeAll();
106 _images.removeAll();
107 _label.clear();
108}
109
110void Screen::drawLabel() {
111 if (!_label.empty()) {
112 int labelLen = _label.length();
113 int w = _charWidth * (labelLen + 2);
114 int h = _charHeight + 2;
115 int top = _height - h;
116 int left = (_width - w) / 2;
117 int textY = top + ((h - _charHeight) / 2);
118
119 maSetColor(0xbfbfbf);
120 maFillRect(left, top, w, h);
121 maSetColor(0xe5e5e5);
122 maLine(left, top, left + w, top);
123 maSetColor(0x737373);
124 maLine(left, top + h - 1, left + w, top + h - 1);
125 maLine(left + w, top + 1, left + w, top + h - 1);
126 maSetColor(0x403c44);
127 maDrawText(left + _charWidth, textY, _label.c_str(), labelLen);
128 }
129}
130
131void Screen::drawMenu() {
132 static const char dot[] = {'\260', '\0'};
133 int gap = _charHeight / 3;
134 int left = _width - _charWidth - (_charWidth / 2);
135 int top = (_height - _charHeight) + gap;
136 maSetColor(_fg);
137 maDrawText(left, top, dot, 1);
138 maDrawText(left, top - gap, dot, 1);
139 maDrawText(left, top - gap - gap, dot, 1);
140}
141
142void Screen::drawShape(Shape *rect) {
143 if (rect != NULL &&
144 rect->_y >= _scrollY &&
145 rect->_y + rect->_height <= _scrollY + _height) {
146 rect->draw(_x + rect->_x, _y + rect->_y - _scrollY, w(), h(), _charWidth);
147
148 List_each(FormInput *, it, _inputs) {
149 FormInput *input = (*it);
150 if (input->isVisible() &&
151 input->isDrawTop() &&
152 input->_y >= _scrollY - _height &&
153 input->_y + input->_height <= _scrollY + _height) {
154 input->draw(_x + input->_x, _y + input->_y - _scrollY, w(), h(), _charWidth);
155 }
156 }
157 }
158}
159
160void Screen::drawOverlay(bool vscroll) {
161 // draw any visible shapes
162 List_each(Shape *, it, _shapes) {
163 DRAW_SHAPE;
164 }
165
166 List_each(ImageDisplay *, it, _images) {
167 DRAW_SHAPE;
168 }
169
170 FormInput *drawTop = NULL;
171 List_each(FormInput *, it, _inputs) {
172 FormInput *input = (*it);
173 if (input->_y >= _scrollY - _height &&
174 input->isVisible()) {
175 if (input->isDrawTop()) {
176 drawTop = input;
177 } else {
178 input->draw(_x + input->_x, _y + input->_y - _scrollY, w(), h(), _charWidth);
179 }
180 }
181 }
182 if (drawTop != NULL) {
183 drawTop->draw(_x + drawTop->_x, _y + drawTop->_y - _scrollY, w(), h(), _charWidth);
184 }
185
186 if (vscroll && _curY) {
187 // display the vertical scrollbar
188 int pageHeight = _curY + _charHeight + _charHeight;
189 int barSize = _height * _height / pageHeight;
190
191 if (barSize < _height) {
192 int barRange = _height - (barSize + SCROLL_IND * 2);
193 int barTop = SCROLL_IND + (barRange * _scrollY / (pageHeight - _height));
194 int barBottom = _y + barTop + barSize;
195 if (barBottom + SCROLL_IND > _height) {
196 barBottom = _height - SCROLL_IND;
197 }
198 maSetColor(_fg);
199 maLine(_x + _width - 3, _y + barTop, _x + _width - 3, barBottom);
200 maLine(_x + _width - 4, _y + barTop, _x + _width - 4, barBottom);
201 }
202 }
203
204 drawLabel();
205
206#if defined(_FLTK)
207 drawMenu();
208#else
209 if ((!_inputs.empty() || !_label.empty()) && isFullScreen()) {
210 // draw the menu widget when in UI mode
211 drawMenu();
212 }
213#endif
214}
215
216void Screen::drawInto(bool background) {
217 maSetColor(background ? _bg : _fg);
218 setDirty();
219}
220
221int Screen::getIndex(FormInput *input) const {
222 int index;
223 if (input == NULL) {
224 index = -1;
225 } else {
226 index = 0;
227 List_each(FormInput *, it, _inputs) {
228 FormInput *next = (*it);
229 if (next == input) {
230 break;
231 } else {
232 index++;
233 }
234 }
235 }
236 return index;
237}
238
239FormInput *Screen::getMenu(FormInput *prev, int px, int py) {
240 FormInput *result = _inputs[0];
241 if (result != NULL && overlaps(px, py)) {
242 int item = (py - _y) / result->_height;
243 result = _inputs[item];
244 } else {
245 result = NULL;
246 }
247 if (result != prev) {
248 MAHandle currentHandle = maSetDrawTarget(HANDLE_SCREEN);
249 if (prev != NULL) {
250 prev->_pressed = false;
251 drawShape(prev);
252 }
253 if (result != NULL) {
254 result->_pressed = true;
255 drawShape(result);
256 }
257 maUpdateScreen();
258 maSetDrawTarget(currentHandle);
259 }
260 return result;
261}
262
263FormInput *Screen::getNextMenu(FormInput *prev, bool up) {
264 int index;
265 if (prev == NULL) {
266 index = 0;
267 } else {
268 index = getIndex(prev) + (up ? -1 : 1);
269 }
270 FormInput *next = prev;
271 if (index > -1 && index < _inputs.size()) {
272 MAHandle currentHandle = maSetDrawTarget(HANDLE_SCREEN);
273 next = _inputs.get(index);
274 next->_pressed = true;
275 drawShape(next);
276 if (prev != NULL) {
277 prev->_pressed = false;
278 drawShape(prev);
279 }
280 maUpdateScreen();
281 maSetDrawTarget(currentHandle);
282 }
283 return next;
284}
285
286void Screen::layoutInputs(int newWidth, int newHeight) {
287 List_each(FormInput *, it, _inputs) {
288 FormInput *r1 = (*it);
289 r1->layout(newWidth, newHeight);
290 }
291}
292
293// whether the point overlaps the label text
294bool Screen::overLabel(int px, int py) {
295 bool result;
296 if (!_label.empty()) {
297 int w = _charWidth * (_label.length() + 2);
298 int h = _charHeight + 2;
299 int top = _height - h;
300 int left = (_width - w) / 2;
301 result = (!OUTSIDE_RECT(px, py, left, top, w, h));
302 } else {
303 result = false;
304 }
305 return result;
306}
307
308// whether the point overlaps the menu widget
309bool Screen::overMenu(int px, int py) {
310 int w = _charWidth * 3;
311 int h = _charHeight * 2;
312 return (!OUTSIDE_RECT(px, py, _width - w, _height - h, w, h));
313}
314
315// whether the given point overlaps with the screen rectangle
316bool Screen::overlaps(int px, int py) {
317 return (!OUTSIDE_RECT(px, py, _x, _y, _width, _height));
318}
319
320int Screen::print(const char *p, int lineHeight, bool allChars) {
321 // print minimum of one character
322 int numChars = 1;
323 int cx = _charWidth;
324 int w = _width - 1;
325
326 // print further non-control, non-null characters
327 // up to the width of the line
328 while (p[numChars] > 31) {
329 cx += _charWidth;
330 if (allChars || _curX + cx < w) {
331 numChars++;
332 } else {
333 break;
334 }
335 }
336
337 _curX += cx;
338 return numChars;
339}
340
341// remove the button from the list
342void Screen::remove(Shape *button) {
343 List_each(Shape *, it, _shapes) {
344 Shape *next = (*it);
345 if (next == button) {
346 _shapes.remove(it);
347 setDirty();
348 break;
349 }
350 }
351}
352
353// remove the image from the list
354bool Screen::removeInput(FormInput *input) {
355 bool result = false;
356 List_each(FormInput *, it, _inputs) {
357 FormInput *next = (*it);
358 if (next == input) {
359 _inputs.remove(it);
360 result = true;
361 break;
362 }
363 }
364 return result;
365}
366
367// remove the image from the list
368void Screen::removeImage(unsigned imageId) {
369 List_each(ImageDisplay *, it, _images) {
370 ImageDisplay *next = (*it);
371 if (next->_id == imageId) {
372 _images.remove(it);
373 delete next;
374 setDirty();
375 break;
376 }
377 }
378}
379
380void Screen::replaceFont(int type) {
381 logEntered();
382 if (_font) {
383 maFontDelete(_font);
384 }
385 _font = maFontLoadDefault(type, _fontStyle, _fontSize);
386 if (_font != -1) {
387 maFontSetCurrent(_font);
388 MAExtent extent = maGetTextSize("W");
389 _charWidth = EXTENT_X(extent);
390 _charHeight = EXTENT_Y(extent) + LINE_SPACING;
391 } else {
392 trace("maFontLoadDefault failed: style=%d size=%d", _fontStyle, _fontSize);
393 }
394}
395
396void Screen::reset(int fontSize) {
397 _fg = DEFAULT_FOREGROUND;
398 _bg = DEFAULT_BACKGROUND;
399 setFont(false, false, fontSize);
400}
401
402void Screen::setColor(long color) {
403 _fg = ansiToMosync(color);
404}
405
406void Screen::setTextColor(long foreground, long background) {
407 _bg = ansiToMosync(background);
408 _fg = ansiToMosync(foreground);
409}
410
411// updated the current font according to accumulated flags
412void Screen::setFont(bool bold, bool italic, int size) {
413 int style = FONT_STYLE_NORMAL;
414 int type = FONT_TYPE_MONOSPACE;
415
416 if (italic) {
417 style |= FONT_STYLE_ITALIC;
418 type = FONT_TYPE_SERIF;
419 }
420 if (bold) {
421 style |= FONT_STYLE_BOLD;
422 if (!italic) {
423 type = FONT_TYPE_SANS_SERIF;
424 }
425 }
426
427 if ((style != _fontStyle || size != _fontSize) || !_font) {
428 _fontStyle = style;
429 _fontSize = size;
430 replaceFont(type);
431 }
432}
433
434FormInput *Screen::getNextField(FormInput *field) {
435 FormInput *result = NULL;
436 bool setNext = false;
437 List_each(FormInput *, it, _inputs) {
438 FormInput *next = (*it);
439 if (!next->isNoFocus()) {
440 if (result == NULL) {
441 // set result to first item
442 result = next;
443 if (field == NULL) {
444 // no next item
445 break;
446 }
447 }
448 if (setNext) {
449 result = next;
450 break;
451 } else if (next == field) {
452 setNext = true;
453 }
454 }
455 }
456 return result;
457}
458
459void Screen::updateInputs(var_p_t form, bool setVars) {
460 List_each(FormInput *, it, _inputs) {
461 FormInput *next = (*it);
462 if (setVars) {
463 next->updateField(form);
464 } else {
465 var_p_t field = next->getField(form);
466 if (field == NULL) {
467 _inputs.remove(it);
468 delete next;
469 setDirty();
470 it--;
471 } else if (next->updateUI(form, field)) {
472 setDirty();
473 }
474 }
475 }
476}
477
478//
479// Graphics and text based screen with limited scrollback support
480//
481GraphicScreen::GraphicScreen(int width, int height, int fontSize) :
482 Screen(0, 0, width, height, fontSize),
483 _image(0),
484 _underline(0),
485 _invert(0),
486 _bold(0),
487 _italic(0),
488 _imageWidth(width),
489 _imageHeight(height),
490 _curYSaved(0),
491 _curXSaved(0),
492 _tabSize(40) { // tab size in pixels (160/32 = 5)
493}
494
495GraphicScreen::~GraphicScreen() {
496 if (_image) {
497 maDestroyPlaceholder(_image);
498 }
499}
500
501// calculate the pixel movement for the given cursor position
502void GraphicScreen::calcTab() {
503 int c = 1;
504 int x = _curX + 1;
505 while (x > _tabSize) {
506 x -= _tabSize;
507 c++;
508 }
509 _curX = c * _tabSize;
510}
511
512bool GraphicScreen::construct() {
513 bool result = true;
514 _image = maCreatePlaceholder();
515 if (maCreateDrawableImage(_image, _imageWidth, _imageHeight) == RES_OK) {
516 reset(_fontSize);
517 } else {
518 result = false;
519 }
520 return result;
521}
522
523void GraphicScreen::clear() {
524 drawInto(true);
525 maSetColor(_bg);
526 maFillRect(0, 0, _imageWidth, _imageHeight);
527 Screen::clear();
528}
529
530void GraphicScreen::drawArc(int xc, int yc, double r, double start, double end, double aspect) {
531 drawInto();
532 maArc(xc, yc, r, start, end, aspect);
533}
534
535void GraphicScreen::drawBase(bool vscroll, bool update) {
536 MARect srcRect;
537 MAPoint2d dstPoint;
538 srcRect.left = 0;
539 srcRect.top = _scrollY;
540 srcRect.width = _width;
541 srcRect.height = _height;
542 dstPoint.x = _x;
543 dstPoint.y = _y;
544 MAHandle currentHandle = maSetDrawTarget(HANDLE_SCREEN);
545 maDrawImageRegion(_image, &srcRect, &dstPoint, TRANS_NONE);
546
547 drawOverlay(vscroll);
548 _dirty = 0;
549 if (update) {
550 maUpdateScreen();
551 }
552 maSetDrawTarget(currentHandle);
553}
554
555void GraphicScreen::drawEllipse(int xc, int yc, int rx, int ry, int fill) {
556 drawInto();
557 maEllipse(xc, yc, rx, ry, fill);
558}
559
560void GraphicScreen::drawImage(ImageDisplay &image) {
561 drawInto();
562 image.draw(image._x, image._y, image._width, image._height, 0);
563}
564
565void GraphicScreen::drawInto(bool background) {
566 maSetDrawTarget(_image);
567 Screen::drawInto(background);
568}
569
570void GraphicScreen::drawLine(int x1, int y1, int x2, int y2) {
571 drawInto();
572 maLine(x1, y1, x2, y2);
573}
574
575void GraphicScreen::drawRect(int x1, int y1, int x2, int y2) {
576 drawInto();
577 maLine(x1, y1, x2, y1); // top
578 maLine(x1, y2, x2, y2); // bottom
579 maLine(x1, y1, x1, y2); // left
580 maLine(x2, y1, x2, y2); // right
581}
582
583void GraphicScreen::drawRectFilled(int x1, int y1, int x2, int y2) {
584 drawInto();
585 maFillRect(x1, y1, x2 - x1, y2 - y1);
586}
587
588// returns the color of the pixel at the given xy location
589int GraphicScreen::getPixel(int x, int y) {
590 MARect rc;
591 rc.left = x;
592 rc.top = y;
593 rc.width = 1;
594 rc.height = 1;
595 int data[1];
596
597 if (x < 0 || y < 0) {
598 rc.left = x < 0 ? -x : x;
599 rc.top = y < 0 ? -y : y;
600 drawBase(false);
601 maGetImageData(HANDLE_SCREEN, &data, &rc, 1);
602 } else {
603 maGetImageData(_image, &data, &rc, 1);
604 }
605 return -(data[0] & 0x00FFFFFF);
606}
607
608// extend the image to allow for additional content on the newline
609void GraphicScreen::imageAppend(MAHandle newImage) {
610 MARect srcRect;
611 MAPoint2d dstPoint;
612
613 srcRect.left = 0;
614 srcRect.top = 0;
615 srcRect.width = _imageWidth;
616 srcRect.height = _imageHeight;
617 dstPoint.x = 0;
618 dstPoint.y = 0;
619
620 maSetDrawTarget(newImage);
621 maDrawImageRegion(_image, &srcRect, &dstPoint, TRANS_NONE);
622
623 // clear the new segment
624 maSetColor(_bg);
625 maFillRect(0, _imageHeight, _imageWidth, _imageHeight + _height);
626 _imageHeight += _height;
627
628 // cleanup the old image
629 maDestroyPlaceholder(_image);
630 _image = newImage;
631}
632
633// scroll back the image to allow for additioal content on the newline
634void GraphicScreen::imageScroll() {
635 MAHandle newImage = maCreatePlaceholder();
636 int newHeight = _imageHeight;
637 if (maCreateDrawableImage(newImage, _imageWidth, newHeight) == RES_OK) {
638 MARect srcRect;
639 MAPoint2d dstPoint;
640 int scrollBack = _height;
641 int copiedHeight = _imageHeight - scrollBack;
642
643 srcRect.left = 0;
644 srcRect.top = scrollBack;
645 srcRect.width = _imageWidth;
646 srcRect.height = copiedHeight;
647 dstPoint.x = 0;
648 dstPoint.y = 0;
649
650 maSetDrawTarget(newImage);
651 maDrawImageRegion(_image, &srcRect, &dstPoint, TRANS_NONE);
652
653 // clear the new segment
654 maSetColor(_bg);
655 maFillRect(0, copiedHeight, _imageWidth, scrollBack);
656
657 // cleanup the old image
658 maDestroyPlaceholder(_image);
659 _image = newImage;
660 _scrollY -= scrollBack;
661 _curY -= scrollBack;
662 } else {
663 // unable to create duplicate
664 maDestroyPlaceholder(newImage);
665 clear();
666 }
667}
668
669// handles the \n character
670void GraphicScreen::newLine(int lineHeight) {
671 lineHeight += _linePadding;
672 _linePadding = 0;
673 _curX = INITXY;
674 if (_height < MAX_HEIGHT) {
675 int offset = _curY + (lineHeight * 2);
676 if (offset >= _height) {
677 if (offset >= _imageHeight) {
678 // extend the base image by another page size
679 MAHandle newImage = maCreatePlaceholder();
680 int newHeight = _imageHeight + _height;
681 if (maCreateDrawableImage(newImage, _imageWidth, newHeight) != RES_OK) {
682 // maximum image size reached
683 maDestroyPlaceholder(newImage);
684 imageScroll();
685 lineHeight = 0;
686 } else {
687 imageAppend(newImage);
688 }
689 }
690 _scrollY += lineHeight;
691 }
692 _curY += lineHeight;
693 } else {
694 // overflow
695 clear();
696 }
697}
698
699int GraphicScreen::print(const char *p, int lineHeight, bool allChars) {
700 if (_curX + _charWidth >= _width - 1) {
701 newLine(lineHeight);
702 }
703
704 int cx = _curX;
705 int numChars = Screen::print(p, lineHeight);
706
707 // erase the background
708 maSetColor(_invert ? _fg : _bg);
709 maFillRect(cx, _curY, _curX-cx, lineHeight);
710
711 // draw the text buffer
712 maSetColor(_invert ? _bg : _fg);
713 maDrawText(cx, _curY, p, numChars);
714
715 if (_underline) {
716 maLine(cx, _curY + lineHeight - 2, _curX, _curY + lineHeight - 2);
717 }
718
719 return numChars;
720}
721
722// reset the current drawing variables
723void GraphicScreen::reset(int fontSize) {
724 Screen::reset(fontSize);
725 _curXSaved = 0;
726 _curYSaved = 0;
727 _invert = false;
728 _underline = false;
729 _bold = false;
730 _italic = false;
731}
732
733// update the widget to new dimensions
734void GraphicScreen::resize(int newWidth, int newHeight, int oldWidth,
735 int oldHeight, int lineHeight) {
736 logEntered();
737 bool fullscreen = ((_width - _x) == oldWidth && (_height - _y) == oldHeight);
738 if (fullscreen && (newWidth > _imageWidth || newHeight > _imageHeight)) {
739 // screen is larger than existing virtual size
740 MARect srcRect;
741 MAPoint2d dstPoint;
742 MAHandle newImage = maCreatePlaceholder();
743 int newImageWidth = MAX(newWidth, _imageWidth);
744 int newImageHeight = MAX(newHeight, _imageHeight);
745
746 srcRect.left = 0;
747 srcRect.top = 0;
748 srcRect.width = MIN(_imageWidth, newImageWidth);
749 srcRect.height = MIN(_imageHeight, newImageHeight);
750 dstPoint.x = 0;
751 dstPoint.y = 0;
752
753 if (maCreateDrawableImage(newImage, newImageWidth, newImageHeight) == RES_OK) {
754 maSetDrawTarget(newImage);
755 maSetColor(_bg);
756 maFillRect(0, 0, newImageWidth, newImageHeight);
757 maDrawImageRegion(_image, &srcRect, &dstPoint, TRANS_NONE);
758 maDestroyPlaceholder(_image);
759 } else {
760 // cannot resize - alert and abort
761 deviceLog("Failed to resize to %d %d", newImageWidth, newImageHeight);
762 abort();
763 }
764
765 _image = newImage;
766 _imageWidth = newImageWidth;
767 _imageHeight = newImageHeight;
768
769 if (_curY >= _imageHeight) {
770 _curY = _height - lineHeight;
771 }
772 if (_curX >= _imageWidth) {
773 _curX = 0;
774 }
775 }
776 _scrollY = 0;
777 _width = newWidth;
778 _height = newHeight;
779 if (!fullscreen) {
780 drawBase(false);
781 }
782 layoutInputs(newWidth, newHeight);
783}
784
785void GraphicScreen::updateFont(int size) {
786 setFont(_bold, _italic, size > 0 ? size : _fontSize);
787}
788
789// handles the given escape character. Returns whether the font has changed
790bool GraphicScreen::setGraphicsRendition(const char c, int escValue, int lineHeight) {
791 switch (c) {
792 case 'K':
793 maSetColor(_bg); // \e[K - clear to eol
794 maFillRect(_curX, _curY, _width - _curX, lineHeight);
795 break;
796 case 'G': // move to column
797 _curX = escValue * _charWidth;
798 break;
799 case 's': // save cursor position
800 _curYSaved = _curX;
801 _curXSaved = _curY;
802 break;
803 case 'u': // restore cursor position
804 _curX = _curYSaved;
805 _curY = _curXSaved;
806 break;
807 case ';': // fallthru
808 case 'm': // \e[...m - ANSI terminal
809 switch (escValue) {
810 case 0: // reset
811 reset(_fontSize);
812 break;
813 case 1: // set bold on
814 _bold = true;
815 return true;
816 case 2: // set faint on
817 break;
818 case 3: // set italic on
819 _italic = true;
820 return true;
821 case 4: // set underline on
822 _underline = true;
823 break;
824 case 5: // set blink on
825 break;
826 case 6: // rapid blink on
827 break;
828 case 7: // reverse video on
829 _invert = true;
830 break;
831 case 8: // conceal on
832 break;
833 case 21: // set bold off
834 _bold = false;
835 return true;
836 case 23:
837 _italic = false;
838 return true;
839 case 24: // set underline off
840 _underline = false;
841 break;
842 case 27: // reverse video off
843 _invert = false;
844 break;
845 // colors - 30..37 foreground, 40..47 background
846 case 30: // set black fg
847 _fg = ansiToMosync(0);
848 break;
849 case 31: // set red fg
850 _fg = ansiToMosync(4);
851 break;
852 case 32: // set green fg
853 _fg = ansiToMosync(2);
854 break;
855 case 33: // set yellow fg
856 _fg = ansiToMosync(6);
857 break;
858 case 34: // set blue fg
859 _fg = ansiToMosync(1);
860 break;
861 case 35: // set magenta fg
862 _fg = ansiToMosync(5);
863 break;
864 case 36: // set cyan fg
865 _fg = ansiToMosync(3);
866 break;
867 case 37: // set white fg
868 _fg = ansiToMosync(7);
869 break;
870 case 40: // set black bg
871 _bg = ansiToMosync(0);
872 break;
873 case 41: // set red bg
874 _bg = ansiToMosync(4);
875 break;
876 case 42: // set green bg
877 _bg = ansiToMosync(2);
878 break;
879 case 43: // set yellow bg
880 _bg = ansiToMosync(6);
881 break;
882 case 44: // set blue bg
883 _bg = ansiToMosync(1);
884 break;
885 case 45: // set magenta bg
886 _bg = ansiToMosync(5);
887 break;
888 case 46: // set cyan bg
889 _bg = ansiToMosync(3);
890 break;
891 case 47: // set white bg
892 _bg = ansiToMosync(15);
893 break;
894 case 48: // subscript on
895 break;
896 case 49: // superscript
897 break;
898 };
899 }
900 return false;
901}
902
903void GraphicScreen::setPixel(int x, int y, int c) {
904 drawInto();
905 maSetColor(ansiToMosync(c));
906 maPlot(x, y);
907}
908
909struct LineShape : Shape {
910 LineShape(int x, int y, int w, int h) : Shape(x, y, w, h) {}
911 void draw(int ax, int ay, int, int, int) {
912 maLine(_x, _y, _width, _height);
913 }
914};
915
916struct RectShape : Shape {
917 RectShape(int x, int y, int w, int h) : Shape(x, y, w, h) {}
918 void draw(int ax, int ay, int, int, int) {
919 int x1 = _x;
920 int y1 = _y;
921 int x2 = _x + _width;
922 int y2 = _y + _width;
923 maLine(x1, y1, x2, y1); // top
924 maLine(x1, y2, x2, y2); // bottom
925 maLine(x1, y1, x1, y2); // left
926 maLine(x2, y1, x2, y2); // right
927 }
928};
929
930struct RectFilledShape : Shape {
931 RectFilledShape(int x, int y, int w, int h) : Shape(x, y, w, h) {}
932 void draw(int ax, int ay, int, int, int) {
933 maFillRect(_x, _y, _width, _height);
934 }
935};
936
937//
938// Text based screen with a large scrollback buffer
939//
940TextScreen::TextScreen(int width, int height, int fontSize) :
941 Screen(0, 0, width, height, fontSize),
942 _over(NULL),
943 _inset(0, 0, 0, 0),
944 _buffer(NULL),
945 _head(0),
946 _tail(0),
947 _rows(TEXT_ROWS),
948 _cols(0) {
949}
950
951TextScreen::~TextScreen() {
952 delete[] _buffer;
953}
954
955void TextScreen::calcTab() {
956 Row *line = getLine(_head); // pointer to current line
957 line->tab();
958}
959
960bool TextScreen::construct() {
961 reset(_fontSize);
962 _buffer = new Row[_rows];
963 return (_buffer != NULL);
964}
965
966//
967// clear the screen
968//
969void TextScreen::clear() {
970 _head = _tail = _cols = 0;
971 getLine(0)->clear();
972 Screen::clear();
973}
974
975//
976// draw the text
977//
978void TextScreen::drawBase(bool vscroll, bool update) {
979 // prepare escape state variables
980 bool bold = false;
981 bool italic = false;
982 bool underline = false;
983 bool invert = false;
984 int color = DEFAULT_FOREGROUND;
985
986 // calculate rows to display
987 int pageRows = getPageRows();
988 int textRows = getTextRows();
989 int numRows = textRows < pageRows ? textRows : pageRows;
990 int firstRow = _tail + (_scrollY / _charHeight);
991 int yoffs = _scrollY % _charHeight; // smooth scrolling offset
992
993 // prevent drawing beyond available text
994 if (numRows > textRows - firstRow) {
995 numRows = textRows - firstRow;
996 }
997
998 if (_over != NULL && _over != this) {
999 _over->drawBase(vscroll, false);
1000 }
1001
1002 // setup the background colour
1003 MAHandle currentHandle = maSetDrawTarget(HANDLE_SCREEN_BUFFER);
1004 maSetColor(_bg);
1005 maFillRect(_x, _y, _width, _height);
1006 maSetColor(color);
1007
1008 // draw the visible segments
1009 int pageWidth = 0;
1010 for (int row = firstRow, rows = 0, py = _y - yoffs;
1011 rows < numRows;
1012 row++, rows++, py += _charHeight) {
1013 Row *line = getLine(row); // next logical row
1014 TextSeg *seg = line->_head;
1015 int px = (_x + INITXY) - _scrollX;
1016 while (seg != NULL) {
1017 if (seg->escape(&bold, &italic, &underline, &invert)) {
1018 setFont(bold, italic, _fontSize);
1019 } else if (seg->isReset()) {
1020 reset(_fontSize);
1021 bold = false;
1022 italic = false;
1023 underline = false;
1024 invert = false;
1025 color = DEFAULT_FOREGROUND;
1026 maSetColor(color);
1027 }
1028 if (seg->_color != NO_COLOR) {
1029 color = seg->_color;
1030 maSetColor(color);
1031 }
1032 int width = seg->width();
1033 if (seg->_str) {
1034 if (invert) {
1035 maSetColor(_fg);
1036 maFillRect(px, py, width, _charHeight);
1037 maSetColor(_bg);
1038 maDrawText(px, py, seg->_str, seg->numChars());
1039 maSetColor(color);
1040 } else {
1041 maDrawText(px, py, seg->_str, seg->numChars());
1042 }
1043 if (underline) {
1044 maLine(px, py + _charHeight, width, py + _charHeight);
1045 }
1046 }
1047 px += width;
1048 seg = seg->_next;
1049 }
1050 int rowWidth = line->width();
1051 if (rowWidth > pageWidth) {
1052 pageWidth = rowWidth;
1053 }
1054 }
1055
1056 // draw the base components
1057 drawOverlay(vscroll);
1058 _dirty = 0;
1059 maUpdateScreen();
1060 maSetDrawTarget(currentHandle);
1061}
1062
1063void TextScreen::drawLine(int x1, int y1, int x2, int y2) {
1064 add(new LineShape(x1, y1, x2, y2));
1065}
1066
1067void TextScreen::drawRect(int x1, int y1, int x2, int y2) {
1068 add(new RectShape(x1, y1, x2, y2));
1069}
1070
1071void TextScreen::drawRectFilled(int x1, int y1, int x2, int y2) {
1072 add(new RectFilledShape(x1, y1, x2 - x1, y2 - y1));
1073}
1074
1075//
1076// return a pointer to the specified line of the display.
1077//
1078Row *TextScreen::getLine(int pos) {
1079 if (pos < 0) {
1080 pos += _rows;
1081 }
1082 if (pos > _rows - 1) {
1083 pos -= _rows;
1084 }
1085
1086 return &_buffer[pos];
1087}
1088
1089void TextScreen::inset(int x, int y, int w, int h, Screen *over) {
1090 _x = over->_x;
1091 _y = over->_y;
1092 _width = over->_width;
1093 _height = over->_height;
1094 _inset._x = x;
1095 _inset._y = y;
1096 _inset._width = w;
1097 _inset._height = h;
1098 _over = over;
1099 resize(_width, _height, 0, 0, 0);
1100}
1101
1102void TextScreen::newLine(int lineHeight) {
1103 // scroll by moving logical last line
1104 if (getTextRows() == _rows) {
1105 _tail = (_tail + 1 >= _rows) ? 0 : _tail + 1;
1106 }
1107 _head = (_head + 1 >= _rows) ? 0 : _head + 1;
1108
1109 // clear the new line
1110 Row* line = getLine(_head);
1111 line->clear();
1112
1113 lineHeight += _linePadding;
1114 _linePadding = 0;
1115
1116 _curX = INITXY;
1117 _curY += lineHeight;
1118}
1119
1120void TextScreen::resize(int newWidth, int newHeight, int, int, int) {
1121 if (_inset._width != 0 && _inset._height != 0) {
1122 _x = newWidth * _inset._x / 100;
1123 _y = newHeight * _inset._y / 100;
1124 _width = (newWidth * _inset._width / 100) - _x;
1125 _height = (newHeight * _inset._height / 100) - _y;
1126 } else {
1127 _width = newWidth;
1128 _height = newHeight;
1129 }
1130 layoutInputs(newWidth, newHeight);
1131}
1132
1133//
1134// Creates a new segment then prints the line
1135//
1136int TextScreen::print(const char *p, int lineHeight, bool allChars) {
1137 Row *line = getLine(_head);
1138 TextSeg *segment = new TextSeg();
1139 line->append(segment);
1140
1141 int numChars = Screen::print(p, lineHeight, true);
1142
1143 // Print the next (possible) line of text
1144 segment->setText(p, numChars);
1145
1146 // remember the maximum line length
1147 if (numChars > _cols) {
1148 _cols = numChars;
1149 }
1150
1151 return numChars;
1152}
1153
1154//
1155// performs the ANSI text SGI function.
1156//
1157bool TextScreen::setGraphicsRendition(const char c, int escValue, int lineHeight) {
1158 if (c == ';' || c == 'm') {
1159 Row *line = getLine(_head);
1160 TextSeg *segment = line->next();
1161
1162 if (segment->_flags || segment->_color != NO_COLOR) {
1163 // avoid overwriting existing flags
1164 segment = new TextSeg();
1165 line->append(segment);
1166 }
1167
1168 switch (escValue) {
1169 case 0:
1170 segment->reset();
1171 reset(_fontSize);
1172 break;
1173
1174 case 1: // Bold on
1175 segment->set(TextSeg::BOLD, true);
1176 break;
1177
1178 case 2: // Faint on
1179 segment->set(TextSeg::BOLD, false);
1180 break;
1181
1182 case 3: // Italic on
1183 segment->set(TextSeg::ITALIC, true);
1184 break;
1185
1186 case 4: // Underscrore
1187 segment->set(TextSeg::UNDERLINE, true);
1188 break;
1189
1190 case 7: // reverse video on
1191 segment->set(TextSeg::INVERT, true);
1192 break;
1193
1194 case 21: // set bold off
1195 segment->set(TextSeg::BOLD, false);
1196 break;
1197
1198 case 23:
1199 segment->set(TextSeg::ITALIC, false);
1200 break;
1201
1202 case 24: // set underline off
1203 segment->set(TextSeg::UNDERLINE, false);
1204 break;
1205
1206 case 27: // reverse video off
1207 segment->set(TextSeg::INVERT, false);
1208 break;
1209
1210 case 30: // Black
1211 _fg = segment->_color = ansiToMosync(0);
1212 break;
1213
1214 case 31: // Red
1215 _fg = segment->_color = ansiToMosync(4);
1216 break;
1217
1218 case 32: // Green
1219 _fg = segment->_color = ansiToMosync(2);
1220 break;
1221
1222 case 33: // Yellow
1223 _fg = segment->_color = ansiToMosync(6);
1224 break;
1225
1226 case 34: // Blue
1227 _fg = segment->_color = ansiToMosync(1);
1228 break;
1229
1230 case 35: // Magenta
1231 _fg = segment->_color = ansiToMosync(5);
1232 break;
1233
1234 case 36: // Cyan
1235 _fg = segment->_color = ansiToMosync(3);
1236 break;
1237
1238 case 37: // White
1239 _fg = segment->_color = ansiToMosync(7);
1240 break;
1241 }
1242 }
1243 return false;
1244}
1245
1246