2 * Acess2 Window Manager v3
3 * - By John Hodge (thePowersGang)
6 * - Formatted Line Editor
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>
18 #define LINES_PER_BLOCK 30
19 #define LINE_SPACE_UNIT 32 // Must be a power of two
22 typedef struct sRichText_Line
24 struct sRichText_Line *Next;
25 struct sRichText_Line *Prev;
28 // TODO: Pre-rendered cache?
33 typedef struct sRichText_Window
35 int DispLines, DispCols;
36 int FirstVisRow, FirstVisCol;
38 int CursorRow, CursorCol;
39 tRichText_Line *FirstLine;
40 tRichText_Line *FirstVisLine;
47 char bNeedsFullRedraw;
49 tRichText_Line *CursorLine;
50 int CursorBytePos; // Recalculated on cursor update
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);
65 tWMRenderer gRenderer_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,
73 [IPC_RICHTEXT_SETATTR] = Renderer_RichText_HandleIPC_SetAttr,
74 [IPC_RICHTEXT_WRITELINE] = Renderer_RichText_HandleIPC_WriteLine
79 int Renderer_RichText_Init(void)
81 WM_RegisterRenderer(&gRenderer_RichText);
85 tWindow *Renderer_RichText_Create(int Flags)
87 tRichText_Window *info;
88 tWindow *ret = WM_CreateWindowStruct( sizeof(*info) );
90 info = ret->RendererInfo;
93 // Initialise font (get an idea of dimensions)
95 WM_Render_GetTextDims(NULL, "yY!", 3, NULL, &h);
101 void Renderer_RichText_Destroy(tWindow *Window)
103 tRichText_Window *info = Window->RendererInfo;
105 // TODO: Is locking needed? WM_Destroy should have taken us off the render tree
106 while( info->FirstLine )
108 tRichText_Line *line = info->FirstLine;
109 info->FirstLine = line->Next;
115 _SysDebug("RichText_Destroy - TODO: Free font");
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)
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,
126 // TODO: Bold, Italic, Underline
127 rwidth = WM_Render_DrawText(Window,
128 X, Row*info->LineHeight,
129 Window->W - X, info->LineHeight,
136 void Renderer_RichText_RenderText(tWindow *Window, int LineNum, tRichText_Line *Line)
138 const char *Text = Line->Data;
139 tRichText_Window *info = Window->RendererInfo;
140 tColour fg = info->DefaultFG;
141 tColour bg = info->DefaultBG;
145 const char *oldtext = Text;
147 for( int i = 0; curx < Window->W && Text < oldtext + Line->ByteLength; i ++ )
152 if( i == info->FirstVisCol )
158 // Not an escape - move along
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 )
172 case 1: // FG Select (\1 RRGGBB)
173 if( sscanf(Text, "%6x%n", &fg, &len) != 1 || len != 6 ) {
175 _SysDebug("foreground scanf failed - len=%i", len);
180 _SysDebug("FG update to %x", fg);
182 case 2: // BG Select (\2 RRGGBB)
183 if( sscanf(Text, "%6x%n", &bg, &len) != 1 || len != 6 ) {
185 _SysDebug("background scanf failed - len=%i", len);
190 _SysDebug("BG update to %x", bg);
192 case 3: // Flagset (0,it,uline,bold)
193 if( sscanf(Text, "%1hhx%n", &flags, &len) != 1 || len != 1 ) {
195 _SysDebug("Flagset scanf failed - len=%i", len);
201 case 4: // Escape (do nothing)
203 // NOTE: No update to oldtext
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);
215 void Renderer_RichText_Redraw(tWindow *Window)
217 tRichText_Window *info = Window->RendererInfo;
218 tRichText_Line *line = info->FirstVisLine;
221 line = info->FirstLine;
222 while(line && line->Num < info->FirstVisRow )
224 info->FirstVisLine = line;
226 while( line && line->Prev && line->Prev->Num > info->FirstVisRow )
230 for( i = 0; i < info->DispLines && line; i ++ )
232 if( i >= info->nLines - info->FirstVisRow )
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,
245 // Hack to clear cursor on NULL lines
246 WM_Render_FillRect(Window,
247 0, i*info->LineHeight,
255 if( info->bNeedsFullRedraw || !line->bIsClean )
257 WM_Render_FillRect(Window,
258 0, i*info->LineHeight,
259 Window->W, info->LineHeight,
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);
272 // Clear out lines i to info->DispLines-1
273 if( info->bNeedsFullRedraw )
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,
283 info->bNeedsFullRedraw = 0;
285 // HACK: Hardcoded text width of 8
286 info->DispCols = Window->W / 8;
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 )
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,
299 info->LineHeight, info->DefaultFG
304 tRichText_Line *Renderer_RichText_int_GetLine(tWindow *Window, int LineNum, tRichText_Line **Prev)
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;
315 if( !line || line->Num > LineNum )
320 void Renderer_RichText_int_UpdateCursorOfs(tRichText_Window *Info)
322 tRichText_Line *line = Info->CursorLine;
324 for( int i = 0; i < Info->CursorCol && ofs < line->ByteLength; i ++ )
326 ofs += ReadUTF8(line->Data + ofs, NULL);
328 Info->CursorBytePos = ofs;
331 int Renderer_RichText_HandleIPC_SetAttr(tWindow *Window, size_t Len, const void *Data)
333 tRichText_Window *info = Window->RendererInfo;
334 const struct sRichTextIPC_SetAttr *msg = Data;
335 if(Len < sizeof(*msg)) return -1;
337 _SysDebug("RichText Attr %i set to %x", msg->Attr, msg->Value);
341 info->DefaultBG = msg->Value;
344 info->DefaultFG = msg->Value;
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);
353 if( newRow != info->CursorRow ) {
354 line = Renderer_RichText_int_GetLine(Window, newRow, NULL);
358 info->CursorRow = newRow;
359 info->CursorCol = newCol;
360 info->CursorLine = line;
361 Renderer_RichText_int_UpdateCursorOfs(info);
362 WM_Invalidate(Window, 1);
365 // TODO: Set scroll flag
367 case _ATTR_LINECOUNT:
368 info->nLines = msg->Value;
375 int Renderer_RichText_HandleIPC_WriteLine(tWindow *Window, size_t Len, const void *Data)
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
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);
388 tRichText_Line *new = malloc(sizeof(*line) + reqspace);
389 // TODO: Bookkeeping on how much memory each window uses
390 new->Next = (prev ? prev->Next : NULL);
392 new->Num = msg->Line;
393 new->Space = reqspace;
394 *(prev ? &prev->Next : &info->FirstLine) = new;
395 if(new->Next) new->Next->Prev = new;
398 else if( line->Space < reqspace )
400 // Need to allocate more space
401 tRichText_Line *new = realloc(line, sizeof(*line) + reqspace);
402 // TODO: Bookkeeping on how much memory each window uses
403 new->Space = reqspace;
405 if(new->Prev) new->Prev->Next = new;
406 else info->FirstLine = new;
407 if(new->Next) new->Next->Prev = new;
414 line->ByteLength = Len - sizeof(*msg) - 1;
415 memcpy(line->Data, msg->LineData, Len - sizeof(*msg));
418 if( line->Num == info->CursorRow ) {
419 info->CursorLine = line;
420 info->CursorBytePos = MIN(info->CursorBytePos, line->ByteLength);
423 // WM_Invalidate(Window, 1);
428 void Renderer_RichText_HandleKeyFire(tWindow *Window, tRichText_Window *Info, const struct sWndMsg_KeyAction *Msg)
430 tRichText_Line *line = Info->CursorLine;
431 size_t len = WriteUTF8(NULL, Msg->UCS32);
437 case KEYSYM_RIGHTARROW:
438 if( Info->CursorBytePos == line->ByteLength )
440 Info->CursorBytePos += ReadUTF8(line->Data + Info->CursorBytePos, NULL);
443 case KEYSYM_LEFTARROW:
444 if( Info->CursorBytePos == 0 )
446 Info->CursorBytePos -= ReadUTF8Rev(line->Data, Info->CursorBytePos, NULL);
450 _SysDebug("TODO: RichText edit up line");
452 case KEYSYM_DOWNARROW:
453 _SysDebug("TODO: RichText edit down line");
460 case '\n': // Newline
461 _SysDebug("TODO: RichText edit newline");
463 case '\b': // Backspace
464 if( Info->CursorBytePos == 0 )
466 len = ReadUTF8Rev(line->Data, Info->CursorBytePos, NULL);
467 Info->CursorBytePos -= len;
470 case '\x7f': // Delete
471 len = ReadUTF8(line->Data + Info->CursorBytePos, NULL);
472 if( Info->CursorBytePos == line->ByteLength )
474 memmove(line->Data + Info->CursorBytePos, line->Data + Info->CursorBytePos + len,
475 line->ByteLength - Info->CursorBytePos - len);
476 line->ByteLength -= len;
477 _SysDebug("RichText: %p Backspace/Delete '%.*s'", Window,
478 line->ByteLength, line->Data);
481 // Increase buffer size
482 if( line->ByteLength + len > line->Space ) {
483 line->Space += LINE_SPACE_UNIT;
484 tRichText_Line *nl = realloc(line, sizeof(*line) + line->Space);
488 *(line->Prev ? &line->Prev->Next : &Info->FirstLine) = nl;
490 line->Next->Prev = nl;
491 if(Info->FirstVisLine == line)
492 Info->FirstVisLine = nl;
493 Info->CursorLine = nl;
498 memmove(line->Data + Info->CursorBytePos + len, line->Data + Info->CursorBytePos,
499 line->ByteLength - Info->CursorBytePos);
501 WriteUTF8(line->Data + Info->CursorBytePos, Msg->UCS32);
502 Info->CursorBytePos += len;
504 line->ByteLength += len;
506 _SysDebug("RichText: %p Appended %X '%.*s' to line %i", Window,
507 Msg->UCS32, len, line->Data + Info->CursorBytePos - len,
513 WM_Invalidate(Window, 1);
516 int Renderer_RichText_HandleMessage(tWindow *Target, int Msg, int Len, const void *Data)
518 tRichText_Window *info = Target->RendererInfo;
521 case WNDMSG_RESIZE: {
522 const struct sWndMsg_Resize *msg = Data;
523 if(Len < sizeof(*msg)) return -1;
524 info->DispLines = msg->H / info->LineHeight;
525 info->bNeedsFullRedraw = 1; // force full rerender
528 if( Len < sizeof(struct sWndMsg_KeyAction) )
530 if( !(info->Flags & AXWIN3_RICHTEXT_READONLY) )
532 Renderer_RichText_HandleKeyFire(Target, info, Data);