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 if( Message->Data[Offset] == '\0' ) {
148 size_t avail = giTerminal_Width - Message->PrefixLen;
149 const char *msg_data = Message->Data + Offset;
150 int used = WordBreak(msg_data+Offset, avail);
155 Windows_int_PaintMessagePrefix(Message, true);
157 printf("\x1b[%iC", Message->PrefixLen);
158 printf("%.*s", used, msg_data);
161 if( msg_data[used] == '\0' )
164 return Offset + used;
167 int Windows_int_GetMessageLines(const tMessage *Message)
169 assert(Message->PrefixLen);
170 const size_t avail = giTerminal_Height - Message->PrefixLen;
171 const size_t msglen = strlen(Message->Data);
175 offset += WordBreak(Message->Data+offset, avail);
177 } while(offset < msglen);
181 void Windows_SwitchTo(tWindow *Window)
183 gpCurrentWindow = Window;
187 void Windows_SetStatusServer(tServer *Server)
189 gWindow_Status.Server = Server;
192 tWindow *Windows_GetByIndex(int Index)
195 for( win = gpWindows; win && Index--; win = win->Next )
200 tWindow *Windows_GetByNameEx(tServer *Server, const char *Name, bool CreateAllowed)
202 tWindow *ret, *prev = NULL;
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 )
209 if( ret->Server == Server && strcmp(ret->Name, Name) == 0 )
215 if( !CreateAllowed ) {
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);
226 ret->Next = prev->Next;
229 else { // Shouldn't happen really
230 ret->Next = gpWindows;
234 // printf("Win %i %s:%s created\n", num, Server->Name, Name);
239 tWindow *Windows_GetByName(tServer *Server, const char *Name)
241 return Windows_GetByNameEx(Server, Name, false);
244 tWindow *Window_Create(tServer *Server, const char *Name)
246 return Windows_GetByNameEx(Server, Name, true);
250 tWindow *Window_int_ParseSpecial(const tWindow *Window)
253 return gpCurrentWindow;
254 if( Window == WINDOW_STATUS )
255 return &gWindow_Status;
256 return (tWindow*)Window;
259 const char *Window_GetName(const tWindow *Window) {
260 return Window_int_ParseSpecial(Window)->Name;
262 tServer *Window_GetServer(const tWindow *Window) {
263 return Window_int_ParseSpecial(Window)->Server;
265 bool Window_IsChat(const tWindow *Window) {
266 return Window_int_ParseSpecial(Window) != &gWindow_Status;
269 // -----------------------------
271 // -----------------------------
272 void Window_AppendMessage(tWindow *Window, enum eMessageClass Class, const char *Source, const char *Message, ...)
274 Window = Window_int_ParseSpecial(Window);
278 va_start(args, Message);
279 size_t len = vsnprintf(NULL, 0, Message, args);
282 tMessage *msg = malloc( sizeof(tMessage) + len+1 + (Source?strlen(Source)+1:0) );
286 msg->Source = (Source ? msg->Data + len+1 : NULL);
287 msg->Timestamp = _SysTimestamp();
289 va_start(args, Message);
290 vsnprintf(msg->Data, len+1, Message, args);
294 strcpy(msg->Source, Source);
297 msg->PrefixLen = Windows_int_PaintMessagePrefix(msg, false);
299 msg->Next = Window->Messages;
300 Window->Messages = msg;
302 if( Window == gpCurrentWindow )
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;
309 printf("\33[T"); // Scroll down 1 (free space below)
310 SetCursorPos(giTerminal_Height-2, 1);
311 len = Windows_int_PaintMessageLine(Message, offset, true);
314 printf("\33[u"); // Restore cursor
319 void Window_AppendMsg_Join(tWindow *Window, const char *Usermask)
321 Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s has joined %s", Usermask, Window->Name);
323 void Window_AppendMsg_Quit(tWindow *Window, const char *Usermask, const char *Reason)
325 Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s has joined quit (%s)", Usermask, Reason);
327 void Window_AppendMsg_Part(tWindow *Window, const char *Usermask, const char *Reason)
329 Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s has joined left %s (%s)", Usermask, Window->Name, Reason);
331 void Window_AppendMsg_Topic(tWindow *Window, const char *Topic)
333 Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "Topic of %s is %s", Window->Name, Topic);
335 void Window_AppendMsg_TopicTime(tWindow *Window, const char *User, const char *Timestamp)
337 Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "Topic set by %s at %s", User, Timestamp);
339 void Window_AppendMsg_Kick(tWindow *Window, const char *Operator, const char *Nick, const char *Reason)
341 Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "%s was kicked from %s by %s [%s]",
342 Nick, Window->Name, Operator, Reason);
344 void Window_AppendMsg_Mode(tWindow *Window, const char *Operator, const char *Flags, const char *Args)
346 Window_AppendMessage(Window, MSG_CLASS_CLIENT, NULL, "mode/%s [%s %s] by %s",
347 Window->Name, Flags, Args, Operator);