Usermode/GUI Terminal - A few fixes to vt100 code exposed by use in kernel
[tpg/acess2.git] / Usermode / Applications / gui_terminal_src / display.c
1 /*
2  * Acess GUI Terminal
3  * - By John Hodge (thePowersGang)
4  *
5  * display.c
6  * - Abstract display manipulation methods
7  */
8 #define DEBUG   0
9 #include "include/display.h"
10 #include <acess/sys.h>  // _SysDebug
11 #include <stdlib.h>     // exit
12 #include <string.h>
13 #include <unicode.h>
14 #include <stdio.h>
15 #include <axwin3/axwin.h>
16 #include <axwin3/richtext.h>
17 #include <stdbool.h>
18 #include <assert.h>
19
20 #if DEBUG
21 # define DEBUGS(v...)   _SysDebug(v)
22 #else
23 # define DEBUGS(v...)   do{}while(0)
24 #endif
25
26 #define UNIMPLIMENTED() do{_SysDebug("UNIMPLIMENTED %s", __func__); exit(-1);}while(0)
27
28 static inline int MIN(int a, int b) { return (a < b ? a : b); }
29 static inline int MAX(int a, int b) { return (a > b ? a : b); }
30
31 // === EXTERN ==
32 extern tHWND    gMainWindow;
33
34 typedef struct sLine    tLine;
35
36 struct sLine {
37         char    *Data;
38         // TODO: Cache offsets to avoid scan-forward
39         size_t  Len;
40         size_t  Size;
41         bool    IsDirty;
42 };
43
44 struct sTerminal {
45          int    ViewCols;
46          int    ViewRows;
47
48          int    ScrollTop;
49          int    ScrollRows;
50
51          int    CursorRow;
52          int    CursorCol;
53
54          int    SavedRow;
55          int    SavedCol;
56
57         size_t  CursorByte;
58
59         bool    bUsingAltBuf;
60         bool    bHaveSwappedBuffers;
61         
62          int    OtherBufRow;
63          int    OtherBufCol;
64
65         size_t  ViewOffset;     
66         size_t  TotalLines;
67         struct sLine    *PriBuf;
68         
69         struct sLine    *AltBuf;
70 };
71
72 // === PROTOTYPES ===
73 void    Display_int_SetCursor(tTerminal *Term, int Row, int Col);
74
75 // === GLOBALS ===
76 tTerminal       gMainBuffer;
77  int    giCurrentLine;
78  int    giCurrentLinePos;       // byte offset, not column
79  int    giCurrentCol;
80  int    giFirstDispLine;        // First displayed line
81  int    giFirstLine;    // Ring buffer start
82 char    **gasDisplayLines;
83  int    *gaiDisplayLineSizes;
84 char    *gabDisplayLinesDirty;
85
86 // === CODE ===
87 tTerminal *Display_Init(int Cols, int Lines, int ExtraScrollbackLines)
88 {
89         tTerminal       *term = &gMainBuffer;
90         term->ViewCols = Cols;
91         term->ViewRows = Lines;
92         term->TotalLines = Lines + ExtraScrollbackLines;
93         term->PriBuf = calloc( sizeof(tLine), (Lines + ExtraScrollbackLines) );
94         
95         AxWin3_RichText_SetLineCount(gMainWindow, Lines+ExtraScrollbackLines);
96         AxWin3_RichText_SetCursorType(gMainWindow, 1);  // TODO: enum
97         return term;
98 }
99
100 // Return the byte length of a single on-screen character
101 size_t _GetCharLength(size_t AvailLength, const char *Text, uint32_t *BaseCodepoint)
102 {
103         if( !AvailLength )
104                 return 0;
105         
106         size_t  charlen = ReadUTF8(Text, BaseCodepoint);
107         
108         while(charlen < AvailLength)
109         {
110                 uint32_t        cp;
111                 size_t size = ReadUTF8(Text+charlen, &cp);
112                 if( Unicode_IsPrinting(cp) )
113                         break;
114                 charlen += size;
115         }
116         
117         return charlen;
118 }
119
120 tLine *Display_int_GetCurLine(tTerminal *Term)
121 {
122          int    lineidx = Term->CursorRow + (Term->bUsingAltBuf ? 0 : Term->ViewOffset);
123         //_SysDebug("lineidx = %i", lineidx);
124         return (Term->bUsingAltBuf ? Term->AltBuf : Term->PriBuf) + lineidx;
125 }
126
127 size_t Display_int_PushCharacter(tTerminal *Term, size_t AvailLength, const char *Text)
128 {
129         tLine   *lineptr = Display_int_GetCurLine(Term);
130         uint32_t        cp;
131         size_t  charlen = _GetCharLength(AvailLength, Text, &cp);
132         bool    bOverwrite = Unicode_IsPrinting(cp);
133         
134         // Figure out how much we need to shift the stream
135          int    shift;
136         if( bOverwrite ) {
137                 //_SysDebug("GetCharLen(%i-%i, %p+%i, NULL)", lineptr->Len, Term->CursorByte,
138                 //      lineptr->Data, Term->CursorByte);
139                 if( Term->CursorByte )
140                         assert(lineptr->Data);
141                 size_t nextlen = _GetCharLength(
142                         lineptr->Len-Term->CursorByte,
143                         lineptr->Data+Term->CursorByte,
144                         NULL);
145                 shift = charlen - nextlen;
146         }
147         else {
148                 shift = charlen;
149         }
150         
151         // Ensure we have space enough
152         if( !lineptr->Data || shift > 0 ) {
153                 const size_t    size_step = 64;
154                 assert(shift > 0);
155                 size_t  newsize = (lineptr->Len+shift+1 + size_step-1) & ~(size_step-1);
156                 if( newsize > lineptr->Size ) {
157                         lineptr->Size = newsize;
158                         void *tmp = realloc(lineptr->Data, lineptr->Size);
159                         if( !tmp )      perror("Display_int_PushCharacter - realloc");
160                         //_SysDebug("realloc gave %p from %p for line %i", tmp, lineptr->Data,
161                         //      Term->CursorRow);
162                         lineptr->Data = tmp;
163                 }
164         }
165         
166         // Insert into stream
167         char    *base = lineptr->Data + Term->CursorByte;
168         if( Term->CursorByte == lineptr->Len ) {
169                 // No shifting needed
170         }
171         else if( shift >= 0 ) {
172                 size_t  bytes = lineptr->Len - (Term->CursorByte+shift);
173                 memmove(base+shift, base, bytes);
174         }
175         else {
176                 shift = -shift;
177                 size_t  bytes = lineptr->Len - (Term->CursorByte+shift);
178                 memmove(base, base+shift, bytes);
179         }
180         memcpy(base, Text, charlen);
181         lineptr->IsDirty = true;
182         lineptr->Len += shift;
183         lineptr->Data[lineptr->Len] = 0;        // NULL Terminate
184
185         Term->CursorByte += charlen;
186         
187         // HACKY: Prevents the CursorCol++ in Display_AddText from having an effect
188         if( !bOverwrite )
189                 Term->CursorCol --;
190         
191         return charlen;
192 }
193
194 void Display_AddText(tTerminal *Term, size_t Length, const char *UTF8Text)
195 {
196         DEBUGS("%i += %i '%.*s'", Term->CursorRow, Length, Length, UTF8Text);
197         while( Length > 0 )
198         {
199                 if( Term->CursorCol == Term->ViewCols ) {
200                         Display_Newline(Term, 1);
201                 }
202                 size_t used = Display_int_PushCharacter(Term, Length, UTF8Text);
203         
204                 Length -= used;
205                 UTF8Text += used;
206                 
207                 Term->CursorCol ++;
208         }
209 }
210
211 void Display_Newline(tTerminal *Term, bool bCarriageReturn)
212 {
213         // Going down!
214         if( Term->bUsingAltBuf )
215         {
216                 if( Term->CursorRow == Term->ScrollTop + Term->ScrollRows-1 ) {
217                         Display_ScrollDown(Term, 1);
218                 }
219                 else if( Term->CursorRow == Term->ViewRows-1 ) {
220                         if( Term->ScrollRows == 0 ) {
221                                 // Scroll entire buffer
222                                 Display_ScrollDown(Term, 1);
223                         }
224                         else {
225                                 // Don't go down a line
226                         }
227                 }
228                 else {
229                         // No scroll needed
230                         Term->CursorRow ++;
231                 }
232         }
233         else
234         {
235                 if( Term->CursorRow == Term->TotalLines-1 ) {
236                         Display_ScrollDown(Term, 1);
237                 }
238                 else {
239                         Term->CursorRow ++;
240                 }
241         }
242         
243         if( bCarriageReturn )
244         {
245                 Term->CursorByte = 0;
246                 Term->CursorCol = 0;
247         }
248         else
249         {
250                 tLine   *line = Display_int_GetCurLine(Term);
251                 
252                 Term->CursorByte = 0;
253                  int    old_col = Term->CursorCol;
254                 Term->CursorCol = 0;
255
256                 size_t  ofs = 0;
257                 while( Term->CursorCol < old_col && ofs < line->Len ) {
258                         ofs += _GetCharLength(line->Len-ofs, line->Data+ofs, NULL);
259                         Term->CursorCol ++;
260                 }
261                 Term->CursorByte = ofs;
262
263                 while( Term->CursorCol < old_col )
264                         Display_AddText(Term, 1, " ");
265         }
266 }
267
268 void Display_SetScrollArea(tTerminal *Term, int Start, int Count)
269 {
270         assert(Start >= 0);
271         assert(Count >= 0);
272         Term->ScrollTop = Start;
273         Term->ScrollRows = MIN(Count, Term->ViewRows - Start);
274 }
275
276 void Display_ScrollDown(tTerminal *Term, int Count)
277 {
278          int    top, max;
279         tLine   *buffer;
280         
281         if( Term->bUsingAltBuf )
282         {
283                 top = (Term->ScrollRows == 0 ? 0 : Term->ScrollTop);
284                 max = (Term->ScrollRows == 0 ? Term->ViewRows : Term->ScrollRows);
285                 buffer = Term->AltBuf;
286         }
287         else
288         {
289                 top = 0;
290                 max = Term->TotalLines;
291                 buffer = Term->PriBuf;
292         }
293         
294         assert(Count < max);
295         assert(Count > -max);
296
297         DEBUGS("Scroll %p %i-%i down by %i", buffer, top, max, Count);
298         
299         buffer += top;
300
301          int    clear_top, clear_max;
302         if( Count < 0 )
303         {
304                 // -ve: Scroll up, move buffer contents down
305                 Count = -Count;
306                 for( int i = max-Count; i < max; i ++ )
307                         free(buffer[i].Data);
308                 memmove(buffer+Count, buffer, (max-Count)*sizeof(*buffer));
309                 
310                 clear_top = 0;
311                 clear_max = Count;
312         }
313         else
314         {
315                 for( int i = 0; i < Count; i ++ )
316                         free(buffer[i].Data);
317                 memmove(buffer, buffer+Count, (max-Count)*sizeof(*buffer));
318                 clear_top = max-Count;
319                 clear_max = max;
320         }
321         // Clear exposed lines
322         for( int i = clear_top; i < clear_max; i ++ )
323         {
324                 buffer[i].Data = NULL;
325                 buffer[i].Len = 0;
326                 buffer[i].Size = 0;
327                 buffer[i].IsDirty = true;
328         }
329         // Send scroll command to GUI
330         AxWin3_RichText_ScrollRange(gMainWindow, top, max, Count);
331         
332         Display_int_SetCursor(Term, Term->CursorRow, Term->CursorCol);
333 }
334
335 void Display_SetCursor(tTerminal *Term, int Row, int Col)
336 {
337         assert(Row >= 0);
338         assert(Col >= 0);
339
340         DEBUGS("Set cursor R%i,C%i", Row, Col); 
341
342         if( !Term->bUsingAltBuf ) {
343                 _SysDebug("NOTE: Using \\e[%i;%iH outside of alternat buffer is undefined", Row, Col);
344         }
345         
346         // NOTE: This may be interesting outside of AltBuffer
347         Display_int_SetCursor(Term, Row, Col);  
348 }
349 void Display_int_SetCursor(tTerminal *Term, int Row, int Col)
350 {
351         Term->CursorRow = Row;
352         tLine   *line = Display_int_GetCurLine(Term);
353         size_t ofs = 0;
354          int    i;
355         for( i = 0; i < Col; i ++ )
356         {
357         
358                 size_t clen = _GetCharLength(line->Len-ofs, line->Data+ofs, NULL);
359                 if( clen == 0 ) {
360                         break;
361                 }
362                 ofs += clen;
363         }
364         Term->CursorCol = i;
365         Term->CursorByte = ofs;
366         // Move to exactly the column specified
367         for( ; i < Col; i ++ ) {
368                 Display_int_PushCharacter(Term, 1, " ");
369                 Term->CursorCol ++;
370         }
371 }
372
373 void Display_MoveCursor(tTerminal *Term, int RelRow, int RelCol)
374 {
375         if( RelRow != 0 )
376         {
377                 UNIMPLIMENTED();
378         }
379         
380         if( RelCol != 0 )
381         {
382                 int req_col = Term->CursorCol + RelCol;
383                 if( req_col < 0 )       req_col = 0;
384                 if( req_col > Term->ViewCols )  req_col = Term->ViewCols;
385
386                 tLine   *line = Display_int_GetCurLine(Term);
387                 size_t  ofs = 0;
388                 for( int i = 0; i < req_col; i ++ )
389                 {
390                         size_t clen = _GetCharLength(line->Len-ofs, line->Data+ofs, NULL);
391                         if( clen == 0 ) {
392                                 req_col = i;
393                                 break;
394                         }
395                         ofs += clen;
396                 }
397
398                 Term->CursorCol = req_col;
399                 Term->CursorByte = ofs;
400         }
401 }
402
403 void Display_SaveCursor(tTerminal *Term)
404 {
405         Term->SavedRow = Term->CursorRow;
406         Term->SavedCol = Term->CursorCol;
407 }
408 void Display_RestoreCursor(tTerminal *Term)
409 {
410         Display_SetCursor(Term, Term->SavedRow, Term->SavedCol);
411 }
412
413 void Display_ClearLine(tTerminal *Term, int Dir)        // 0: All, 1: Forward, -1: Reverse
414 {
415         if( Dir == 0 )
416         {
417                 tLine   *line = Display_int_GetCurLine(Term);
418                 // Completely clear line
419                 if( line->Data )
420                         free(line->Data);
421                 line->Data = NULL;
422                 line->IsDirty = true;
423                 Term->CursorCol = 0;
424                 Term->CursorByte = 0;
425         }
426         else if( Dir == 1 )
427         {
428                 // Forward clear (truncate)
429         }
430         else if( Dir == -1 )
431         {
432                 // Reverse clear (replace with spaces)
433         }
434         else
435         {
436                 // BUGCHECK
437         }
438 }
439
440 void Display_ClearLines(tTerminal *Term, int Dir)       // 0: All, 1: Forward, -1: Reverse
441 {
442         if( Dir == 0 )
443         {
444                 // Push giDisplayLines worth of empty lines
445                 // Move cursor back up by giDisplayLines
446         }
447         else if( Dir == 1 )
448         {
449                 // Push (giDisplayLines - (giCurrentLine-giFirstDispLine)) and reverse
450         }
451         else if( Dir == -1 )
452         {
453                 // Reverse clear (replace with spaces)
454         }
455         else
456         {
457                 // BUGCHECK
458         }
459 }
460
461 void Display_ResetAttributes(tTerminal *Term)
462 {
463         UNIMPLIMENTED();
464 }
465 void Display_SetForeground(tTerminal *Term, uint32_t RGB)
466 {
467         char    buf[7+1];
468         sprintf(buf, "\1%06x", RGB&0xFFFFFF);
469         Display_AddText(Term, 7, buf);
470 }
471
472 void Display_SetBackground(tTerminal *Term, uint32_t RGB)
473 {
474         char    buf[7+1];
475         sprintf(buf, "\2%06x", RGB&0xFFFFFF);
476         Display_AddText(Term, 7, buf);
477 }
478
479 void Display_Flush(tTerminal *Term)
480 {
481          int    viewOfs = (Term->bUsingAltBuf ? 0 : Term->ViewOffset);
482         tLine   *buffer = (Term->bUsingAltBuf ? Term->AltBuf : Term->PriBuf );
483         AxWin3_RichText_SetLineCount(gMainWindow, (Term->bUsingAltBuf ? Term->ViewRows : Term->TotalLines));
484         for( int i = 0; i < Term->ViewRows; i ++ )
485         {
486                  int    line = (viewOfs + i) % Term->TotalLines;
487                 tLine   *lineptr = &buffer[line];
488                 // Swapping buffers should cause a full resend
489                 if( !Term->bHaveSwappedBuffers && !lineptr->IsDirty )
490                         continue;
491                 DEBUGS("Line %i+%i %p '%.*s'", viewOfs, i, lineptr->Data, lineptr->Len, lineptr->Data);
492                 AxWin3_RichText_SendLine(gMainWindow, viewOfs + i,
493                         lineptr->Data ? lineptr->Data : "" );
494                 lineptr->IsDirty = false;
495         }
496         AxWin3_RichText_SetCursorPos(gMainWindow, Term->CursorRow, Term->CursorCol);
497         Term->bHaveSwappedBuffers = false;
498 }
499
500 void Display_ShowAltBuffer(tTerminal *Term, bool AltBufEnabled)
501 {
502         if( Term->bUsingAltBuf == AltBufEnabled )
503         {
504                 // Nothing to do, so do nothing
505                 return ;
506         }
507
508         int row = Term->OtherBufRow;
509         int col = Term->OtherBufCol;
510         Term->OtherBufRow = Term->CursorRow;
511         Term->OtherBufCol = Term->CursorCol;
512         
513         Term->bUsingAltBuf = AltBufEnabled;
514         Term->bHaveSwappedBuffers = true;
515         if( AltBufEnabled )
516         {
517                 if( !Term->AltBuf )
518                 {
519                         Term->AltBuf = calloc( sizeof(Term->AltBuf[0]), Term->ViewRows );
520                 }
521                 AxWin3_RichText_SetLineCount(gMainWindow, Term->ViewRows);
522         }
523         else
524         {
525                 AxWin3_RichText_SetLineCount(gMainWindow, Term->TotalLines);
526         }
527         Display_int_SetCursor(Term, row, col);
528 }
529

UCC git Repository :: git.ucc.asn.au