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

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