Added IsSupportingRTP function to simplify detecting when STUN supports RTP
[pwlib.git] / src / pwclib / ansiterm.cxx
blob46e163626bfc9b2c6245525cb0ffdd4af40d0a2e
1 /*
2 * ansiterm.cxx
4 * ANSI terminal emulation class.
6 * Portable Windows Library
8 * Copyright (c) 1993-1998 Equivalence Pty. Ltd.
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
18 * under the License.
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
24 * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
25 * All Rights Reserved.
27 * Contributor(s): ______________________________________.
29 * $Log$
30 * Revision 1.41 2001/09/10 02:51:23 robertj
31 * Major change to fix problem with error codes being corrupted in a
32 * PChannel when have simultaneous reads and writes in threads.
34 * Revision 1.40 1998/12/20 09:14:37 robertj
35 * Added pragma implementation for GNU compiler.
37 * Revision 1.39 1998/12/12 00:44:13 robertj
38 * Added capability to get text lines out of scroll back buffer.
40 * Revision 1.38 1998/11/30 04:52:12 robertj
41 * New directory structure
43 * Revision 1.37 1998/10/16 11:08:14 robertj
44 * GNU compatibility.
46 * Revision 1.36 1998/09/23 06:29:33 robertj
47 * Added open source copyright license.
49 * Revision 1.35 1998/09/18 13:09:33 robertj
50 * Fixed clearing of very bottom of window.
52 * Revision 1.34 1998/09/17 04:24:45 robertj
53 * Fixed update problems requiring screen refresh to see output text.
55 * Revision 1.33 1998/09/04 06:57:02 robertj
56 * Fixed crash if window was realy, really wide.
58 * Revision 1.32 1997/04/27 05:50:20 robertj
59 * DLL support.
61 * Revision 1.31 1996/12/21 07:01:35 robertj
62 * Fixed flush on close problem in ANSI terminal stream.
64 * Revision 1.30 1996/06/03 10:07:31 robertj
65 * Fixed bug in auto scroll to cursor position on output.
66 * Fixed bug in DEL character processing (no screen update).
68 * Revision 1.29 1996/05/15 10:14:40 robertj
69 * Rewrite of terminal to make descendant only require single character output function.
71 * Revision 1.28 1996/04/30 12:33:45 robertj
72 * Changed "inPixels" boolean to enum for three coordinate systems.
74 * Revision 1.27 1996/04/29 12:18:24 robertj
75 * Added local echo capability to terminal.
77 * Revision 1.26 1995/12/10 11:36:16 robertj
78 * Fixed bug in display of window bigger than virtual screen.
79 * Fixed warning in GNU compiler.
81 * Revision 1.25 1995/11/20 11:04:09 robertj
82 * Fixed bugs in non-adjustable rows and columns mode.
83 * Implemented horizontal scrolling.
85 * Revision 1.24 1995/11/09 12:19:50 robertj
86 * Removed GNU warning.
88 * Revision 1.23 1995/08/24 12:35:51 robertj
89 * Implemented copy/paste.
91 * Revision 1.22 1995/07/31 12:15:47 robertj
92 * Removed PContainer from PChannel ancestor.
94 * Revision 1.21 1995/07/02 01:20:58 robertj
95 * Removed OnDoubleClick() and added BOOL to OnMouseDown() for double click.
97 * Revision 1.20 1995/06/17 11:11:09 robertj
98 * Changed OnKeyDown to return BOOL indicating OnKeyInput is to be used.
100 * Revision 1.19 1995/06/04 08:41:42 robertj
101 * Some minor speedups.
103 * Revision 1.18 1995/04/25 11:28:06 robertj
104 * Fixed Borland compiler warnings.
106 * Revision 1.17 1995/04/01 08:30:08 robertj
107 * Changed default end of line wrap option.
108 * Fixed loss of focus problem.
110 * Revision 1.16 1995/01/07 04:39:44 robertj
111 * Redesigned font enumeration code and changed font styles.
113 * Revision 1.15 1995/01/06 10:43:53 robertj
114 * Changed PRealFont usage from pointer to reference.
116 * Revision 1.14 1994/12/05 11:20:22 robertj
117 * Removed Normalise() function from PRect as all rectangles should be inherently normal.
119 * Revision 1.13 1994/11/28 12:35:42 robertj
120 * Fixed closure of channel not clearing the keyboard stream.
121 * Moved saved cursor position variables to PAnsiTerminal.
123 * Revision 1.12 1994/10/30 11:52:02 robertj
124 * Fixed bug in auto scrolling vertical scroll bar position.
126 * Revision 1.11 1994/10/23 03:40:23 robertj
127 * Fixed scrolling wrong direction.
128 * Added ReturnOnLineFeed option.
129 * Added capability of setting cursor type.
130 * Improved the channel.
132 * Revision 1.10 1994/09/25 10:48:32 robertj
133 * Added boundary condition tests.
135 * Revision 1.9 1994/08/21 23:43:02 robertj
136 * Bug fixes.
138 * Revision 1.8 1994/08/01 03:41:24 robertj
139 * Use of PNEW instead of new for heap debugging. Need undef for Unix end.
141 * Revision 1.7 1994/07/27 05:58:07 robertj
142 * Synchronisation.
144 * Revision 1.6 1994/07/25 03:38:07 robertj
145 * Changed variables to int for better portability.
147 * Revision 1.5 1994/07/21 12:33:49 robertj
148 * Moved cooperative threads to common.
150 * Revision 1.4 1994/07/17 10:46:06 robertj
151 * Major redesign to get list instead of array for line data.
153 * Revision 1.3 1994/06/25 11:55:15 robertj
154 * Unix version synchronisation.
156 * Revision 1.2 1994/04/20 12:17:44 robertj
157 * some implementation
159 * Revision 1.1 1994/04/01 14:27:01 robertj
160 * Initial revision
163 #ifdef __GNUC__
164 #pragma implementation "ansiterm.h"
165 #endif
167 #include <pwlib.h>
168 #include <pwclib/ansiterm.h>
169 #include <limits.h>
171 #define new PNEW
174 PTerminal::PTerminal(PScroller * parent, ostream * kbd)
175 : PScrollable(parent)
177 keyboard = kbd;
178 PScrollable::SetFont(PFont("Courier", 10));
179 SetCursor(PCursor(PSTD_ID_CURSOR_IBEAM));
180 variableRowColumns = TRUE;
181 blockCursor = TRUE;
182 lineFeedOnReturn = FALSE;
183 returnOnLineFeed = FALSE;
184 localEcho = FALSE;
185 wrapEOL = FALSE;
186 columns = 80;
187 rows = 25;
188 maxSavedRows = 0;
189 columnModeSelection = FALSE;
190 updateColumn = 0;
191 Reset();
192 GrabFocus();
193 ShowCaret();
197 PTerminal::Row::Row(Attribute attribute)
199 for (PINDEX i = 0; i < MaxColumns; i++)
200 attrib[i] = attribute;
201 memset(ascii, ' ', sizeof(ascii));
205 void PTerminal::Reset()
207 cellWidth = font.GetAvgWidth(TRUE);
208 cellHeight = font.GetHeight(TRUE);
209 SetScaleFactor(cellWidth, cellHeight);
211 cursorRow = cursorColumn = 0;
212 caret.SetDimensions(blockCursor ? cellWidth : 1, cellHeight);
213 SetCaretPos(0, 0, PixelCoords);
215 PDim dim = GetDimensions(PixelCoords);
216 columnsVisible = dim.Width()/cellWidth;
217 if (columnsVisible > Row::MaxColumns)
218 columnsVisible = Row::MaxColumns;
219 rowsVisible = dim.Height()/cellHeight;
221 if (variableRowColumns) {
222 columns = columnsVisible > 1 ? columnsVisible : 2;
223 rows = rowsVisible > 1 ? rowsVisible : 2;
226 scrollTop = 0;
227 scrollBottom = rows;
229 attribute.bold = FALSE;
230 attribute.underline = FALSE;
231 attribute.blink = FALSE;
232 attribute.inverse = FALSE;
233 attribute.selected = FALSE;
234 attribute.fgColor = 8; // Window fg colour
235 attribute.bkColor = 9; // Window bk colour
237 rowData.RemoveAll();
238 for (PINDEX i = 0; i < rows; i++)
239 rowData.Append(new Row(attribute));
241 SetScrollRange(PRect(0, 0, columns, rows));
243 anchor = selection = 0;
245 Invalidate();
249 void PTerminal::SetFont(const PFont & newFont, BOOL toChildren)
251 PScrollable::SetFont(newFont, toChildren);
253 cellWidth = font.GetAvgWidth(TRUE);
254 cellHeight = font.GetHeight(TRUE);
255 SetScaleFactor(cellWidth, cellHeight);
257 caret.SetDimensions(blockCursor ? cellWidth : 1, cellHeight);
259 SetDimensions(GetDimensions(PixelCoords), PixelCoords);
260 Invalidate();
264 void PTerminal::_SetDimensions(PDIMENSION width, PDIMENSION height,
265 CoordinateSystem coords)
267 PScrollable::_SetDimensions(width, height, coords);
269 PDim dim = GetDimensions(PixelCoords);
270 columnsVisible = dim.Width()/cellWidth;
271 if (columnsVisible > Row::MaxColumns)
272 columnsVisible = Row::MaxColumns;
273 rowsVisible = dim.Height()/cellHeight;
275 if (variableRowColumns) {
276 variableRowColumns = FALSE;
277 SetColumns(columnsVisible);
278 SetRows(rowsVisible);
279 variableRowColumns = TRUE;
281 else
282 SetScrollRange(PRect(0, 0, columns, rowData.GetSize()));
286 void PTerminal::Row::AdjustSelection(PINDEX start, PINDEX finish, BOOL select)
288 for (PINDEX col = start; col < finish; col++)
289 attrib[col].selected = select;
293 void PTerminal::AdjustSelection(PCanvas & canvas,
294 PINDEX start, PINDEX finish, BOOL select)
296 if (start == finish)
297 return;
299 if (start > finish) {
300 PINDEX tmp = start;
301 start = finish;
302 finish = tmp;
305 PINDEX firstRow = start/Row::MaxColumns;
306 PINDEX lastRow = finish/Row::MaxColumns;
307 PINDEX firstCol = start%Row::MaxColumns;
308 PINDEX lastCol = finish%Row::MaxColumns;
310 if (columnModeSelection) {
311 if (firstCol > lastCol) {
312 PINDEX tmp = lastCol;
313 lastCol = firstCol;
314 firstCol = tmp;
316 for (PINDEX row = firstRow; row <= lastRow; row++) {
317 rowData[row].AdjustSelection(0, Row::MaxColumns, FALSE);
318 if (select)
319 rowData[row].AdjustSelection(firstCol, lastCol, select);
320 rowData[row].Draw(this, canvas, row - origin.Y(), 0);
323 else {
324 for (PINDEX row = firstRow; row <= lastRow; row++) {
325 rowData[row].AdjustSelection(firstCol,
326 row < lastRow ? (int)Row::MaxColumns : lastCol, select);
327 rowData[row].Draw(this, canvas, row - origin.Y(), 0);
328 firstCol = 0;
334 void PTerminal::OnMouseTrack(PCanvas * canvas,
335 const PPoint & where, BOOL lastTrack)
337 PINDEX pos = origin.Y()*Row::MaxColumns + origin.X();
338 if (where.Y() > 0)
339 pos += (where.Y()/cellHeight)*Row::MaxColumns;
340 if (where.X() > 0)
341 pos += (where.X() + cellWidth/2)/cellWidth;
342 if (pos >= rowData.GetSize()*Row::MaxColumns)
343 pos = rowData.GetSize()*Row::MaxColumns - 1;
345 if (pos == selection)
346 return;
348 if ((pos >= anchor && selection < anchor) ||
349 (pos <= anchor && selection > anchor)) {
350 AdjustSelection(*canvas, anchor, selection, FALSE);
351 selection = anchor;
354 if (columnModeSelection) {
355 if ((pos > anchor && pos < selection) ||
356 (pos < anchor && pos > selection))
357 AdjustSelection(*canvas, selection, pos, FALSE);
358 AdjustSelection(*canvas, anchor, pos, TRUE);
360 else
361 AdjustSelection(*canvas, pos, selection,
362 (pos > anchor && pos > selection) || (pos < anchor && pos < selection));
364 selection = pos;
366 if (lastTrack)
367 UpdateCommandSources();
371 void PTerminal::OnMouseDown(PKeyCode button, const PPoint & where, BOOL)
373 GrabFocus();
375 PCanvas * canvas = StartMouseTrack(TRUE);
377 PINDEX pos = (origin.Y() + where.Y()/cellHeight)*Row::MaxColumns +
378 origin.X() + (where.X()+cellWidth/2)/cellWidth;
379 if (pos >= rowData.GetSize()*Row::MaxColumns)
380 pos = rowData.GetSize()*Row::MaxColumns - 1;
382 if (HasSelection() && button.IsModifier(PKeyCode::Shift)) {
383 if ((anchor > selection && pos > anchor) ||
384 (anchor < selection && pos < anchor)) {
385 PINDEX tmp = anchor;
386 anchor = selection;
387 selection = tmp;
389 OnMouseTrack(canvas, where, FALSE);
391 else {
392 AdjustSelection(*canvas, anchor, selection, FALSE);
393 anchor = selection = pos;
394 columnModeSelection = button.IsModifier(PKeyCode::Control);
399 void PTerminal::OnKeyInput(const PString & str)
401 if (keyboard != NULL) {
402 *keyboard << str;
403 keyboard->flush();
404 if (localEcho)
405 Write(str);
410 void PTerminal::Row::Draw(PTerminal * term,
411 PCanvas & canvas, PINDEX line, PINDEX lastCol)
413 static PColour colour[10] = {
414 PColour::Black,
415 PColour::Red,
416 PColour::Yellow,
417 PColour::Green,
418 PColour::Blue,
419 PColour::Magenta,
420 PColour::Cyan,
421 PColour::White,
422 PApplication::Current().GetWindowFgColour(),
423 PApplication::Current().GetWindowBkColour()
425 PDIMENSION origins[MaxColumns];
427 for (int i = 0; i < MaxColumns; i++)
428 origins[i] = term->cellWidth;
430 WORD lastAttr = *(WORD *)&attrib[0];
431 PINDEX col = lastCol;
432 while (lastCol < term->columnsVisible) {
433 col++;
434 if (lastAttr != *(WORD *)&attrib[col] || col >= term->columnsVisible) {
435 if (col > lastCol) {
436 Attribute attr = attrib[lastCol];
437 if (attr.inverse ^ attr.selected) {
438 canvas.SetTextFgColour(colour[attr.bkColor]);
439 canvas.SetTextBkColour(colour[attr.fgColor]);
441 else {
442 canvas.SetTextFgColour(colour[attr.fgColor]);
443 canvas.SetTextBkColour(colour[attr.bkColor]);
445 WORD styles = PFont::Regular;
446 if (attr.bold)
447 styles |= PFont::Bold;
448 if (attr.underline)
449 styles |= PFont::Underline;
450 PFont newFont(term->font.GetFacename(), term->font.GetSize(), styles);
451 canvas.SetFont(newFont);
452 canvas.DrawTextLine(lastCol*term->cellWidth,
453 line*term->cellHeight, &ascii[lastCol], origins, col - lastCol);
455 lastAttr = *(WORD *)&attrib[col];
456 lastCol = col;
462 void PTerminal::OnRedraw(PCanvas & canvas)
464 HideCaret();
466 canvas.SetMappingRect(canvas.GetViewportRect());
467 canvas.SetOrigin(PPoint(origin.X()*scaleFactor.X(), 0));
469 PINDEX lastRow = PMIN(rowData.GetSize(), rowsVisible);
470 PINDEX bottom = origin.Y() + lastRow;
472 canvas.SetPenFgColour(GetBackgroundColour());
473 canvas.DrawRect(0, lastRow*cellHeight, 30000, 30000);
475 for (PINDEX line = 1; line <= lastRow; line++)
476 rowData[bottom - line].Draw(this, canvas, lastRow - line, 0);
478 ShowCaret();
482 PString PTerminal::GetRow(PINDEX idx) const
484 if (idx >= GetTotalRows())
485 return PString();
487 return rowData[idx].GetText();
491 BOOL PTerminal::HasSelection() const
493 return anchor != selection;
497 PString PTerminal::Row::GetText() const
499 PINDEX len = sizeof(ascii)-1;
500 while (len > 0 && ascii[len] == ' ')
501 len--;
503 return PString(ascii, len);
507 PString PTerminal::Row::GetSelection() const
509 PINDEX firstCol = 0;
510 while (firstCol < MaxColumns && !attrib[firstCol].selected)
511 firstCol++;
513 PINDEX lastCol = MaxColumns - 1;
514 while (lastCol > firstCol && (!attrib[lastCol].selected || ascii[lastCol] == ' '))
515 lastCol--;
517 if (lastCol >= firstCol)
518 return PString(&ascii[firstCol], lastCol - firstCol + 1);
519 else
520 return PString();
524 PString PTerminal::GetSelection() const
526 PString str;
528 if (HasSelection()) {
529 PINDEX row = PMIN(anchor, selection)/Row::MaxColumns;
530 PINDEX finish = PMAX(anchor, selection)/Row::MaxColumns;
531 while (row <= finish)
532 str += rowData[row++].GetSelection() + "\r\n";
535 return str;
539 void PTerminal::SetSelection(PINDEX startRow, PINDEX startColumn,
540 PINDEX finishRow, PINDEX finishColumn)
542 anchor = startRow*Row::MaxColumns + startColumn;
543 selection = finishRow*Row::MaxColumns + finishColumn;
544 Invalidate();
548 void PTerminal::SetKeyboardStream(ostream * kbd)
550 keyboard = kbd;
554 void PTerminal::SetMaxSavedRows(PINDEX newRows)
556 maxSavedRows = newRows;
557 if (maxSavedRows != 0 && rowData.GetSize() > maxSavedRows+rows)
558 rowData.SetSize(maxSavedRows+rows);
562 void PTerminal::SetVariableRowColumns(BOOL b)
564 variableRowColumns = b;
565 if (variableRowColumns)
566 SetDimensions(GetDimensions(PixelCoords), PixelCoords);
567 else {
568 SetRows(rowsVisible > 0 ? rowsVisible : 25);
569 SetColumns(columnsVisible > 1 ? columnsVisible : (int)Row::MaxColumns);
574 void PTerminal::SetRows(PINDEX newRows)
576 if (variableRowColumns) {
577 SetDimensions(GetDimensions(PixelCoords), PixelCoords);
578 return;
581 if (newRows < 2)
582 newRows = 2;
584 if (scrollTop >= newRows)
585 scrollTop = newRows - 1;
586 if (scrollBottom == rows || scrollBottom > newRows)
587 scrollBottom = newRows;
589 if (newRows <= rows)
590 AdjustCaretPosition((int)newRows - (int)rows, 0);
591 else {
592 for (PINDEX i = rows; i < newRows; i++)
593 rowData.Append(new Row(attribute));
594 SetScrollRange(PRect(0, 0, columns, rowData.GetSize()));
597 rows = newRows;
601 void PTerminal::SetColumns(PINDEX newColumns)
603 if (variableRowColumns) {
604 SetDimensions(GetDimensions(PixelCoords), PixelCoords);
605 return;
608 if (newColumns < 2)
609 newColumns = 2;
610 else if (newColumns > Row::MaxColumns)
611 newColumns = Row::MaxColumns;
613 if (columns != newColumns)
614 SetScrollRange(PRect(0, 0, columns, rowData.GetSize()));
615 columns = newColumns;
619 void PTerminal::AdjustCaretPosition(int dLine, int dCol)
621 cursorColumn += dCol;
622 if ((int)cursorColumn < 0)
623 cursorColumn = 0;
624 else if (cursorColumn >= columns)
625 cursorColumn = columns-1;
627 cursorRow += dLine;
628 if ((int)cursorRow < (int)scrollTop) // Need signed compare here
629 cursorRow = scrollTop;
630 else if (cursorRow >= scrollBottom)
631 cursorRow = scrollBottom-1;
633 SetCaretPos(cursorColumn*cellWidth,
634 blockCursor ? cursorRow*cellHeight : (cursorRow+1)*cellHeight-1,
635 PixelCoords);
639 void PTerminal::MoveCaretPosition(PDrawCanvas & canvas, int dLine, int dCol)
641 PINDEX absRow = rowData.GetSize() - rows + cursorRow;
643 rowData[absRow].Draw(this, canvas, absRow - origin.Y(), updateColumn);
644 AdjustCaretPosition(dLine, dCol);
645 updateColumn = cursorColumn;
647 if (absRow < (PINDEX)origin.Y() ||
648 absRow >= (PINDEX)origin.Y()+rowsVisible ||
649 cursorColumn < (PINDEX)origin.X() ||
650 cursorColumn >= (PINDEX)origin.X()+columnsVisible)
651 ScrollTo(cursorColumn-columnsVisible, cursorRow);
655 void PTerminal::SetBlockCursor(BOOL b)
657 blockCursor = b;
658 caret.SetDimensions(blockCursor ? cellWidth : 1, cellHeight);
662 PTerminal::Row & PTerminal::GetCursorRow()
664 return rowData[rowData.GetSize() - rows + cursorRow];
668 void PTerminal::ScrollLines(PDrawCanvas & canvas,
669 PINDEX top, PINDEX bottom, int lines)
671 PAssert(lines != 0 && bottom > top, PInvalidParameter);
673 HideCaret();
674 Update(); // Make sure interactor is up to date (fully redrawn)
675 MoveCaretPosition(canvas, 0, 0);
677 PINDEX totalRows = rowData.GetSize();
678 int topRow = totalRows - rows + top;
679 int bottomRow = totalRows - rows + bottom;
680 int absLines = PABS(lines);
681 if (absLines > (int)(bottom - top))
682 absLines = bottom - top;
684 if (lines > 0 && top == 0) {
685 while (absLines-- > 0)
686 rowData.InsertAt(bottomRow, new Row(attribute));
687 if (maxSavedRows != 0 && rowData.GetSize() > maxSavedRows+rows)
688 rowData.SetSize(maxSavedRows+rows);
689 if (rowData.GetSize() != totalRows) {
690 SetScrollRange(PRect(0, 0, columns, rowData.GetSize()));
691 ScrollTo(0, rowData.GetSize());
694 else {
695 PRect scrollRect(0, top*cellHeight, 30000, (bottom - top)*cellHeight);
696 PDIMENSION scrollAmount = absLines*cellHeight;
697 BOOL doScroll = scrollAmount < scrollRect.Height();
698 if (lines > 0) {
699 while (absLines-- > 0) {
700 rowData.InsertAt(bottomRow, new Row(attribute));
701 rowData.RemoveAt(topRow);
703 if (doScroll) {
704 PDrawCanvas canvas(this, TRUE);
705 canvas.Scroll(0, -(PORDINATE)scrollAmount, scrollRect);
706 scrollRect.SetTop(scrollRect.Bottom() - scrollAmount);
709 else {
710 while (absLines-- > 0) {
711 rowData.RemoveAt(bottomRow-1);
712 rowData.InsertAt(topRow, new Row(attribute));
714 if (doScroll) {
715 PDrawCanvas canvas(this, TRUE);
716 canvas.Scroll(0, scrollAmount, scrollRect);
717 scrollRect.SetHeight(scrollAmount);
720 Invalidate(scrollRect, PixelCoords);
723 ShowCaret();
727 void PTerminal::Row::Scroll(PTerminal * term, PCanvas & canvas,
728 PINDEX line, PINDEX left, PINDEX right, int columns)
730 PAssert(columns != 0 && right > left, PInvalidParameter);
732 if (right >= MaxColumns)
733 right = MaxColumns-1;
735 PINDEX absCols = PABS(columns);
736 if (absCols > right - left)
737 absCols = right - left;
739 if (columns < 0) {
740 memmove(&attrib[left+absCols], &attrib[left], (right-left-absCols)*sizeof(attrib[0]));
741 for (PINDEX i = 0; i < absCols; i++)
742 attrib[left+i] = term->attribute;
743 memmove(&ascii[left+absCols], &ascii[left], (right-left-absCols)*sizeof(ascii[0]));
744 memset(&ascii[left], ' ', absCols*sizeof(ascii[0]));
746 else {
747 memmove(&attrib[left], &attrib[left+absCols], (right-left-absCols)*sizeof(attrib[0]));
748 for (PINDEX i = 0; i < absCols; i++)
749 attrib[right-absCols+i] = term->attribute;
750 memmove(&ascii[left], &ascii[left+absCols], (right-left-absCols)*sizeof(ascii[0]));
751 memset(&ascii[right-absCols], ' ', absCols*sizeof(ascii[0]));
754 Draw(term, canvas, line, left);
758 void PTerminal::Write(const char * buf, PINDEX len)
760 if (len <= 0 || (len == 1 && *buf == '\0'))
761 return;
763 PDrawCanvas updateCanvas(this, TRUE);
764 updateCanvas.SetOrigin(PPoint(origin.X()*scaleFactor.X(), 0));
766 HideCaret();
767 MoveCaretPosition(updateCanvas, 0, 0);
769 for (PINDEX i = 0; i < len; i++)
770 ProcessCharacter(updateCanvas, *buf++);
772 MoveCaretPosition(updateCanvas, 0, 0);
773 ShowCaret();
777 void PTerminal::ProcessCharacter(PDrawCanvas & canvas, char ch)
779 switch (ch) {
780 case '\a' :
781 PSound::Beep();
782 break;
784 case '\b' :
785 MoveCaretPosition(canvas, 0, -1);
786 break;
788 case '\013' : // ^K
789 MoveCaretPosition(canvas, -1, 0);
790 break;
792 case '\014' : // ^L
793 MoveCaretPosition(canvas, 0, 1);
794 break;
796 case '\t' :
797 MoveCaretPosition(canvas, 0, 8 - cursorColumn%8);
798 break;
800 case '\r' :
801 MoveCaretPosition(canvas, 0, -(int)cursorColumn);
802 if (!lineFeedOnReturn)
803 break;
804 // Else do line feed case
806 case '\n' :
807 if (cursorRow < scrollBottom-1)
808 MoveCaretPosition(canvas, 1, 0);
809 else
810 ScrollLines(canvas, scrollTop, scrollBottom, 1);
811 if (returnOnLineFeed)
812 MoveCaretPosition(canvas, 0, -(int)cursorColumn);
813 break;
815 case '\x7f' : // DEL
816 if (cursorColumn > 0) {
817 MoveCaretPosition(canvas, 0, -1);
818 GetCursorRow().SetCell(cursorColumn, ' ', attribute);
820 break;
822 default :
823 GetCursorRow().SetCell(cursorColumn, ch, attribute);
825 if (cursorColumn < columns-1)
826 AdjustCaretPosition(0, 1);
827 else if (wrapEOL) {
828 ProcessCharacter(canvas, '\r');
829 ProcessCharacter(canvas, '\n');
836 ///////////////////////////////////////////////////////////////////////////////
838 PAnsiTerminal::PAnsiTerminal(PScroller * parent, ostream * kbd)
839 : PTerminal(parent, kbd)
841 Reset();
845 void PAnsiTerminal::Reset()
847 PTerminal::Reset();
849 inEscapeSequence = FALSE;
850 savedCursorRow = savedCursorColumn = 0;
854 BOOL PAnsiTerminal::OnKeyDown(PKeyCode key, unsigned repeat)
856 static const struct {
857 PKeyCode::Value value;
858 const char * sequence;
859 } ansikeys[] = {
860 { PKeyCode::F1, "\033OP" },
861 { PKeyCode::F2, "\033OQ" },
862 { PKeyCode::F3, "\033OR" },
863 { PKeyCode::F4, "\033OS" },
864 { PKeyCode::Home, "\033[H" },
865 { PKeyCode::Separator,"\033Ol" },
866 { PKeyCode::Subtract, "\033Om" },
867 { PKeyCode::Decimal, "\033On" },
868 { PKeyCode::KP0, "\033Op" },
869 { PKeyCode::KP1, "\033Oq" },
870 { PKeyCode::KP2, "\033Or" },
871 { PKeyCode::KP3, "\033Os" },
872 { PKeyCode::KP4, "\033Ot" },
873 { PKeyCode::KP5, "\033Ou" },
874 { PKeyCode::KP6, "\033Ov" },
875 { PKeyCode::KP7, "\033Ow" },
876 { PKeyCode::KP8, "\033Ox" },
877 { PKeyCode::KP9, "\033Oy" },
878 { PKeyCode::Up, "\033[A" },
879 { PKeyCode::Down, "\033[B" },
880 { PKeyCode::Left, "\033[D" },
881 { PKeyCode::Right, "\033[C" },
882 { PKeyCode::Delete, "\177" },
885 if (keyboard != NULL && key.GetModifiers() == PKeyCode::NoModifier) {
886 for (PINDEX i = 0; i < PARRAYSIZE(ansikeys); i++) {
887 if (key.GetValue() == ansikeys[i].value) {
888 do {
889 *keyboard << ansikeys[i].sequence;
890 } while (repeat-- > 0);
891 keyboard->flush();
892 break;
896 return PTerminal::OnKeyDown(key, repeat);
900 void PAnsiTerminal::ProcessCharacter(PDrawCanvas & canvas, char ch)
902 if (!inEscapeSequence) {
903 if (ch != '\033')
904 PTerminal::ProcessCharacter(canvas, ch);
905 else {
906 inEscapeSequence = TRUE;
907 inParameter = FALSE;
908 numParameters = 0;
909 memset(parameter, 0, sizeof(parameter));
911 return;
914 if (inParameter && ch >= '0' && ch <= '9') {
915 parameter[numParameters] = parameter[numParameters]*10 + ch-'0';
916 return;
919 switch (ch) {
920 case '\033' : // <esc>
921 inParameter = FALSE;
922 numParameters = 0;
923 memset(parameter, 0, sizeof(parameter));
924 return;
926 case '?' : // <esc>?
927 return;
929 case '[' : // <esc>[
930 inParameter = TRUE;
931 return;
933 case ';' : // <esc>[0;1;2; etc
934 numParameters++;
935 return;
937 case 'h' : // <esc>[0h Set Mode
938 case 'l' : // <esc>[0l Reset Mode
939 switch (parameter[0]) {
940 case 7 :
941 wrapEOL = ch == 'h';
942 break;
943 case 20 :
944 lineFeedOnReturn = ch == 'h';
945 break;
947 break;
949 case 'r' : // <esc>[0;24r Set scroll region
950 if (parameter[0] != 0)
951 parameter[0]--;
952 if (parameter[1] == 0)
953 parameter[1] = rows;
954 if (parameter[0] < parameter[1]) {
955 scrollTop = PMIN(parameter[0], rows-1);
956 scrollBottom = PMIN(parameter[1], rows);
958 break;
960 case 'A' : // <esc>[0A Cursor Up
961 MoveCaretPosition(canvas, -PMAX(parameter[0], 1), 0);
962 break;
964 case 'B' : // <esc>[0B Cursor Down
965 MoveCaretPosition(canvas, PMAX(parameter[0], 1), 0);
966 break;
968 case 'C' : // <esc>[0C Cursor Forward
969 MoveCaretPosition(canvas, 0, PMAX(parameter[0], 1));
970 break;
972 case 'D' :
973 if (inParameter) // <esc>[0D Cursor Backward
974 MoveCaretPosition(canvas, 0, -PMAX(parameter[0], 1));
975 else // <esc>D Scroll Forward
976 ScrollLines(canvas, scrollTop, scrollBottom, 1);
977 break;
979 case 'H' : // <esc>[1;1H Cursor motion
980 case 'f' : // <esc>[1;1f Set active position
981 MoveCaretPosition(canvas,
982 parameter[0]-cursorRow-1, parameter[1]-cursorColumn-1);
983 break;
985 case 'J' : // <esc>[0J Erase display
986 switch (parameter[0]) {
987 case 0 : // <esc>[0J To end
988 if (cursorRow != 0 || cursorColumn != 0) {
989 GetCursorRow().Scroll(this,
990 canvas, cursorRow, cursorColumn, INT_MAX, INT_MAX);
991 if (cursorRow+1 < rows)
992 ScrollLines(canvas, cursorRow+1, rows, INT_MAX);
993 break;
995 // Do whole screen if cursor is homed
997 case 2 : // <esc>[2J Whole screen
998 ScrollLines(canvas, 0, rows, INT_MAX);
999 break;
1001 case 1 : // <esc>[1J To beginning
1002 GetCursorRow().Scroll(this,
1003 canvas, cursorRow, 0, cursorColumn+1, INT_MAX);
1004 if (cursorRow > 0)
1005 ScrollLines(canvas, 0, cursorRow, INT_MAX);
1006 break;
1008 break;
1010 case 'K' : // <esc>[0K Erase Line
1011 switch (parameter[0]) {
1012 case 0 : // <esc>[0K To end
1013 GetCursorRow().Scroll(this,
1014 canvas, cursorRow, cursorColumn, INT_MAX, INT_MAX);
1015 break;
1017 case 1 : // <esc>[1K To beginning
1018 GetCursorRow().Scroll(this,
1019 canvas, cursorRow, 0, cursorColumn+1, INT_MAX);
1020 break;
1022 case 2 : // <esc>[2K Whole
1023 GetCursorRow().Scroll(this, canvas, cursorRow, 0, INT_MAX, INT_MAX);
1024 break;
1026 break;
1028 case 'L' : // <esc>[1L Insert Lines
1029 ScrollLines(canvas, cursorRow, scrollBottom, -PMAX(parameter[0], 1));
1030 break;
1032 case 'M' :
1033 if (inParameter) // <esc>[1M Delete Lines
1034 ScrollLines(canvas, cursorRow, scrollBottom, PMAX(parameter[0], 1));
1035 else // <esc>M Scroll Backward
1036 ScrollLines(canvas, scrollTop, scrollBottom, -1);
1037 break;
1039 case '@' : // <esc>[1@ Insert Character
1040 GetCursorRow().Scroll(this,
1041 canvas, cursorRow, cursorColumn, INT_MAX, -PMAX(parameter[0], 1));
1042 break;
1044 case 'P' : // <esc>[1P Delete Character
1045 GetCursorRow().Scroll(this,
1046 canvas, cursorRow, cursorColumn, INT_MAX, PMAX(parameter[0], 1));
1047 break;
1049 case '7' :
1050 if (inParameter)
1051 break;
1052 // Else is <esc>7 Save Cursor Position
1054 case 's' : // <esc>[s Save Cursor Position
1055 savedCursorRow = cursorRow;
1056 savedCursorColumn = cursorColumn;
1057 break;
1059 case '8' :
1060 if (inParameter)
1061 break;
1062 // Else is <esc>8 Restore Cursor Position
1064 case 'u' : // <esc>[u Restore Cursor Position
1065 cursorRow = savedCursorRow;
1066 cursorColumn = savedCursorColumn;
1067 MoveCaretPosition(canvas, 0, 0);
1068 break;
1070 case 'm' : { // <esc>[0m Set Graphic Rendition
1071 for (register PINDEX i = 0; i <= numParameters; i++) {
1072 switch (parameter[i]) {
1073 case 0 :
1074 attribute.bold = FALSE;
1075 attribute.underline = FALSE;
1076 attribute.blink = FALSE;
1077 attribute.inverse = FALSE;
1078 attribute.fgColor = 8; // Window fg colour
1079 attribute.bkColor = 9; // Window bk colour
1080 break;
1081 case 1 :
1082 attribute.bold = TRUE;
1083 break;
1084 case 4 :
1085 attribute.underline = TRUE;
1086 break;
1087 case 5 :
1088 attribute.blink = TRUE;
1089 break;
1090 case 7 :
1091 attribute.inverse = TRUE;
1092 break;
1094 default :
1095 if (parameter[i] >= 30 && parameter[i] <= 39)
1096 attribute.fgColor = parameter[i]-30;
1097 else if (parameter[i] >= 40 && parameter[i] <= 49)
1098 attribute.bkColor = parameter[i]-40;
1101 break;
1104 case 'n' : // <esc>[n Device Status Report
1105 if (keyboard != NULL) {
1106 *keyboard << "\033[" << (cursorRow+1) << ';' << (cursorColumn+1) << 'R';
1107 keyboard->flush();
1109 break;
1112 inEscapeSequence = FALSE;
1117 ///////////////////////////////////////////////////////////////////////////////
1119 PTerminalChannel::PTerminalChannel()
1121 terminal = NULL;
1125 PTerminalChannel::PTerminalChannel(PTerminal & term)
1127 Open(term);
1131 PTerminalChannel::~PTerminalChannel()
1133 Close();
1137 BOOL PTerminalChannel::IsOpen() const
1139 return terminal != NULL;
1143 PString PTerminalChannel::GetName() const
1145 if (IsOpen())
1146 return terminal->GetClass();
1148 return PString();
1152 BOOL PTerminalChannel::Read(void * buf, PINDEX len)
1154 if (!IsOpen()) {
1155 lastReadCount = 0;
1156 return SetErrorValues(NotOpen, EBADF, LastReadError);
1159 readBuf.read((char *)buf, len);
1160 lastReadCount = readBuf.gcount();
1161 if (lastReadCount > 0)
1162 return TRUE;
1164 ConvertOSError(0, LastReadError);
1165 return FALSE;
1169 BOOL PTerminalChannel::Write(const void * buf, PINDEX len)
1171 if (!IsOpen()) {
1172 lastWriteCount = 0;
1173 return SetErrorValues(NotOpen, EBADF, LastWriteError);
1176 terminal->Write((const char *)buf, len);
1177 lastWriteCount = len;
1178 return TRUE;
1182 BOOL PTerminalChannel::Close()
1184 if (!IsOpen())
1185 return SetErrorValues(NotOpen, EBADF);
1187 flush();
1189 terminal->SetKeyboardStream(NULL);
1190 terminal = NULL;
1191 return ConvertOSError(0);
1195 BOOL PTerminalChannel::Open(PTerminal & term)
1197 terminal = &term;
1198 term.SetKeyboardStream(&readBuf);
1199 return ConvertOSError(0);
1202 #undef new
1205 // End Of File ///////////////////////////////////////////////////////////////