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

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