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_HandleIPC_ScrollRange(tWindow *Window, size_t Len, const void *Data);
63 int Renderer_RichText_HandleMessage(tWindow *Target, int Msg, int Len, const void *Data);
66 tWMRenderer gRenderer_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,
74 [IPC_RICHTEXT_SETATTR] = Renderer_RichText_HandleIPC_SetAttr,
75 [IPC_RICHTEXT_WRITELINE] = Renderer_RichText_HandleIPC_WriteLine,
76 [IPC_RICHTEXT_SCROLLRANGE] = Renderer_RichText_HandleIPC_ScrollRange
81 int Renderer_RichText_Init(void)
83 WM_RegisterRenderer(&gRenderer_RichText);
87 tWindow *Renderer_RichText_Create(int Flags)
89 tRichText_Window *info;
90 tWindow *ret = WM_CreateWindowStruct( sizeof(*info) );
92 info = ret->RendererInfo;
95 // Initialise font (get an idea of dimensions)
97 WM_Render_GetTextDims(NULL, "yY!", 3, NULL, &h);
103 void Renderer_RichText_Destroy(tWindow *Window)
105 tRichText_Window *info = Window->RendererInfo;
107 // TODO: Is locking needed? WM_Destroy should have taken us off the render tree
108 while( info->FirstLine )
110 tRichText_Line *line = info->FirstLine;
111 info->FirstLine = line->Next;
117 _SysDebug("RichText_Destroy - TODO: Free font");
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)
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,
128 // TODO: Bold, Italic, Underline
129 rwidth = WM_Render_DrawText(Window,
130 X, Row*info->LineHeight,
131 Window->W - X, info->LineHeight,
138 void Renderer_RichText_RenderText(tWindow *Window, int LineNum, tRichText_Line *Line)
140 const char *Text = Line->Data;
141 tRichText_Window *info = Window->RendererInfo;
142 tColour fg = info->DefaultFG;
143 tColour bg = info->DefaultBG;
147 const char *oldtext = Text;
149 for( int i = 0; curx < Window->W && Text < oldtext + Line->ByteLength; i ++ )
154 if( i == info->FirstVisCol )
160 // Not an escape - move along
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 )
174 case 1: // FG Select (\1 RRGGBB)
175 if( sscanf(Text, "%6x%n", &fg, &len) != 1 || len != 6 ) {
177 _SysDebug("foreground scanf failed - len=%i", len);
182 _SysDebug("FG update to %x", fg);
184 case 2: // BG Select (\2 RRGGBB)
185 if( sscanf(Text, "%6x%n", &bg, &len) != 1 || len != 6 ) {
187 _SysDebug("background scanf failed - len=%i", len);
192 _SysDebug("BG update to %x", bg);
194 case 3: // Flagset (0,it,uline,bold)
195 if( sscanf(Text, "%1hhx%n", &flags, &len) != 1 || len != 1 ) {
197 _SysDebug("Flagset scanf failed - len=%i", len);
203 case 4: // Escape (do nothing)
205 // NOTE: No update to oldtext
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);
217 void Renderer_RichText_Redraw(tWindow *Window)
219 tRichText_Window *info = Window->RendererInfo;
220 tRichText_Line *line = info->FirstVisLine;
223 line = info->FirstLine;
224 while(line && line->Num < info->FirstVisRow )
226 info->FirstVisLine = line;
228 while( line && line->Prev && line->Prev->Num > info->FirstVisRow )
232 for( i = 0; i < info->DispLines && line; i ++ )
234 if( i >= info->nLines - info->FirstVisRow )
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,
247 // Hack to clear cursor on NULL lines
248 WM_Render_FillRect(Window,
249 0, i*info->LineHeight,
257 if( info->bNeedsFullRedraw || !line->bIsClean )
259 WM_Render_FillRect(Window,
260 0, i*info->LineHeight,
261 Window->W, info->LineHeight,
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);
274 // Clear out lines i to info->DispLines-1
275 if( info->bNeedsFullRedraw )
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,
285 info->bNeedsFullRedraw = 0;
287 // HACK: Hardcoded text width of 8
288 info->DispCols = Window->W / 8;
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 )
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,
301 info->LineHeight, info->DefaultFG
306 tRichText_Line *Renderer_RichText_int_GetLine(tWindow *Window, int LineNum, tRichText_Line **Prev)
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;
317 if( !line || line->Num > LineNum )
322 void Renderer_RichText_int_UpdateCursorOfs(tRichText_Window *Info)
324 tRichText_Line *line = Info->CursorLine;
328 for( int i = 0; i < Info->CursorCol && ofs < line->ByteLength; i ++ )
330 ofs += ReadUTF8(line->Data + ofs, NULL);
333 Info->CursorBytePos = ofs;
336 int Renderer_RichText_HandleIPC_SetAttr(tWindow *Window, size_t Len, const void *Data)
338 tRichText_Window *info = Window->RendererInfo;
339 const struct sRichTextIPC_SetAttr *msg = Data;
340 if(Len < sizeof(*msg)) return -1;
342 _SysDebug("RichText Attr %i set to %x", msg->Attr, msg->Value);
346 info->DefaultBG = msg->Value;
349 info->DefaultFG = msg->Value;
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);
358 if( newRow != info->CursorRow ) {
359 line = Renderer_RichText_int_GetLine(Window, newRow, NULL);
363 info->CursorRow = newRow;
364 info->CursorCol = newCol;
365 info->CursorLine = line;
366 Renderer_RichText_int_UpdateCursorOfs(info);
367 WM_Invalidate(Window, 1);
370 // TODO: Set scroll flag
372 case _ATTR_LINECOUNT:
373 info->nLines = msg->Value;
380 int Renderer_RichText_HandleIPC_WriteLine(tWindow *Window, size_t Len, const void *Data)
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
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;
395 new = malloc(sizeof(*line) + reqspace);
396 new->Next = (prev ? prev->Next : NULL);
398 new->Num = msg->Line;
400 else if( line->Space < reqspace )
402 // Need to allocate more space
403 new = realloc(line, sizeof(*line) + reqspace);
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;
418 line->ByteLength = data_bytes - 1;
419 memcpy(line->Data, msg->LineData, data_bytes);
422 _SysDebug("RichText: %p - Write %i %i'%.*s'", Window, line->Num, line->ByteLength,
423 line->ByteLength, line->Data);
425 if( line->Num == info->CursorRow ) {
426 info->CursorLine = line;
427 info->CursorBytePos = MIN(info->CursorBytePos, line->ByteLength);
430 // WM_Invalidate(Window, 1);
435 int Renderer_RichText_HandleIPC_ScrollRange(tWindow *Window, size_t Len, const void *Data)
437 tRichText_Window *info = Window->RendererInfo;
438 const struct sRichTextIPC_ScrollRange *msg = Data;
439 if( Len < sizeof(*msg) ) return -1;
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 ) {
450 // Find the range start
451 tRichText_Line *line = info->FirstLine;
452 tRichText_Line *prev = NULL;
453 while( line && line->Num < msg->First ) {
458 _SysDebug("RichText ScrollRange: Search for %i ran off end, nlines=%i",
459 msg->First, info->nLines);
464 _SysDebug("TODO: RichText ScrollRange -ve");
468 if( msg->First <= info->FirstVisRow && info->FirstVisRow < msg->First+msg->Range )
469 info->FirstVisLine = NULL;
471 // Remove 'msg->Count' lines from beginning of the range, ...
472 while( line && line->Num < msg->First + msg->Count )
474 tRichText_Line *next = line->Next;
475 _SysDebug("- RichText ScrollRange: Remove %i '%.*s'",
476 line->Num, line->ByteLength, line->Data);
484 info->FirstLine = line;
487 // ... and shift ->Num down for the rest
488 for( ; line && line->Num < msg->First + msg->Range; line = line->Next )
490 line->Num -= msg->Count;
491 if( line->Num >= info->FirstVisRow && !info->FirstVisLine )
492 info->FirstVisLine = line;
495 info->nLines -= msg->Count;
498 info->bNeedsFullRedraw = 1;
503 void Renderer_RichText_HandleKeyFire(tWindow *Window, tRichText_Window *Info, const struct sWndMsg_KeyAction *Msg)
505 tRichText_Line *line = Info->CursorLine;
506 size_t len = WriteUTF8(NULL, Msg->UCS32);
512 case KEYSYM_RIGHTARROW:
513 if( Info->CursorBytePos == line->ByteLength )
515 Info->CursorBytePos += ReadUTF8(line->Data + Info->CursorBytePos, NULL);
518 case KEYSYM_LEFTARROW:
519 if( Info->CursorBytePos == 0 )
521 Info->CursorBytePos -= ReadUTF8Rev(line->Data, Info->CursorBytePos, NULL);
525 _SysDebug("TODO: RichText edit up line");
527 case KEYSYM_DOWNARROW:
528 _SysDebug("TODO: RichText edit down line");
535 case '\n': // Newline
536 _SysDebug("TODO: RichText edit newline");
538 case '\b': // Backspace
539 if( Info->CursorBytePos == 0 )
541 len = ReadUTF8Rev(line->Data, Info->CursorBytePos, NULL);
542 Info->CursorBytePos -= len;
545 case '\x7f': // Delete
546 len = ReadUTF8(line->Data + Info->CursorBytePos, NULL);
547 if( Info->CursorBytePos == line->ByteLength )
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);
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);
563 *(line->Prev ? &line->Prev->Next : &Info->FirstLine) = nl;
565 line->Next->Prev = nl;
566 if(Info->FirstVisLine == line)
567 Info->FirstVisLine = nl;
568 Info->CursorLine = nl;
573 memmove(line->Data + Info->CursorBytePos + len, line->Data + Info->CursorBytePos,
574 line->ByteLength - Info->CursorBytePos);
576 WriteUTF8(line->Data + Info->CursorBytePos, Msg->UCS32);
577 Info->CursorBytePos += len;
579 line->ByteLength += len;
581 _SysDebug("RichText: %p Appended %X '%.*s' to line %i", Window,
582 Msg->UCS32, len, line->Data + Info->CursorBytePos - len,
588 WM_Invalidate(Window, 1);
591 int Renderer_RichText_HandleMessage(tWindow *Target, int Msg, int Len, const void *Data)
593 tRichText_Window *info = Target->RendererInfo;
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
603 if( Len < sizeof(struct sWndMsg_KeyAction) )
605 if( !(info->Flags & AXWIN3_RICHTEXT_READONLY) )
607 Renderer_RichText_HandleKeyFire(Target, info, Data);