Usermode/AxWin3 - Slight cleanup in richtext code
[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_HandleMessage(tWindow *Target, int Msg, int Len, const void *Data);
63
64 // === GLOBALS ===
65 tWMRenderer     gRenderer_RichText = {
66         .Name = "RichText",
67         .CreateWindow   = Renderer_RichText_Create,
68         .DestroyWindow  = Renderer_RichText_Destroy,
69         .Redraw         = Renderer_RichText_Redraw,
70         .HandleMessage  = Renderer_RichText_HandleMessage,
71         .nIPCHandlers = N_IPC_RICHTEXT,
72         .IPCHandlers = {
73                 [IPC_RICHTEXT_SETATTR] = Renderer_RichText_HandleIPC_SetAttr,
74                 [IPC_RICHTEXT_WRITELINE] = Renderer_RichText_HandleIPC_WriteLine
75         }
76 };
77
78 // === CODE ===
79 int Renderer_RichText_Init(void)
80 {
81         WM_RegisterRenderer(&gRenderer_RichText);       
82         return 0;
83 }
84
85 tWindow *Renderer_RichText_Create(int Flags)
86 {
87         tRichText_Window        *info;
88         tWindow *ret = WM_CreateWindowStruct( sizeof(*info) );
89         if(!ret)        return NULL;
90         info = ret->RendererInfo;
91         info->Flags = Flags;
92         
93         // Initialise font (get an idea of dimensions)
94         int h;
95         WM_Render_GetTextDims(NULL, "yY!", 3, NULL, &h);
96         info->LineHeight = h;
97         
98         return ret;
99 }
100
101 void Renderer_RichText_Destroy(tWindow *Window)
102 {
103         tRichText_Window        *info = Window->RendererInfo;
104
105         // TODO: Is locking needed? WM_Destroy should have taken us off the render tree
106         while( info->FirstLine )
107         {
108                 tRichText_Line *line = info->FirstLine;
109                 info->FirstLine = line->Next;
110
111                 free(line);
112         }
113
114         if( info->Font )
115                 _SysDebug("RichText_Destroy - TODO: Free font");
116 }
117
118 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)
119 {
120          int    rwidth;
121         // TODO: Fill only what is needed? What about the rest of the line?
122         WM_Render_DrawRect(Window, X, Row*info->LineHeight,
123                 Window->W - X, info->LineHeight,
124                 BG
125                 );
126         // TODO: Bold, Italic, Underline
127         rwidth = WM_Render_DrawText(Window,
128                 X, Row*info->LineHeight,
129                 Window->W - X, info->LineHeight,
130                 info->Font, FG,
131                 Text, Bytes
132                 );
133         return rwidth;
134 }
135
136 void Renderer_RichText_RenderText(tWindow *Window, int LineNum, tRichText_Line *Line)
137 {
138         const char      *Text = Line->Data;
139         tRichText_Window        *info = Window->RendererInfo;
140         tColour fg = info->DefaultFG;
141         tColour bg = info->DefaultBG;
142          int    flagset = 0;
143          int    bRender = 0;
144          int    curx = 0;
145         const char      *oldtext = Text;
146         
147         for( int i = 0; curx < Window->W && Text < oldtext + Line->ByteLength; i ++ )
148         {
149                 char    ch, flags;
150                  int    len;
151
152                 if( i == info->FirstVisCol )
153                         bRender = 1;
154
155                 ch = *Text++;
156                 if( ch == 0 )   break;
157
158                 // Not an escape - move along
159                 if( ch > 4 )
160                         continue ;              
161
162                 if( bRender ) {
163                         // Render previous characters
164                         curx += Renderer_RichText_RenderText_Act(Window, info, curx, LineNum,
165                                 oldtext, Text - oldtext - 1, fg, bg, flagset);
166                         if( curx >= Window->W )
167                                 break;
168                 }
169                 oldtext = Text;
170                 switch(ch)
171                 {
172                 case 1: // FG Select (\1 RRGGBB)
173                         if( sscanf(Text, "%6x%n", &fg, &len) != 1 || len != 6 ) {
174                                 // Bad client
175                                 _SysDebug("foreground scanf failed - len=%i", len);
176                                 len = 0;
177                         }
178                         Text += len;
179                         oldtext = Text;
180                         _SysDebug("FG update to %x", fg);
181                         break ;
182                 case 2: // BG Select (\2 RRGGBB)
183                         if( sscanf(Text, "%6x%n", &bg, &len) != 1 || len != 6 ) {
184                                 // Bad client
185                                 _SysDebug("background scanf failed - len=%i", len);
186                                 len = 0;
187                         }
188                         Text += len;
189                         oldtext = Text;
190                         _SysDebug("BG update to %x", bg);
191                         break ;
192                 case 3: // Flagset (0,it,uline,bold)
193                         if( sscanf(Text, "%1hhx%n", &flags, &len) != 1 || len != 1 ) {
194                                 // Bad client
195                                 _SysDebug("Flagset scanf failed - len=%i", len);
196                         }
197                         flagset = flags & 7;
198                         Text += len;
199                         oldtext = Text;
200                         break ;
201                 case 4: // Escape (do nothing)
202                         Text ++;
203                         // NOTE: No update to oldtext
204                         break;
205                 default: // Error.
206                         break;
207                 }
208         }
209         curx += Renderer_RichText_RenderText_Act(Window, info, curx,
210                 LineNum, oldtext, Text - oldtext, fg, bg, flagset);
211         WM_Render_DrawRect(Window, curx, LineNum * info->LineHeight,
212                 Window->W - curx, info->LineHeight, info->DefaultBG);
213 }
214
215 void Renderer_RichText_Redraw(tWindow *Window)
216 {
217         tRichText_Window        *info = Window->RendererInfo;
218         tRichText_Line  *line = info->FirstVisLine;
219         
220         if( !line ) {
221                 line = info->FirstLine;
222                 while(line && line->Num < info->FirstVisRow )
223                         line = line->Next;
224                 info->FirstVisLine = line;
225         }
226         while( line && line->Prev && line->Prev->Num > info->FirstVisRow )
227                 line = line->Prev;
228
229          int    i;
230         for( i = 0; i < info->DispLines && line; i ++ )
231         {
232                 if( i >= info->nLines - info->FirstVisRow )
233                         break;
234                 // Empty line is noted by a discontinuity
235                 if( line->Num > info->FirstVisRow + i ) {
236                         // Clear line if window needs full redraw
237                         if( info->bNeedsFullRedraw ) {
238                                 WM_Render_FillRect(Window,
239                                         0, i*info->LineHeight,
240                                         Window->W, info->LineHeight,
241                                         info->DefaultBG
242                                         );
243                         }
244                         else {
245                                 // Hack to clear cursor on NULL lines
246                                 WM_Render_FillRect(Window,
247                                         0, i*info->LineHeight,
248                                         1, info->LineHeight,
249                                         info->DefaultBG
250                                         );
251                         }
252                         continue ;
253                 }
254
255                 if( info->bNeedsFullRedraw || !line->bIsClean )
256                 {
257                         WM_Render_FillRect(Window,
258                                 0, i*info->LineHeight,
259                                 Window->W, info->LineHeight,
260                                 info->DefaultBG
261                                 );
262                         
263                         // Formatted text out
264                         Renderer_RichText_RenderText(Window, i, line);
265                         _SysDebug("RichText: %p - Render %i '%.*s'", Window,
266                                 line->Num, line->ByteLength, line->Data);
267                         line->bIsClean = 1;
268                 }
269
270                 line = line->Next;
271         }
272         // Clear out lines i to info->DispLines-1
273         if( info->bNeedsFullRedraw )
274         {
275                 _SysDebug("RichText: %p - Clear %i px lines with %06x starting at %i",
276                         Window, (info->DispLines-i)*info->LineHeight, info->DefaultBG, i*info->LineHeight);
277                 WM_Render_FillRect(Window,
278                         0, i*info->LineHeight,
279                         Window->W, (info->DispLines-i)*info->LineHeight,
280                         info->DefaultBG
281                         );
282         }
283         info->bNeedsFullRedraw = 0;
284
285         // HACK: Hardcoded text width of 8
286         info->DispCols = Window->W / 8; 
287
288         // Text cursor
289         _SysDebug("Cursor at %i,%i", info->CursorCol, info->CursorRow);
290         _SysDebug(" Range [%i+%i],[%i+%i]", info->FirstVisRow, info->DispLines, info->FirstVisCol, info->DispCols);
291         if( info->CursorRow >= info->FirstVisRow && info->CursorRow < info->FirstVisRow + info->DispLines
292          && info->CursorCol >= info->FirstVisCol && info->CursorCol < info->FirstVisCol + info->DispCols )
293         {
294                 // TODO: Kill hardcoded 8 with cached text distance
295                 WM_Render_FillRect(Window,
296                         (info->CursorCol - info->FirstVisCol) * 8,
297                         (info->CursorRow - info->FirstVisRow) * info->LineHeight,
298                         1,
299                         info->LineHeight, info->DefaultFG
300                         );
301         }
302 }
303
304 tRichText_Line *Renderer_RichText_int_GetLine(tWindow *Window, int LineNum, tRichText_Line **Prev)
305 {
306         tRichText_Window        *info = Window->RendererInfo;
307         tRichText_Line  *line = info->FirstLine;
308         tRichText_Line  *prev = NULL;
309         while(line && line->Num < LineNum)
310                 prev = line, line = line->Next;
311         
312         if( Prev )
313                 *Prev = prev;
314         
315         if( !line || line->Num > LineNum )
316                 return NULL;
317         return line;
318 }
319
320 void Renderer_RichText_int_UpdateCursorOfs(tRichText_Window *Info)
321 {
322         tRichText_Line  *line = Info->CursorLine;
323         size_t  ofs = 0;
324         for( int i = 0; i < Info->CursorCol && ofs < line->ByteLength; i ++ )
325         {
326                 ofs += ReadUTF8(line->Data + ofs, NULL);
327         }
328         Info->CursorBytePos = ofs;
329 }
330
331 int Renderer_RichText_HandleIPC_SetAttr(tWindow *Window, size_t Len, const void *Data)
332 {
333         tRichText_Window        *info = Window->RendererInfo;
334         const struct sRichTextIPC_SetAttr *msg = Data;
335         if(Len < sizeof(*msg))  return -1;
336
337         _SysDebug("RichText Attr %i set to %x", msg->Attr, msg->Value);
338         switch(msg->Attr)
339         {
340         case _ATTR_DEFBG:
341                 info->DefaultBG = msg->Value;
342                 break;
343         case _ATTR_DEFFG:
344                 info->DefaultFG = msg->Value;
345                 break;
346         case _ATTR_CURSORPOS: {
347                  int    newRow = msg->Value >> 12;
348                  int    newCol = msg->Value & 0xFFF;
349                 // Force redraw of old and new row
350                 tRichText_Line  *line = Renderer_RichText_int_GetLine(Window, info->CursorRow, NULL);
351                 if( line )
352                         line->bIsClean = 0;
353                 if( newRow != info->CursorRow ) {
354                         line = Renderer_RichText_int_GetLine(Window, newRow, NULL);
355                         if(line)
356                                 line->bIsClean = 0;
357                 }
358                 info->CursorRow = newRow;
359                 info->CursorCol = newCol;
360                 info->CursorLine = line;
361                 Renderer_RichText_int_UpdateCursorOfs(info);
362                 WM_Invalidate(Window, 1);
363                 break; }
364         case _ATTR_SCROLL:
365                 // TODO: Set scroll flag
366                 break;
367         case _ATTR_LINECOUNT:
368                 info->nLines = msg->Value;
369                 break;
370         }
371         
372         return 0;
373 }
374
375 int Renderer_RichText_HandleIPC_WriteLine(tWindow *Window, size_t Len, const void *Data)
376 {
377         tRichText_Window        *info = Window->RendererInfo;
378         const struct sRichTextIPC_WriteLine     *msg = Data;
379         if( Len < sizeof(*msg) )        return -1;
380         if( msg->Line >= info->nLines ) return 1;       // Bad count
381
382         tRichText_Line  *prev = NULL;
383         tRichText_Line  *line = Renderer_RichText_int_GetLine(Window, msg->Line, &prev);
384          int    reqspace = ((Len - sizeof(*msg)) + LINE_SPACE_UNIT-1) & ~(LINE_SPACE_UNIT-1);
385         tRichText_Line  *new = NULL;
386         if( !line )
387         {
388                 // New line!
389                 tRichText_Line  *new = malloc(sizeof(*line) + reqspace);
390                 new->Next = (prev ? prev->Next : NULL);
391                 new->Prev = prev;
392                 new->Num = msg->Line;
393         }
394         else if( line->Space < reqspace )
395         {
396                 // Need to allocate more space
397                 tRichText_Line *new = realloc(line, sizeof(*line) + reqspace);
398         }
399         else
400         {
401                 // It fits :)
402         }
403         if( new ) 
404         {
405                 // TODO: Bookkeeping on how much memory each window uses
406                 new->Space = reqspace;
407                 if(new->Prev)   new->Prev->Next = new;
408                 else    info->FirstLine = new;
409                 if(new->Next)   new->Next->Prev = new;
410                 line = new;
411         }
412         line->ByteLength = Len - sizeof(*msg) - 1;
413         memcpy(line->Data, msg->LineData, Len - sizeof(*msg));
414         line->bIsClean = 0;
415
416         if( line->Num == info->CursorRow ) {
417                 info->CursorLine = line;
418                 info->CursorBytePos = MIN(info->CursorBytePos, line->ByteLength);
419         }
420
421 //      WM_Invalidate(Window, 1);
422
423         return 0;
424 }
425
426 void Renderer_RichText_HandleKeyFire(tWindow *Window, tRichText_Window *Info, const struct sWndMsg_KeyAction *Msg)
427 {
428         tRichText_Line  *line = Info->CursorLine;
429         size_t  len = WriteUTF8(NULL, Msg->UCS32);
430         switch(Msg->UCS32)
431         {
432         case 0:
433                 switch(Msg->KeySym)
434                 {
435                 case KEYSYM_RIGHTARROW:
436                         if( Info->CursorBytePos == line->ByteLength )
437                                 break;
438                         Info->CursorBytePos += ReadUTF8(line->Data + Info->CursorBytePos, NULL);
439                         Info->CursorCol ++;
440                         break;
441                 case KEYSYM_LEFTARROW:
442                         if( Info->CursorBytePos == 0 )
443                                 break;
444                         Info->CursorBytePos -= ReadUTF8Rev(line->Data, Info->CursorBytePos, NULL);
445                         Info->CursorCol --;
446                         break;
447                 case KEYSYM_UPARROW:
448                         _SysDebug("TODO: RichText edit up line");
449                         break;
450                 case KEYSYM_DOWNARROW:
451                         _SysDebug("TODO: RichText edit down line");
452                         break;
453                 default:
454                         // No effect
455                         return ;
456                 }
457                 break;
458         case '\n':      // Newline
459                 _SysDebug("TODO: RichText edit newline");
460                 break;
461         case '\b':      // Backspace
462                 if( Info->CursorBytePos == 0 )
463                         return ;
464                 len = ReadUTF8Rev(line->Data, Info->CursorBytePos, NULL);
465                 Info->CursorBytePos -= len;
466                 Info->CursorCol --;
467                 if(0)
468         case '\x7f':    // Delete
469                 len = ReadUTF8(line->Data + Info->CursorBytePos, NULL);
470                 if( Info->CursorBytePos == line->ByteLength )
471                         return ;
472                 memmove(line->Data + Info->CursorBytePos, line->Data + Info->CursorBytePos + len,
473                         line->ByteLength - Info->CursorBytePos - len);
474                 line->ByteLength -= len;
475                 _SysDebug("RichText: %p Backspace/Delete '%.*s'", Window,
476                         line->ByteLength, line->Data);
477                 break;
478         default:
479                 // Increase buffer size
480                 if( line->ByteLength + len > line->Space ) {
481                         line->Space += LINE_SPACE_UNIT;
482                         tRichText_Line *nl = realloc(line, sizeof(*line) + line->Space);
483                         if( nl == NULL )
484                                 return ;
485                         if( nl != line ) {
486                                 *(line->Prev ? &line->Prev->Next : &Info->FirstLine) = nl;
487                                 if(line->Next)
488                                         line->Next->Prev = nl;
489                                 if(Info->FirstVisLine == line)
490                                         Info->FirstVisLine = nl;
491                                 Info->CursorLine = nl;
492                                 line = nl;
493                         }
494                 }
495                 // Shift data
496                 memmove(line->Data + Info->CursorBytePos + len, line->Data + Info->CursorBytePos,
497                         line->ByteLength - Info->CursorBytePos);
498                 // Encode
499                 WriteUTF8(line->Data + Info->CursorBytePos, Msg->UCS32);
500                 Info->CursorBytePos += len;
501                 Info->CursorCol ++;
502                 line->ByteLength += len;
503                 
504                 _SysDebug("RichText: %p Appended %X '%.*s' to line %i", Window,
505                         Msg->UCS32, len, line->Data + Info->CursorBytePos - len,
506                         line->Num);
507                 break;
508         }
509         // Invalidate line
510         line->bIsClean = 0;
511         WM_Invalidate(Window, 1);
512 }
513
514 int Renderer_RichText_HandleMessage(tWindow *Target, int Msg, int Len, const void *Data)
515 {
516         tRichText_Window        *info = Target->RendererInfo;
517         switch(Msg)
518         {
519         case WNDMSG_RESIZE: {
520                 const struct sWndMsg_Resize *msg = Data;
521                 if(Len < sizeof(*msg))  return -1;
522                 info->DispLines = msg->H / info->LineHeight;
523                 info->bNeedsFullRedraw = 1;     // force full rerender
524                 return 1; }
525         case WNDMSG_KEYFIRE:
526                 if( Len < sizeof(struct sWndMsg_KeyAction) )
527                         return -1;
528                 if( !(info->Flags & AXWIN3_RICHTEXT_READONLY) )
529                 {
530                         Renderer_RichText_HandleKeyFire(Target, info, Data);
531                 }
532                 return 1;
533         case WNDMSG_KEYDOWN:
534         case WNDMSG_KEYUP:
535                 return 1;
536         }
537         return 0;
538 }

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