Usermode/irc - New code working, glitches on wrapped messages (swapping windows might...
[tpg/acess2.git] / Usermode / Applications / irc_src / window.c
1 /*
2  */
3 #include "window.h"
4 #include <stddef.h>
5 #include <string.h>
6 #include <stdarg.h>
7 #include <stdio.h>      // TODO: replace with calls into ACurses_*
8 #include <stdlib.h>
9 #include <assert.h>
10 #include "server.h"
11 #include <ctype.h>
12
13 struct sMessage
14 {
15         struct sMessage *Next;
16         time_t  Timestamp;
17         enum eMessageClass      Class;
18         char    *Source;        // Pointer to the end of `Data`
19         size_t  PrefixLen;
20         char    Data[];
21 };
22
23 struct sWindow
24 {
25         struct sWindow  *Next;
26         tMessage        *Messages;
27         tServer *Server;        //!< Canonical server (can be NULL)
28          int    ActivityLevel;
29         char    Name[]; // Channel name / remote user
30 };
31
32 // === PROTOTYPES ===
33 void    Windows_RepaintCurrent(void);
34 size_t  WordBreak(const char *Line, size_t avail);
35 size_t  Windows_int_PaintMessagePrefix(const tMessage *Message, bool EnablePrint);
36 size_t  Windows_int_PaintMessageLine(const tMessage *Message, size_t Offset, bool EnablePrint);
37  int    Windows_int_GetMessageLines(const tMessage *Message);
38  int    Windows_int_PaintMessage(tMessage *Message);
39
40 // === GLOBALS ===
41 tWindow gWindow_Status = {
42         NULL, NULL, NULL,       // No next, empty list, no server
43         0, {"(status)"} // No activity, empty name (rendered as status)
44 };
45 tWindow *gpWindows = &gWindow_Status;
46 tWindow *gpCurrentWindow = &gWindow_Status;
47
48 // === CODE ===
49 void Windows_RepaintCurrent(void)
50 {
51         // TODO: Title bar?
52         SetCursorPos(1, 1);
53         printf("\x1b[37;44m\x1b[2K%s\x1b[0m\n", gpCurrentWindow->Name);
54
55          int    avail_rows = giTerminal_Height - 3;
56
57         // Note: This renders from the bottom up
58         tMessage *msg = gpCurrentWindow->Messages;
59         for( int y = avail_rows; msg && y > 0; )
60         {
61                 int lines = Windows_int_GetMessageLines(msg);
62                 y -= lines;
63                 size_t  ofs = 0;
64                 size_t  len;
65                  int    i = 0;
66                 do {
67                         SetCursorPos(2 + y+i, 1);
68                         len = Windows_int_PaintMessageLine(msg, ofs, (y+i >= 0));
69                         ofs += len;
70                         i ++;
71                 } while( len > 0 );
72                 msg = msg->Next;
73         }
74
75         // Status line is our department
76         SetCursorPos(giTerminal_Height-1, 1);
77         printf("\x1b[37;44m\x1b[2K[%s] [%s/%s]\x1b[0m", Server_GetNick(gpCurrentWindow->Server),
78                 Server_GetName(gpCurrentWindow->Server), gpCurrentWindow->Name);
79         fflush(stdout);
80         // Bottom line is rendered by the prompt
81 }
82
83 size_t WordBreak(const char *Line, size_t avail)
84 {
85         // If sufficient space, don't need to break on a word
86         if( strlen(Line) < avail )
87                 return strlen(Line);
88         
89         // Search backwards from end of space for a non-alpha-numeric character
90         size_t  ret = avail-1;
91         while( ret > 0 && isalnum(Line[ret]) )
92                 ret --;
93         
94         // if one wasn't found in a sane area, just split
95         if( ret < avail-20 || ret == 0 )
96                 return avail;
97         
98         return ret;
99 }
100
101 size_t Windows_int_PaintMessagePrefix(const tMessage *Message, bool EnablePrint)
102 {
103         size_t  len = 0;
104         
105         unsigned long   seconds_today = Message->Timestamp/1000 % (24 * 3600);
106         if(EnablePrint)
107                 printf("%02i:%02i:%02i ", seconds_today/3600, (seconds_today/60)%60, seconds_today%60);
108         else
109                 len += snprintf(NULL, 0, "%02i:%02i:%02i ", seconds_today/3600, (seconds_today/60)%60, seconds_today%60);
110         
111         const char *format;
112         switch(Message->Class)
113         {
114         case MSG_CLASS_BARE:
115                 format = "";
116                 break;
117         case MSG_CLASS_CLIENT:
118                 if(Message->Source)
119                         format = "[%s] -!- ";
120                 else 
121                         format = "-!- ";
122                 break;
123         case MSG_CLASS_WALL:
124                 format = "[%s] ";
125                 break;
126         case MSG_CLASS_MESSAGE:
127                 format = "<%s> ";
128                 break;
129         case MSG_CLASS_ACTION:
130                 format = "* %s ";
131                 break;
132         }
133         
134         if( EnablePrint )
135                 len += printf(format, Message->Source);
136         else
137                 len += snprintf(NULL, 0, format, Message->Source);
138
139         return len;
140 }
141
142 size_t Windows_int_PaintMessageLine(const tMessage *Message, size_t Offset, bool EnablePrint)
143 {
144         if( Message->Data[Offset] == '\0' ) {
145                 return 0;
146         }
147         
148         size_t  avail = giTerminal_Width - Message->PrefixLen;
149         const char *msg_data = Message->Data + Offset;
150         int used = WordBreak(msg_data+Offset, avail);
151         
152         if( EnablePrint )
153         {
154                 if( Offset == 0 )
155                         Windows_int_PaintMessagePrefix(Message, true);
156                 else
157                         printf("\x1b[%iC", Message->PrefixLen);
158                 printf("%.*s", used, msg_data);
159         }
160         
161         if( msg_data[used] == '\0' )
162                 return 0;
163         
164         return Offset + used;
165 }
166
167 int Windows_int_GetMessageLines(const tMessage *Message)
168 {
169         assert(Message->PrefixLen);
170         const size_t    avail = giTerminal_Height - Message->PrefixLen;
171         const size_t    msglen = strlen(Message->Data);
172         size_t  offset = 0;
173          int    nLines = 0;
174         do {
175                 offset += WordBreak(Message->Data+offset, avail);
176                 nLines ++;
177         } while(offset < msglen);
178         return nLines;
179 }
180
181 void Windows_SwitchTo(tWindow *Window)
182 {
183         gpCurrentWindow = Window;
184         Redraw_Screen();
185 }
186
187 void Windows_SetStatusServer(tServer *Server)
188 {
189         gWindow_Status.Server = Server;
190 }
191
192 tWindow *Windows_GetByIndex(int Index)
193 {
194         tWindow *win;
195         for( win = gpWindows; win && Index--; win = win->Next )
196                 ;
197         return win;
198 }
199
200 tWindow *Windows_GetByNameEx(tServer *Server, const char *Name, bool CreateAllowed)
201 {
202         tWindow *ret, *prev = NULL;
203          int    num = 1;
204         
205         // Get the end of the list and check for duplicates
206         // TODO: Cache this instead
207         for( ret = &gWindow_Status; ret; prev = ret, ret = ret->Next )
208         {
209                 if( ret->Server == Server && strcmp(ret->Name, Name) == 0 )
210                 {
211                         return ret;
212                 }
213                 num ++;
214         }
215         if( !CreateAllowed ) {
216                 return NULL;
217         }
218         
219         ret = malloc(sizeof(tWindow) + strlen(Name) + 1);
220         ret->Messages = NULL;
221         ret->Server = Server;
222         ret->ActivityLevel = 1;
223         strcpy(ret->Name, Name);
224         
225         if( prev ) {
226                 ret->Next = prev->Next;
227                 prev->Next = ret;
228         }
229         else {  // Shouldn't happen really
230                 ret->Next = gpWindows;
231                 gpWindows = ret;
232         }
233         
234 //      printf("Win %i %s:%s created\n", num, Server->Name, Name);
235         
236         return ret;
237 }
238
239 tWindow *Windows_GetByName(tServer *Server, const char *Name)
240 {
241         return Windows_GetByNameEx(Server, Name, false);
242 }
243
244 tWindow *Window_Create(tServer *Server, const char *Name)
245 {
246         return Windows_GetByNameEx(Server, Name, true);
247 }
248
249
250 tWindow *Window_int_ParseSpecial(const tWindow *Window)
251 {
252         if( Window == NULL )
253                 return gpCurrentWindow;
254         if( Window == WINDOW_STATUS )
255                 return &gWindow_Status;
256         return (tWindow*)Window;
257 }
258
259 const char *Window_GetName(const tWindow *Window) {
260         return Window_int_ParseSpecial(Window)->Name;
261 }
262 tServer *Window_GetServer(const tWindow *Window) {
263         return Window_int_ParseSpecial(Window)->Server;
264 }
265 bool Window_IsChat(const tWindow *Window) {
266         return Window_int_ParseSpecial(Window) != &gWindow_Status;
267 }
268
269 // -----------------------------
270 //  Messages
271 // -----------------------------
272 void Window_AppendMessage(tWindow *Window, enum eMessageClass Class, const char *Source, const char *Message, ...)
273 {
274         Window = Window_int_ParseSpecial(Window);
275
276         va_list args;
277         
278         va_start(args, Message);
279         size_t len = vsnprintf(NULL, 0, Message, args);
280         va_end(args);
281         
282         tMessage *msg = malloc( sizeof(tMessage) + len+1 + (Source?strlen(Source)+1:0) );
283         assert(msg);
284         
285         msg->Class = Class;
286         msg->Source = (Source ? msg->Data + len+1 : NULL);
287         msg->Timestamp = _SysTimestamp();
288         
289         va_start(args, Message);
290         vsnprintf(msg->Data, len+1, Message, args);
291         va_end(args);
292         
293         if( Source ) {
294                 strcpy(msg->Source, Source);
295         }
296         
297         msg->PrefixLen = Windows_int_PaintMessagePrefix(msg, false);
298         
299         msg->Next = Window->Messages;
300         Window->Messages = msg;
301         
302         if( Window == gpCurrentWindow )
303         {
304                 // Scroll if needed, and redraw?
305                 // - Lazy option of draw at bottom of screen
306                 printf("\33[s");        // Save cursor
307                 size_t  offset = 0, len;
308                 do {
309                         printf("\33[T");        // Scroll down 1 (free space below)
310                         SetCursorPos(giTerminal_Height-2, 1);
311                         len = Windows_int_PaintMessageLine(Message, offset, true);
312                         offset += len;
313                 } while( len > 0 );
314                 printf("\33[u");        // Restore cursor
315                 fflush(stdout);
316         }
317 }
318
319 void Window_AppendMsg_Join(tWindow *Window, const char *Usermask)
320 {
321         Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s has joined %s", Usermask, Window->Name);
322 }
323 void Window_AppendMsg_Quit(tWindow *Window, const char *Usermask, const char *Reason)
324 {
325         Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s has joined quit (%s)", Usermask, Reason);
326 }
327 void Window_AppendMsg_Part(tWindow *Window, const char *Usermask, const char *Reason)
328 {
329         Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s has joined left %s (%s)", Usermask, Window->Name, Reason);
330 }
331 void Window_AppendMsg_Topic(tWindow *Window, const char *Topic)
332 {
333         Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "Topic of %s is %s", Window->Name, Topic);
334 }
335 void Window_AppendMsg_TopicTime(tWindow *Window, const char *User, const char *Timestamp)
336 {
337         Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "Topic set by %s at %s", User, Timestamp);
338 }
339 void Window_AppendMsg_Kick(tWindow *Window, const char *Operator, const char *Nick, const char *Reason)
340 {
341         Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s was kicked from %s by %s [%s]",
342                 Nick, Window->Name, Operator, Reason);
343 }
344 void Window_AppendMsg_Mode(tWindow *Window, const char *Operator, const char *Flags, const char *Args)
345 {
346         Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "mode/%s [%s %s] by %s",
347                 Window->Name, Flags, Args, Operator);
348 }
349

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