Merge branch 'master' of git://git.ucc.asn.au/tpg/acess2
[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         _SysDebug("Windows_int_PaintMessageLine: Message=%p,Offset=%i,EnablePrint=%b",
145                 Message, (int)Offset, EnablePrint);
146         if( Offset > strlen(Message->Data) ) {
147                 _SysDebug("Windows_int_PaintMessageLine: No message left");
148                 return 0;
149         }
150         _SysDebug("Windows_int_PaintMessageLine: Message->Data=\"%.*s\"+\"%s\"",
151                 (int)Offset, Message->Data, Message->Data+Offset);
152         
153         size_t  avail = giTerminal_Width - Message->PrefixLen;
154         const char *msg_data = Message->Data + Offset;
155         int used = WordBreak(msg_data, avail);
156         
157         if( EnablePrint )
158         {
159                 if( Offset == 0 )
160                         Windows_int_PaintMessagePrefix(Message, true);
161                 else {
162                         for(int i = 0; i < Message->PrefixLen; i ++)
163                                 printf(" ");
164                         //printf("\x1b[%iC", Message->PrefixLen);
165                 }
166                 printf("%.*s", used, msg_data);
167         }
168         
169         _SysDebug("used(%i) >= strlen(msg_data)(%i)", used, strlen(msg_data));
170         if( used >= strlen(msg_data) )
171                 return 0;
172         
173         return Offset + used;
174 }
175
176 int Windows_int_GetMessageLines(const tMessage *Message)
177 {
178         assert(Message->PrefixLen);
179         const size_t    avail = giTerminal_Height - Message->PrefixLen;
180         const size_t    msglen = strlen(Message->Data);
181         size_t  offset = 0;
182          int    nLines = 0;
183         do {
184                 offset += WordBreak(Message->Data+offset, avail);
185                 nLines ++;
186         } while(offset < msglen);
187         return nLines;
188 }
189
190 void Windows_SwitchTo(tWindow *Window)
191 {
192         gpCurrentWindow = Window;
193         Redraw_Screen();
194 }
195
196 void Windows_SetStatusServer(tServer *Server)
197 {
198         gWindow_Status.Server = Server;
199 }
200
201 tWindow *Windows_GetByIndex(int Index)
202 {
203         tWindow *win;
204         for( win = gpWindows; win && Index--; win = win->Next )
205                 ;
206         return win;
207 }
208
209 tWindow *Windows_GetByNameEx(tServer *Server, const char *Name, bool CreateAllowed)
210 {
211         tWindow *ret, *prev = NULL;
212          int    num = 1;
213         
214         // Get the end of the list and check for duplicates
215         // TODO: Cache this instead
216         for( ret = &gWindow_Status; ret; prev = ret, ret = ret->Next )
217         {
218                 if( ret->Server == Server && strcmp(ret->Name, Name) == 0 )
219                 {
220                         return ret;
221                 }
222                 num ++;
223         }
224         if( !CreateAllowed ) {
225                 return NULL;
226         }
227         
228         ret = malloc(sizeof(tWindow) + strlen(Name) + 1);
229         ret->Messages = NULL;
230         ret->Server = Server;
231         ret->ActivityLevel = 1;
232         strcpy(ret->Name, Name);
233         
234         if( prev ) {
235                 ret->Next = prev->Next;
236                 prev->Next = ret;
237         }
238         else {  // Shouldn't happen really
239                 ret->Next = gpWindows;
240                 gpWindows = ret;
241         }
242         
243 //      printf("Win %i %s:%s created\n", num, Server->Name, Name);
244         
245         return ret;
246 }
247
248 tWindow *Windows_GetByName(tServer *Server, const char *Name)
249 {
250         return Windows_GetByNameEx(Server, Name, false);
251 }
252
253 tWindow *Window_Create(tServer *Server, const char *Name)
254 {
255         return Windows_GetByNameEx(Server, Name, true);
256 }
257
258
259 tWindow *Window_int_ParseSpecial(const tWindow *Window)
260 {
261         if( Window == NULL )
262                 return gpCurrentWindow;
263         if( Window == WINDOW_STATUS )
264                 return &gWindow_Status;
265         return (tWindow*)Window;
266 }
267
268 const char *Window_GetName(const tWindow *Window) {
269         return Window_int_ParseSpecial(Window)->Name;
270 }
271 tServer *Window_GetServer(const tWindow *Window) {
272         return Window_int_ParseSpecial(Window)->Server;
273 }
274 bool Window_IsChat(const tWindow *Window) {
275         return Window_int_ParseSpecial(Window) != &gWindow_Status;
276 }
277
278 // -----------------------------
279 //  Messages
280 // -----------------------------
281 void Window_AppendMessage(tWindow *Window, enum eMessageClass Class, const char *Source, const char *Message, ...)
282 {
283         Window = Window_int_ParseSpecial(Window);
284
285         va_list args;
286         
287         va_start(args, Message);
288         size_t len = vsnprintf(NULL, 0, Message, args);
289         va_end(args);
290         
291         tMessage *msg = malloc( sizeof(tMessage) + len+1 + (Source?strlen(Source)+1:0) );
292         assert(msg);
293         
294         msg->Class = Class;
295         msg->Source = (Source ? msg->Data + len+1 : NULL);
296         msg->Timestamp = _SysTimestamp();
297         
298         va_start(args, Message);
299         vsnprintf(msg->Data, len+1, Message, args);
300         va_end(args);
301         
302         if( Source ) {
303                 strcpy(msg->Source, Source);
304         }
305         
306         msg->PrefixLen = Windows_int_PaintMessagePrefix(msg, false);
307         
308         msg->Next = Window->Messages;
309         Window->Messages = msg;
310         
311         if( Window == gpCurrentWindow )
312         {
313                 // Scroll if needed, and redraw?
314                 // - Lazy option of draw at bottom of screen
315                 printf("\33[s");        // Save cursor
316                 size_t  offset = 0, len;
317                 do {
318                         printf("\33[S");        // Scroll down 1 (free space below)
319                         SetCursorPos(giTerminal_Height-2, 1);
320                         len = Windows_int_PaintMessageLine(msg, offset, true);
321                         offset += len;
322                 } while( len > 0 );
323                 printf("\33[u");        // Restore cursor
324                 fflush(stdout);
325         }
326 }
327
328 void Window_AppendMsg_Join(tWindow *Window, const char *Usermask)
329 {
330         Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s has joined %s", Usermask, Window->Name);
331 }
332 void Window_AppendMsg_Quit(tWindow *Window, const char *Usermask, const char *Reason)
333 {
334         Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s has joined quit (%s)", Usermask, Reason);
335 }
336 void Window_AppendMsg_Part(tWindow *Window, const char *Usermask, const char *Reason)
337 {
338         Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s has joined left %s (%s)", Usermask, Window->Name, Reason);
339 }
340 void Window_AppendMsg_Topic(tWindow *Window, const char *Topic)
341 {
342         Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "Topic of %s is %s", Window->Name, Topic);
343 }
344 void Window_AppendMsg_TopicTime(tWindow *Window, const char *User, const char *Timestamp)
345 {
346         Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "Topic set by %s at %s", User, Timestamp);
347 }
348 void Window_AppendMsg_Kick(tWindow *Window, const char *Operator, const char *Nick, const char *Reason)
349 {
350         Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s was kicked from %s by %s [%s]",
351                 Nick, Window->Name, Operator, Reason);
352 }
353 void Window_AppendMsg_Mode(tWindow *Window, const char *Operator, const char *Flags, const char *Args)
354 {
355         Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "mode/%s [%s %s] by %s",
356                 Window->Name, Flags, Args, Operator);
357 }
358

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