Merge branch 'master' of git://git.ucc.asn.au/tpg/acess2
[tpg/acess2.git] / Usermode / Applications / axwin3_src / WM / renderers / richtext.c
1 /*
2  * Acess2 Window Manager v3
3  * - By John Hodge (thePowersGang)
4  *
5  * render/richtext.c
6  * - Formatted Line Editor
7  */
8 #include <common.h>
9 #include <wm_renderer.h>
10 #include <wm_messages.h>
11 #include <richtext_messages.h>
12 #include <stdio.h>      // sscanf
13 #include <string.h>     // memcpy
14 #include <unicode.h>    // ReadUTF8
15 #include <axwin3/keysyms.h>
16 #include <axwin3/richtext.h>
17
18 #define LINES_PER_BLOCK 30
19 #define LINE_SPACE_UNIT 32      // Must be a power of two
20
21 // === TYPES ===
22 typedef struct sRichText_Line
23 {
24         struct sRichText_Line   *Next;
25         struct sRichText_Line   *Prev;
26          int    Num;
27         char    bIsClean;
28         // TODO: Pre-rendered cache?
29         short   ByteLength;
30         short   Space;
31         char    Data[];
32 } tRichText_Line;
33 typedef struct sRichText_Window
34 {
35          int    DispLines, DispCols;
36          int    FirstVisRow, FirstVisCol;
37          int    nLines, nCols;
38          int    CursorRow, CursorCol;
39         tRichText_Line  *FirstLine;
40         tRichText_Line  *FirstVisLine;
41         
42         tColour DefaultFG;
43         tColour DefaultBG;
44         tFont   *Font;
45          int    Flags;
46         
47         char    bNeedsFullRedraw;
48         
49         tRichText_Line  *CursorLine;
50          int    CursorBytePos;  // Recalculated on cursor update
51         
52         short   LineHeight;
53 } tRichText_Window;
54
55 // === PROTOTYPES ===
56  int    Renderer_RichText_Init(void);
57 tWindow *Renderer_RichText_Create(int Flags);
58 void    Renderer_RichText_Destroy(tWindow *Window);
59 void    Renderer_RichText_Redraw(tWindow *Window);
60  int    Renderer_RichText_HandleIPC_SetAttr(tWindow *Window, size_t Len, const void *Data);
61  int    Renderer_RichText_HandleIPC_WriteLine(tWindow *Window, size_t Len, const void *Data);
62  int    Renderer_RichText_HandleIPC_ScrollRange(tWindow *Window, size_t Len, const void *Data);
63  int    Renderer_RichText_HandleMessage(tWindow *Target, int Msg, int Len, const void *Data);
64
65 // === GLOBALS ===
66 tWMRenderer     gRenderer_RichText = {
67         .Name = "RichText",
68         .CreateWindow   = Renderer_RichText_Create,
69         .DestroyWindow  = Renderer_RichText_Destroy,
70         .Redraw         = Renderer_RichText_Redraw,
71         .HandleMessage  = Renderer_RichText_HandleMessage,
72         .nIPCHandlers = N_IPC_RICHTEXT,
73         .IPCHandlers = {
74                 [IPC_RICHTEXT_SETATTR] = Renderer_RichText_HandleIPC_SetAttr,
75                 [IPC_RICHTEXT_WRITELINE] = Renderer_RichText_HandleIPC_WriteLine,
76                 [IPC_RICHTEXT_SCROLLRANGE] = Renderer_RichText_HandleIPC_ScrollRange
77         }
78 };
79
80 // === CODE ===
81 int Renderer_RichText_Init(void)
82 {
83         WM_RegisterRenderer(&gRenderer_RichText);       
84         return 0;
85 }
86
87 tWindow *Renderer_RichText_Create(int Flags)
88 {
89         tRichText_Window        *info;
90         tWindow *ret = WM_CreateWindowStruct( sizeof(*info) );
91         if(!ret)        return NULL;
92         info = ret->RendererInfo;
93         info->Flags = Flags;
94         
95         // Initialise font (get an idea of dimensions)
96         int h;
97         WM_Render_GetTextDims(NULL, "yY!", 3, NULL, &h);
98         info->LineHeight = h;
99         
100         return ret;
101 }
102
103 void Renderer_RichText_Destroy(tWindow *Window)
104 {
105         tRichText_Window        *info = Window->RendererInfo;
106
107         // TODO: Is locking needed? WM_Destroy should have taken us off the render tree
108         while( info->FirstLine )
109         {
110                 tRichText_Line *line = info->FirstLine;
111                 info->FirstLine = line->Next;
112
113                 free(line);
114         }
115
116         if( info->Font )
117                 _SysDebug("RichText_Destroy - TODO: Free font");
118 }
119
120 static inline int Renderer_RichText_RenderText_Act(tWindow *Window, tRichText_Window *info, int X, int Row, const char *Text, int Bytes, tColour FG, tColour BG, int Flags)
121 {
122          int    rwidth;
123         // TODO: Fill only what is needed? What about the rest of the line?
124         WM_Render_DrawRect(Window, X, Row*info->LineHeight,
125                 Window->W - X, info->LineHeight,
126                 BG
127                 );
128         // TODO: Bold, Italic, Underline
129         rwidth = WM_Render_DrawText(Window,
130                 X, Row*info->LineHeight,
131                 Window->W - X, info->LineHeight,
132                 info->Font, FG,
133                 Text, Bytes
134                 );
135         return rwidth;
136 }
137
138 void Renderer_RichText_RenderText(tWindow *Window, int LineNum, tRichText_Line *Line)
139 {
140         const char      *Text = Line->Data;
141         tRichText_Window        *info = Window->RendererInfo;
142         tColour fg = info->DefaultFG;
143         tColour bg = info->DefaultBG;
144          int    flagset = 0;
145          int    bRender = 0;
146          int    curx = 0;
147         const char      *oldtext = Text;
148         
149         for( int i = 0; curx < Window->W && Text < oldtext + Line->ByteLength; i ++ )
150         {
151                 char    ch, flags;
152                  int    len;
153
154                 if( i == info->FirstVisCol )
155                         bRender = 1;
156
157                 ch = *Text++;
158                 if( ch == 0 )   break;
159
160                 // Not an escape - move along
161                 if( ch > 4 )
162                         continue ;              
163
164                 if( bRender ) {
165                         // Render previous characters
166                         curx += Renderer_RichText_RenderText_Act(Window, info, curx, LineNum,
167                                 oldtext, Text - oldtext - 1, fg, bg, flagset);
168                         if( curx >= Window->W )
169                                 break;
170                 }
171                 oldtext = Text;
172                 switch(ch)
173                 {
174                 case 1: // FG Select (\1 RRGGBB)
175                         if( sscanf(Text, "%6x%n", &fg, &len) != 1 || len != 6 ) {
176                                 // Bad client
177                                 _SysDebug("foreground scanf failed - len=%i", len);
178                                 len = 0;
179                         }
180                         Text += len;
181                         oldtext = Text;
182                         _SysDebug("FG update to %x", fg);
183                         break ;
184                 case 2: // BG Select (\2 RRGGBB)
185                         if( sscanf(Text, "%6x%n", &bg, &len) != 1 || len != 6 ) {
186                                 // Bad client
187                                 _SysDebug("background scanf failed - len=%i", len);
188                                 len = 0;
189                         }
190                         Text += len;
191                         oldtext = Text;
192                         _SysDebug("BG update to %x", bg);
193                         break ;
194                 case 3: // Flagset (0,it,uline,bold)
195                         if( sscanf(Text, "%1hhx%n", &flags, &len) != 1 || len != 1 ) {
196                                 // Bad client
197                                 _SysDebug("Flagset scanf failed - len=%i", len);
198                         }
199                         flagset = flags & 7;
200                         Text += len;
201                         oldtext = Text;
202                         break ;
203                 case 4: // Escape (do nothing)
204                         Text ++;
205                         // NOTE: No update to oldtext
206                         break;
207                 default: // Error.
208                         break;
209                 }
210         }
211         curx += Renderer_RichText_RenderText_Act(Window, info, curx,
212                 LineNum, oldtext, Text - oldtext, fg, bg, flagset);
213         WM_Render_DrawRect(Window, curx, LineNum * info->LineHeight,
214                 Window->W - curx, info->LineHeight, info->DefaultBG);
215 }
216
217 void Renderer_RichText_Redraw(tWindow *Window)
218 {
219         tRichText_Window        *info = Window->RendererInfo;
220         tRichText_Line  *line = info->FirstVisLine;
221         
222         if( !line ) {
223                 line = info->FirstLine;
224                 while(line && line->Num < info->FirstVisRow )
225                         line = line->Next;
226                 info->FirstVisLine = line;
227         }
228         while( line && line->Prev && line->Prev->Num > info->FirstVisRow )
229                 line = line->Prev;
230
231          int    i;
232         for( i = 0; i < info->DispLines && line; i ++ )
233         {
234                 if( i >= info->nLines - info->FirstVisRow )
235                         break;
236                 // Empty line is noted by a discontinuity
237                 if( line->Num > info->FirstVisRow + i ) {
238                         // Clear line if window needs full redraw
239                         if( info->bNeedsFullRedraw ) {
240                                 WM_Render_FillRect(Window,
241                                         0, i*info->LineHeight,
242                                         Window->W, info->LineHeight,
243                                         info->DefaultBG
244                                         );
245                         }
246                         else {
247                                 // Hack to clear cursor on NULL lines
248                                 WM_Render_FillRect(Window,
249                                         0, i*info->LineHeight,
250                                         1, info->LineHeight,
251                                         info->DefaultBG
252                                         );
253                         }
254                         continue ;
255                 }
256
257                 if( info->bNeedsFullRedraw || !line->bIsClean )
258                 {
259                         WM_Render_FillRect(Window,
260                                 0, i*info->LineHeight,
261                                 Window->W, info->LineHeight,
262                                 info->DefaultBG
263                                 );
264                         
265                         // Formatted text out
266                         Renderer_RichText_RenderText(Window, i, line);
267                         _SysDebug("RichText: %p - Render %i '%.*s'", Window,
268                                 line->Num, line->ByteLength, line->Data);
269                         line->bIsClean = 1;
270                 }
271
272                 line = line->Next;
273         }
274         // Clear out lines i to info->DispLines-1
275         if( info->bNeedsFullRedraw )
276         {
277                 _SysDebug("RichText: %p - Clear %i px lines with %06x starting at %i",
278                         Window, (info->DispLines-i)*info->LineHeight, info->DefaultBG, i*info->LineHeight);
279                 WM_Render_FillRect(Window,
280                         0, i*info->LineHeight,
281                         Window->W, (info->DispLines-i)*info->LineHeight,
282                         info->DefaultBG
283                         );
284         }
285         info->bNeedsFullRedraw = 0;
286
287         // HACK: Hardcoded text width of 8
288         info->DispCols = Window->W / 8; 
289
290         // Text cursor
291         _SysDebug("Cursor at %i,%i", info->CursorCol, info->CursorRow);
292         _SysDebug(" Range [%i+%i],[%i+%i]", info->FirstVisRow, info->DispLines, info->FirstVisCol, info->DispCols);
293         if( info->CursorRow >= info->FirstVisRow && info->CursorRow < info->FirstVisRow + info->DispLines
294          && info->CursorCol >= info->FirstVisCol && info->CursorCol < info->FirstVisCol + info->DispCols )
295         {
296                 // TODO: Kill hardcoded 8 with cached text distance
297                 WM_Render_FillRect(Window,
298                         (info->CursorCol - info->FirstVisCol) * 8,
299                         (info->CursorRow - info->FirstVisRow) * info->LineHeight,
300                         1,
301                         info->LineHeight, info->DefaultFG
302                         );
303         }
304 }
305
306 tRichText_Line *Renderer_RichText_int_GetLine(tWindow *Window, int LineNum, tRichText_Line **Prev)
307 {
308         tRichText_Window        *info = Window->RendererInfo;
309         tRichText_Line  *line = info->FirstLine;
310         tRichText_Line  *prev = NULL;
311         while(line && line->Num < LineNum)
312                 prev = line, line = line->Next;
313         
314         if( Prev )
315                 *Prev = prev;
316         
317         if( !line || line->Num > LineNum )
318                 return NULL;
319         return line;
320 }
321
322 void Renderer_RichText_int_UpdateCursorOfs(tRichText_Window *Info)
323 {
324         tRichText_Line  *line = Info->CursorLine;
325         size_t  ofs = 0;
326         if( line )
327         {
328                 for( int i = 0; i < Info->CursorCol && ofs < line->ByteLength; i ++ )
329                 {
330                         ofs += ReadUTF8(line->Data + ofs, NULL);
331                 }
332         }
333         Info->CursorBytePos = ofs;
334 }
335
336 int Renderer_RichText_HandleIPC_SetAttr(tWindow *Window, size_t Len, const void *Data)
337 {
338         tRichText_Window        *info = Window->RendererInfo;
339         const struct sRichTextIPC_SetAttr *msg = Data;
340         if(Len < sizeof(*msg))  return -1;
341
342         _SysDebug("RichText Attr %i set to %x", msg->Attr, msg->Value);
343         switch(msg->Attr)
344         {
345         case _ATTR_DEFBG:
346                 info->DefaultBG = msg->Value;
347                 break;
348         case _ATTR_DEFFG:
349                 info->DefaultFG = msg->Value;
350                 break;
351         case _ATTR_CURSORPOS: {
352                  int    newRow = msg->Value >> 12;
353                  int    newCol = msg->Value & 0xFFF;
354                 // Force redraw of old and new row
355                 tRichText_Line  *line = Renderer_RichText_int_GetLine(Window, info->CursorRow, NULL);
356                 if( line )
357                         line->bIsClean = 0;
358                 if( newRow != info->CursorRow ) {
359                         line = Renderer_RichText_int_GetLine(Window, newRow, NULL);
360                         if(line)
361                                 line->bIsClean = 0;
362                 }
363                 info->CursorRow = newRow;
364                 info->CursorCol = newCol;
365                 info->CursorLine = line;
366                 Renderer_RichText_int_UpdateCursorOfs(info);
367                 WM_Invalidate(Window, 1);
368                 break; }
369         case _ATTR_SCROLL:
370                 // TODO: Set scroll flag
371                 break;
372         case _ATTR_LINECOUNT:
373                 info->nLines = msg->Value;
374                 break;
375         }
376         
377         return 0;
378 }
379
380 int Renderer_RichText_HandleIPC_WriteLine(tWindow *Window, size_t Len, const void *Data)
381 {
382         tRichText_Window        *info = Window->RendererInfo;
383         const struct sRichTextIPC_WriteLine     *msg = Data;
384         if( Len < sizeof(*msg) )        return -1;
385         if( msg->Line >= info->nLines ) return 1;       // Bad count
386
387         tRichText_Line  *prev = NULL;
388         tRichText_Line  *line = Renderer_RichText_int_GetLine(Window, msg->Line, &prev);
389         size_t  data_bytes = Len - sizeof(*msg);
390          int    reqspace = (data_bytes + LINE_SPACE_UNIT-1) & ~(LINE_SPACE_UNIT-1);
391         tRichText_Line  *new = NULL;
392         if( !line )
393         {
394                 // New line!
395                 new = malloc(sizeof(*line) + reqspace);
396                 new->Next = (prev ? prev->Next : NULL);
397                 new->Prev = prev;
398                 new->Num = msg->Line;
399         }
400         else if( line->Space < reqspace )
401         {
402                 // Need to allocate more space
403                 new = realloc(line, sizeof(*line) + reqspace);
404         }
405         else
406         {
407                 // It fits :)
408         }
409         if( new ) 
410         {
411                 // TODO: Bookkeeping on how much memory each window uses
412                 new->Space = reqspace;
413                 if(new->Prev)   new->Prev->Next = new;
414                 else    info->FirstLine = new;
415                 if(new->Next)   new->Next->Prev = new;
416                 line = new;
417         }
418         line->ByteLength = data_bytes - 1;
419         memcpy(line->Data, msg->LineData, data_bytes);
420         line->bIsClean = 0;
421
422         _SysDebug("RichText: %p - Write %i %i'%.*s'", Window, line->Num, line->ByteLength,
423                 line->ByteLength, line->Data);
424
425         if( line->Num == info->CursorRow ) {
426                 info->CursorLine = line;
427                 info->CursorBytePos = MIN(info->CursorBytePos, line->ByteLength);
428         }
429
430 //      WM_Invalidate(Window, 1);
431
432         return 0;
433 }
434
435 int Renderer_RichText_HandleIPC_ScrollRange(tWindow *Window, size_t Len, const void *Data)
436 {
437         tRichText_Window        *info = Window->RendererInfo;
438         const struct sRichTextIPC_ScrollRange   *msg = Data;
439         if( Len < sizeof(*msg) )        return -1;
440         
441         if( msg->First >= info->nLines )
442                 return 1;       // Bad start
443         if( msg->Count > info->nLines - msg->First )
444                 return 1;       // Bad count
445         if( msg->Count == 0 ) {
446                 // No-op
447                 return 0;
448         }
449
450         // Find the range start
451         tRichText_Line *line = info->FirstLine;
452         tRichText_Line *prev = NULL;
453         while( line && line->Num < msg->First ) {
454                 prev = line;
455                 line = line->Next;
456         }
457         if( !line ) {
458                 _SysDebug("RichText ScrollRange: Search for %i ran off end, nlines=%i",
459                         msg->First, info->nLines);
460         }
461                 
462         if( msg->Count < 0 )
463         {
464                 _SysDebug("TODO: RichText ScrollRange -ve");
465         }
466         else
467         {
468                 if( msg->First <= info->FirstVisRow && info->FirstVisRow < msg->First+msg->Range  )
469                         info->FirstVisLine = NULL;
470         
471                 // Remove 'msg->Count' lines from beginning of the range, ...
472                 while( line && line->Num < msg->First + msg->Count )
473                 {
474                         tRichText_Line *next = line->Next;
475                         _SysDebug("- RichText ScrollRange: Remove %i '%.*s'",
476                                 line->Num, line->ByteLength, line->Data);
477                         free(line);
478                         line = next;
479                 }
480                 // Fix up list
481                 if( prev )
482                         prev->Next = line;
483                 else
484                         info->FirstLine = line;
485                 if(line)
486                         line->Prev = prev;
487                 // ... and shift ->Num down for the rest
488                 for( ; line && line->Num < msg->First + msg->Range; line = line->Next )
489                 {
490                         line->Num -= msg->Count;
491                         if( line->Num >= info->FirstVisRow && !info->FirstVisLine )
492                                 info->FirstVisLine = line;
493                 }
494                 
495                 info->nLines -= msg->Count;
496         }
497
498         info->bNeedsFullRedraw = 1;
499
500         return 0;
501 }
502
503 void Renderer_RichText_HandleKeyFire(tWindow *Window, tRichText_Window *Info, const struct sWndMsg_KeyAction *Msg)
504 {
505         tRichText_Line  *line = Info->CursorLine;
506         size_t  len = WriteUTF8(NULL, Msg->UCS32);
507         switch(Msg->UCS32)
508         {
509         case 0:
510                 switch(Msg->KeySym)
511                 {
512                 case KEYSYM_RIGHTARROW:
513                         if( Info->CursorBytePos == line->ByteLength )
514                                 break;
515                         Info->CursorBytePos += ReadUTF8(line->Data + Info->CursorBytePos, NULL);
516                         Info->CursorCol ++;
517                         break;
518                 case KEYSYM_LEFTARROW:
519                         if( Info->CursorBytePos == 0 )
520                                 break;
521                         Info->CursorBytePos -= ReadUTF8Rev(line->Data, Info->CursorBytePos, NULL);
522                         Info->CursorCol --;
523                         break;
524                 case KEYSYM_UPARROW:
525                         _SysDebug("TODO: RichText edit up line");
526                         break;
527                 case KEYSYM_DOWNARROW:
528                         _SysDebug("TODO: RichText edit down line");
529                         break;
530                 default:
531                         // No effect
532                         return ;
533                 }
534                 break;
535         case '\n':      // Newline
536                 _SysDebug("TODO: RichText edit newline");
537                 break;
538         case '\b':      // Backspace
539                 if( Info->CursorBytePos == 0 )
540                         return ;
541                 len = ReadUTF8Rev(line->Data, Info->CursorBytePos, NULL);
542                 Info->CursorBytePos -= len;
543                 Info->CursorCol --;
544                 if(0)
545         case '\x7f':    // Delete
546                 len = ReadUTF8(line->Data + Info->CursorBytePos, NULL);
547                 if( Info->CursorBytePos == line->ByteLength )
548                         return ;
549                 memmove(line->Data + Info->CursorBytePos, line->Data + Info->CursorBytePos + len,
550                         line->ByteLength - Info->CursorBytePos - len);
551                 line->ByteLength -= len;
552                 _SysDebug("RichText: %p Backspace/Delete '%.*s'", Window,
553                         line->ByteLength, line->Data);
554                 break;
555         default:
556                 // Increase buffer size
557                 if( line->ByteLength + len > line->Space ) {
558                         line->Space += LINE_SPACE_UNIT;
559                         tRichText_Line *nl = realloc(line, sizeof(*line) + line->Space);
560                         if( nl == NULL )
561                                 return ;
562                         if( nl != line ) {
563                                 *(line->Prev ? &line->Prev->Next : &Info->FirstLine) = nl;
564                                 if(line->Next)
565                                         line->Next->Prev = nl;
566                                 if(Info->FirstVisLine == line)
567                                         Info->FirstVisLine = nl;
568                                 Info->CursorLine = nl;
569                                 line = nl;
570                         }
571                 }
572                 // Shift data
573                 memmove(line->Data + Info->CursorBytePos + len, line->Data + Info->CursorBytePos,
574                         line->ByteLength - Info->CursorBytePos);
575                 // Encode
576                 WriteUTF8(line->Data + Info->CursorBytePos, Msg->UCS32);
577                 Info->CursorBytePos += len;
578                 Info->CursorCol ++;
579                 line->ByteLength += len;
580                 
581                 _SysDebug("RichText: %p Appended %X '%.*s' to line %i", Window,
582                         Msg->UCS32, len, line->Data + Info->CursorBytePos - len,
583                         line->Num);
584                 break;
585         }
586         // Invalidate line
587         line->bIsClean = 0;
588         WM_Invalidate(Window, 1);
589 }
590
591 int Renderer_RichText_HandleMessage(tWindow *Target, int Msg, int Len, const void *Data)
592 {
593         tRichText_Window        *info = Target->RendererInfo;
594         switch(Msg)
595         {
596         case WNDMSG_RESIZE: {
597                 const struct sWndMsg_Resize *msg = Data;
598                 if(Len < sizeof(*msg))  return -1;
599                 info->DispLines = msg->H / info->LineHeight;
600                 info->bNeedsFullRedraw = 1;     // force full rerender
601                 return 1; }
602         case WNDMSG_KEYFIRE:
603                 if( Len < sizeof(struct sWndMsg_KeyAction) )
604                         return -1;
605                 if( !(info->Flags & AXWIN3_RICHTEXT_READONLY) )
606                 {
607                         Renderer_RichText_HandleKeyFire(Target, info, Data);
608                 }
609                 return 1;
610         case WNDMSG_KEYDOWN:
611         case WNDMSG_KEYUP:
612                 return 1;
613         }
614         return 0;
615 }

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