7 #include <stdio.h> // TODO: replace with calls into ACurses_*
15 struct sMessage *Next;
17 enum eMessageClass Class;
18 char *Source; // Pointer to the end of `Data`
27 tServer *Server; //!< Canonical server (can be NULL)
29 char Name[]; // Channel name / remote user
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);
41 tWindow gWindow_Status = {
42 NULL, NULL, NULL, // No next, empty list, no server
43 0, {"(status)"} // No activity, empty name (rendered as status)
45 tWindow *gpWindows = &gWindow_Status;
46 tWindow *gpCurrentWindow = &gWindow_Status;
49 void Windows_RepaintCurrent(void)
53 printf("\x1b[37;44m\x1b[2K%s\x1b[0m\n", gpCurrentWindow->Name);
55 int avail_rows = giTerminal_Height - 3;
57 // Note: This renders from the bottom up
58 tMessage *msg = gpCurrentWindow->Messages;
59 for( int y = avail_rows; msg && y > 0; )
61 int lines = Windows_int_GetMessageLines(msg);
67 SetCursorPos(2 + y+i, 1);
68 len = Windows_int_PaintMessageLine(msg, ofs, (y+i >= 0));
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);
80 // Bottom line is rendered by the prompt
83 size_t WordBreak(const char *Line, size_t avail)
85 // If sufficient space, don't need to break on a word
86 if( strlen(Line) < avail )
89 // Search backwards from end of space for a non-alpha-numeric character
91 while( ret > 0 && isalnum(Line[ret]) )
94 // if one wasn't found in a sane area, just split
95 if( ret < avail-20 || ret == 0 )
101 size_t Windows_int_PaintMessagePrefix(const tMessage *Message, bool EnablePrint)
105 unsigned long seconds_today = Message->Timestamp/1000 % (24 * 3600);
107 printf("%02i:%02i:%02i ", seconds_today/3600, (seconds_today/60)%60, seconds_today%60);
109 len += snprintf(NULL, 0, "%02i:%02i:%02i ", seconds_today/3600, (seconds_today/60)%60, seconds_today%60);
112 switch(Message->Class)
117 case MSG_CLASS_CLIENT:
119 format = "[%s] -!- ";
126 case MSG_CLASS_MESSAGE:
129 case MSG_CLASS_ACTION:
135 len += printf(format, Message->Source);
137 len += snprintf(NULL, 0, format, Message->Source);
142 size_t Windows_int_PaintMessageLine(const tMessage *Message, size_t Offset, bool EnablePrint)
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");
150 _SysDebug("Windows_int_PaintMessageLine: Message->Data=\"%.*s\"+\"%s\"",
151 (int)Offset, Message->Data, Message->Data+Offset);
153 size_t avail = giTerminal_Width - Message->PrefixLen;
154 const char *msg_data = Message->Data + Offset;
155 int used = WordBreak(msg_data, avail);
160 Windows_int_PaintMessagePrefix(Message, true);
162 for(int i = 0; i < Message->PrefixLen; i ++)
164 //printf("\x1b[%iC", Message->PrefixLen);
166 printf("%.*s", used, msg_data);
169 _SysDebug("used(%i) >= strlen(msg_data)(%i)", used, strlen(msg_data));
170 if( used >= strlen(msg_data) )
173 return Offset + used;
176 int Windows_int_GetMessageLines(const tMessage *Message)
178 assert(Message->PrefixLen);
179 const size_t avail = giTerminal_Height - Message->PrefixLen;
180 const size_t msglen = strlen(Message->Data);
184 offset += WordBreak(Message->Data+offset, avail);
186 } while(offset < msglen);
190 void Windows_SwitchTo(tWindow *Window)
192 gpCurrentWindow = Window;
196 void Windows_SetStatusServer(tServer *Server)
198 gWindow_Status.Server = Server;
201 tWindow *Windows_GetByIndex(int Index)
204 for( win = gpWindows; win && Index--; win = win->Next )
209 tWindow *Windows_GetByNameEx(tServer *Server, const char *Name, bool CreateAllowed)
211 tWindow *ret, *prev = NULL;
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 )
218 if( ret->Server == Server && strcmp(ret->Name, Name) == 0 )
224 if( !CreateAllowed ) {
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);
235 ret->Next = prev->Next;
238 else { // Shouldn't happen really
239 ret->Next = gpWindows;
243 // printf("Win %i %s:%s created\n", num, Server->Name, Name);
248 tWindow *Windows_GetByName(tServer *Server, const char *Name)
250 return Windows_GetByNameEx(Server, Name, false);
253 tWindow *Window_Create(tServer *Server, const char *Name)
255 return Windows_GetByNameEx(Server, Name, true);
259 tWindow *Window_int_ParseSpecial(const tWindow *Window)
262 return gpCurrentWindow;
263 if( Window == WINDOW_STATUS )
264 return &gWindow_Status;
265 return (tWindow*)Window;
268 const char *Window_GetName(const tWindow *Window) {
269 return Window_int_ParseSpecial(Window)->Name;
271 tServer *Window_GetServer(const tWindow *Window) {
272 return Window_int_ParseSpecial(Window)->Server;
274 bool Window_IsChat(const tWindow *Window) {
275 return Window_int_ParseSpecial(Window) != &gWindow_Status;
278 // -----------------------------
280 // -----------------------------
281 void Window_AppendMessage(tWindow *Window, enum eMessageClass Class, const char *Source, const char *Message, ...)
283 Window = Window_int_ParseSpecial(Window);
287 va_start(args, Message);
288 size_t len = vsnprintf(NULL, 0, Message, args);
291 tMessage *msg = malloc( sizeof(tMessage) + len+1 + (Source?strlen(Source)+1:0) );
295 msg->Source = (Source ? msg->Data + len+1 : NULL);
296 msg->Timestamp = _SysTimestamp();
298 va_start(args, Message);
299 vsnprintf(msg->Data, len+1, Message, args);
303 strcpy(msg->Source, Source);
306 msg->PrefixLen = Windows_int_PaintMessagePrefix(msg, false);
308 msg->Next = Window->Messages;
309 Window->Messages = msg;
311 if( Window == gpCurrentWindow )
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;
318 printf("\33[S"); // Scroll down 1 (free space below)
319 SetCursorPos(giTerminal_Height-2, 1);
320 len = Windows_int_PaintMessageLine(msg, offset, true);
323 printf("\33[u"); // Restore cursor
328 void Window_AppendMsg_Join(tWindow *Window, const char *Usermask)
330 Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s has joined %s", Usermask, Window->Name);
332 void Window_AppendMsg_Quit(tWindow *Window, const char *Usermask, const char *Reason)
334 Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s has joined quit (%s)", Usermask, Reason);
336 void Window_AppendMsg_Part(tWindow *Window, const char *Usermask, const char *Reason)
338 Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s has joined left %s (%s)", Usermask, Window->Name, Reason);
340 void Window_AppendMsg_Topic(tWindow *Window, const char *Topic)
342 Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "Topic of %s is %s", Window->Name, Topic);
344 void Window_AppendMsg_TopicTime(tWindow *Window, const char *User, const char *Timestamp)
346 Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "Topic set by %s at %s", User, Timestamp);
348 void Window_AppendMsg_Kick(tWindow *Window, const char *Operator, const char *Nick, const char *Reason)
350 Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s was kicked from %s by %s [%s]",
351 Nick, Window->Name, Operator, Reason);
353 void Window_AppendMsg_Mode(tWindow *Window, const char *Operator, const char *Flags, const char *Args)
355 Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "mode/%s [%s %s] by %s",
356 Window->Name, Flags, Args, Operator);