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

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