Updated copyright notice to 2011
[xboard.git] / winboard / wchat.c
1 /*\r
2  * Chat window (PV)\r
3  *\r
4  * Author: H.G.Muller (August 2009)\r
5  *\r
6  * Copyright 2009, 2010, 2011 Free Software Foundation, Inc.\r
7  *\r
8  * ------------------------------------------------------------------------\r
9  *\r
10  * GNU XBoard is free software: you can redistribute it and/or modify\r
11  * it under the terms of the GNU General Public License as published by\r
12  * the Free Software Foundation, either version 3 of the License, or (at\r
13  * your option) any later version.\r
14  *\r
15  * GNU XBoard is distributed in the hope that it will be useful, but\r
16  * WITHOUT ANY WARRANTY; without even the implied warranty of\r
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
18  * General Public License for more details.\r
19  *\r
20  * You should have received a copy of the GNU General Public License\r
21  * along with this program. If not, see http://www.gnu.org/licenses/.  *\r
22  *\r
23  *------------------------------------------------------------------------\r
24  ** See the file ChangeLog for a revision history.  */\r
25 \r
26 #include "config.h"\r
27 \r
28 #include <windows.h> /* required for all Windows applications */\r
29 #include <richedit.h>\r
30 #include <stdio.h>\r
31 #include <stdlib.h>\r
32 #include <malloc.h>\r
33 #include <commdlg.h>\r
34 #include <dlgs.h>\r
35 #include <Windowsx.h>\r
36 \r
37 #include "common.h"\r
38 #include "frontend.h"\r
39 #include "winboard.h"\r
40 #include "backend.h"\r
41 \r
42 #include "wsnap.h"\r
43 \r
44 int chatCount;\r
45 static int onTop;\r
46 extern char chatPartner[MAX_CHAT][MSG_SIZ];\r
47 HANDLE chatHandle[MAX_CHAT];\r
48 static WNDPROC chatInputWindowProc;\r
49 \r
50 void SendToICS P((char *s));\r
51 void ChatPopUp P((char *s));\r
52 void ChatPopDown();\r
53 \r
54 /* Imports from backend.c */\r
55 extern int opponentKibitzes;\r
56 \r
57 /* Imports from winboard.c */\r
58 VOID SaveInHistory(char *cmd);\r
59 char *PrevInHistory(char *cmd);\r
60 char *NextInHistory();\r
61 extern HWND ChatDialog;\r
62 \r
63 extern HINSTANCE hInst;\r
64 extern HWND hwndConsole;\r
65 extern char ics_handle[];\r
66 \r
67 extern WindowPlacement wpChat[MAX_CHAT];\r
68 extern WindowPlacement wpConsole;\r
69 \r
70 extern BoardSize boardSize;\r
71 \r
72 /* Module variables */\r
73 #define H_MARGIN            5\r
74 #define V_MARGIN            5\r
75 \r
76 // front end, although we might make GetWindowRect front end instead\r
77 static int GetControlWidth( HWND hDlg, int id )\r
78 {\r
79     RECT rc;\r
80 \r
81     GetWindowRect( GetDlgItem( hDlg, id ), &rc );\r
82 \r
83     return rc.right - rc.left;\r
84 }\r
85 \r
86 // front end?\r
87 static int GetControlHeight( HWND hDlg, int id )\r
88 {\r
89     RECT rc;\r
90 \r
91     GetWindowRect( GetDlgItem( hDlg, id ), &rc );\r
92 \r
93     return rc.bottom - rc.top;\r
94 }\r
95 \r
96 static void SetControlPos( HWND hDlg, int id, int x, int y, int width, int height )\r
97 {\r
98     HWND hControl = GetDlgItem( hDlg, id );\r
99 \r
100     SetWindowPos( hControl, HWND_TOP, x, y, width, height, SWP_NOZORDER );\r
101 }\r
102 \r
103 // Also here some of the size calculations should go to the back end, and their actual application to a front-end routine\r
104 static void ResizeWindowControls( HWND hDlg )\r
105 {\r
106     RECT rc;\r
107     int clientWidth;\r
108     int clientHeight;\r
109     int maxControlWidth;\r
110     int buttonWidth, buttonHeight;\r
111 \r
112     /* Initialize variables */\r
113     GetClientRect( hDlg, &rc );\r
114 \r
115     clientWidth = rc.right - rc.left;\r
116     clientHeight = rc.bottom - rc.top;\r
117 \r
118     maxControlWidth = clientWidth - 2*H_MARGIN;\r
119     buttonWidth  = GetControlWidth(hDlg, IDC_Send);\r
120     buttonHeight = GetControlHeight(hDlg, IDC_Send);\r
121 \r
122     /* Resize controls */\r
123     SetControlPos( hDlg, IDC_Clear, maxControlWidth+H_MARGIN-2*buttonWidth-5, V_MARGIN, buttonWidth, buttonHeight );\r
124     SetControlPos( hDlg, IDC_Send, maxControlWidth+H_MARGIN-buttonWidth, V_MARGIN, buttonWidth, buttonHeight );\r
125     SetControlPos( hDlg, IDC_ChatMemo, H_MARGIN, 2*V_MARGIN+buttonHeight, maxControlWidth, clientHeight-3*V_MARGIN-2*buttonHeight );\r
126     SetControlPos( hDlg, OPT_ChatInput, H_MARGIN, clientHeight-V_MARGIN-buttonHeight, maxControlWidth, buttonHeight );\r
127 \r
128 //    InvalidateRect( GetDlgItem(hDlg,IDC_EngineMemo1), NULL, FALSE );\r
129 //    InvalidateRect( GetDlgItem(hDlg,IDC_EngineMemo2), NULL, FALSE );\r
130 }\r
131 \r
132 // front end. Actual printing of PV lines into the output field\r
133 static void InsertIntoMemo( HANDLE hDlg, char * text )\r
134 {\r
135     HANDLE hMemo = GetDlgItem(hDlg, IDC_ChatMemo);\r
136 \r
137     SendMessage( hMemo, EM_SETSEL, 1000000, 1000000 );\r
138 \r
139     SendMessage( hMemo, EM_REPLACESEL, (WPARAM) FALSE, (LPARAM) text );\r
140     SendMessage( hMemo, EM_SCROLLCARET, 0, 0);\r
141 }\r
142 \r
143 LRESULT CALLBACK\r
144 InterceptArrowKeys(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)\r
145 {\r
146   char buf[MSG_SIZ];\r
147   char *p;\r
148   CHARRANGE sel;\r
149 \r
150   switch (message) {\r
151   case WM_KEYDOWN: // cloned from ConsoleInputSubClass()\r
152     switch (wParam) {\r
153     case VK_UP:\r
154       GetWindowText(hwnd, buf, MSG_SIZ);\r
155       p = PrevInHistory(buf);\r
156       if (p != NULL) {\r
157         SetWindowText(hwnd, p);\r
158         sel.cpMin = 999999;\r
159         sel.cpMax = 999999;\r
160         SendMessage(hwnd, EM_EXSETSEL, 0, (LPARAM)&sel);\r
161         return 0;\r
162       }\r
163       break;\r
164     case VK_DOWN:\r
165       p = NextInHistory();\r
166       if (p != NULL) {\r
167         SetWindowText(hwnd, p);\r
168         sel.cpMin = 999999;\r
169         sel.cpMax = 999999;\r
170         SendMessage(hwnd, EM_EXSETSEL, 0, (LPARAM)&sel);\r
171         return 0;\r
172       }\r
173       break;\r
174     }\r
175   }\r
176   return (*chatInputWindowProc)(hwnd, message, wParam, lParam);\r
177 }\r
178 \r
179 // This seems pure front end\r
180 LRESULT CALLBACK ChatProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )\r
181 {\r
182     static SnapData sd;\r
183     char buf[MSG_SIZ], mess[MSG_SIZ];\r
184     int partner = -1, i, x, y;\r
185     static BOOL filterHasFocus[MAX_CHAT];\r
186     WORD wMask;\r
187     HWND hMemo;\r
188 \r
189     for(i=0; i<MAX_CHAT; i++) if(hDlg == chatHandle[i]) { partner = i; break; }\r
190 \r
191     switch (message) {\r
192     case WM_INITDIALOG:\r
193         Translate(hDlg, DLG_Chat);\r
194         if(partner<0) {\r
195                 for(i=0; i<MAX_CHAT; i++) if(chatHandle[i] == NULL) { partner = i; break; }\r
196                 chatHandle[partner] = hDlg;\r
197                 snprintf(buf, MSG_SIZ, T_("Chat Window %s"), ics_handle[0] ? ics_handle : first.tidy);\r
198                 SetWindowText(hDlg, buf);\r
199         }\r
200         for(i=0; i<MAX_CHAT; i++) if(chatHandle[i]) {\r
201             if(i == partner) continue;\r
202             // set our button in other open chats\r
203             SetDlgItemText(chatHandle[i], IDC_Focus1+partner-(i<partner), chatPartner[partner]);\r
204             EnableWindow( GetDlgItem(chatHandle[i], IDC_Focus1+partner-(i<partner)), 1 );\r
205             // and buttons for other chats in ours\r
206             SetDlgItemText(hDlg, IDC_Focus1+i-(i>partner), chatPartner[i]);\r
207         } else EnableWindow( GetDlgItem(hDlg, IDC_Focus1+i-(i>partner)), 0 );\r
208         for(i=0; i<MAX_CHAT-1; i++) { Button_SetStyle(GetDlgItem(hDlg, IDC_Focus1+i), BS_PUSHBUTTON|BS_LEFT, TRUE); }\r
209         x = wpConsole.x; y = wpConsole.y; EnsureOnScreen(&x, &y, 0, 0);\r
210         SetWindowPos(hDlg, NULL, x, y, 0, 0, SWP_NOZORDER|SWP_NOSIZE);\r
211         SendMessage( GetDlgItem(hDlg, IDC_ChatPartner), // [HGM] clickbox: initialize with requested handle\r
212                         WM_SETTEXT, 0, (LPARAM) chatPartner[partner] );\r
213         filterHasFocus[partner] = TRUE;\r
214         onTop = partner; // a newly opened box becomes top one\r
215         if(chatPartner[partner][0]) {\r
216             filterHasFocus[partner] = FALSE;\r
217             SetFocus( GetDlgItem(hDlg, OPT_ChatInput) );\r
218         }\r
219         hMemo = GetDlgItem(hDlg, IDC_ChatMemo);\r
220         wMask = (WORD) SendMessage(hMemo, EM_GETEVENTMASK, 0, 0L);\r
221         SendMessage(hMemo, EM_SETEVENTMASK, 0, wMask | ENM_LINK);\r
222         SendMessage(hMemo, EM_AUTOURLDETECT, TRUE, 0L);\r
223         chatInputWindowProc = (WNDPROC) // cloned from ConsoleWndProc(). Assume they all share same proc.\r
224               SetWindowLong(GetDlgItem(hDlg, OPT_ChatInput), GWL_WNDPROC, (LONG) InterceptArrowKeys);\r
225         return FALSE;\r
226 \r
227     case WM_NOTIFY:\r
228       if (((NMHDR*)lParam)->code == EN_LINK)\r
229       {\r
230         ENLINK *pLink = (ENLINK*)lParam;\r
231         if (pLink->msg == WM_LBUTTONUP)\r
232         {\r
233           TEXTRANGE tr;\r
234 \r
235           tr.chrg = pLink->chrg;\r
236           tr.lpstrText = malloc(1+tr.chrg.cpMax-tr.chrg.cpMin);\r
237           SendMessage( GetDlgItem(hDlg, IDC_ChatMemo), EM_GETTEXTRANGE, 0, (LPARAM)&tr);\r
238           ShellExecute(NULL, "open", tr.lpstrText, NULL, NULL, SW_SHOW);\r
239           free(tr.lpstrText);\r
240         }\r
241       }\r
242     break;\r
243 \r
244     case WM_COMMAND:\r
245       /*\r
246         [AS]\r
247         If <Enter> is pressed while editing the filter, it's better to apply\r
248         the filter rather than selecting the current game.\r
249       */\r
250       if( LOWORD(wParam) == IDC_ChatPartner ) {\r
251           switch( HIWORD(wParam) ) {\r
252           case EN_SETFOCUS:\r
253               filterHasFocus[partner] = TRUE;\r
254               break;\r
255           case EN_KILLFOCUS:\r
256               filterHasFocus[partner] = FALSE;\r
257               break;\r
258           }\r
259       }\r
260 \r
261       if( filterHasFocus[partner] && (LOWORD(wParam) == IDC_Send) ) {\r
262           SetFocus(GetDlgItem(hDlg, OPT_ChatInput));\r
263           wParam = IDC_Change;\r
264       }\r
265       /* [AS] End command replacement */\r
266 \r
267         switch (LOWORD(wParam)) {\r
268 \r
269         case IDCANCEL: /* let Esc key switch focus back to console */\r
270             SetFocus(GetDlgItem(hwndConsole, OPT_ConsoleInput));\r
271             break;\r
272 \r
273         case IDC_Clear:\r
274             SendMessage( GetDlgItem(hDlg, IDC_ChatMemo), WM_SETTEXT, 0, (LPARAM) "" );\r
275             break;\r
276 \r
277         case IDC_Change:\r
278             GetDlgItemText(hDlg, IDC_ChatPartner, chatPartner[partner], MSG_SIZ);\r
279             for(i=0; i<MAX_CHAT; i++) if(chatHandle[i] && i != partner) {\r
280               // set our button in other open chats\r
281               SetDlgItemText(chatHandle[i], IDC_Focus1+partner-(i<partner), chatPartner[partner]);\r
282             }\r
283             break;\r
284 \r
285         case IDC_Send:\r
286             GetDlgItemText(hDlg, OPT_ChatInput, mess, MSG_SIZ);\r
287             SetDlgItemText(hDlg, OPT_ChatInput, "");\r
288             // from here on it could be back-end\r
289             SaveInHistory(mess);\r
290             if(!strcmp("whispers", chatPartner[partner]))\r
291               snprintf(buf, MSG_SIZ, "whisper %s\n", mess); // WHISPER box uses "whisper" to send\r
292             else if(!strcmp("shouts", chatPartner[partner]))\r
293               snprintf(buf, MSG_SIZ, "shout %s\n", mess); // SHOUT box uses "shout" to send\r
294             else {\r
295                 if(!atoi(chatPartner[partner])) {\r
296                   snprintf(buf, MSG_SIZ, "> %s\r\n", mess); // echo only tells to handle, not channel\r
297                 InsertIntoMemo(hDlg, buf);\r
298                 snprintf(buf, MSG_SIZ, "xtell %s %s\n", chatPartner[partner], mess);\r
299                 } else\r
300                   snprintf(buf, MSG_SIZ, "tell %s %s\n", chatPartner[partner], mess);\r
301             }\r
302             SendToICS(buf);\r
303             break;\r
304 \r
305         case IDC_Focus1:\r
306         case IDC_Focus2:\r
307         case IDC_Focus3:\r
308         case IDC_Focus4:\r
309             i = LOWORD(wParam) - IDC_Focus1;\r
310             if(i >= partner) i++;\r
311             onTop = i;\r
312             SetFocus(GetDlgItem(hDlg, IDC_Send));\r
313             if(chatHandle[i]) {\r
314                 int j;\r
315                 for(j=0; j<MAX_CHAT; j++) if(i != j && chatHandle[j])\r
316                     Button_SetState(GetDlgItem(chatHandle[j], IDC_Focus1+i-(j<i)), FALSE);\r
317                 SetFocus(GetDlgItem(chatHandle[i], OPT_ChatInput));\r
318             }\r
319             break;\r
320 \r
321         default:\r
322           break;\r
323         }\r
324 \r
325         break;\r
326 \r
327     case WM_CLOSE:\r
328         chatHandle[partner] = 0;\r
329         chatPartner[partner][0] = 0;\r
330         ChatPopDown();\r
331         for(i=0; i<MAX_CHAT; i++) if(chatHandle[i] && i != partner) {\r
332             // set our button in other open chats\r
333             SetDlgItemText(chatHandle[i], IDC_Focus1+partner-(i<partner), "");\r
334             EnableWindow( GetDlgItem(chatHandle[i], IDC_Focus1+partner-(i<partner)), 0 );\r
335         }\r
336         EndDialog(hDlg, TRUE);\r
337         break;\r
338 \r
339     case WM_SIZE:\r
340         ResizeWindowControls( hDlg );\r
341         break;\r
342 \r
343     case WM_ENTERSIZEMOVE:\r
344         return OnEnterSizeMove( &sd, hDlg, wParam, lParam );\r
345 \r
346     case WM_SIZING:\r
347         return OnSizing( &sd, hDlg, wParam, lParam );\r
348 \r
349     case WM_MOVING:\r
350         return OnMoving( &sd, hDlg, wParam, lParam );\r
351 \r
352     case WM_EXITSIZEMOVE:\r
353         return OnExitSizeMove( &sd, hDlg, wParam, lParam );\r
354     }\r
355 \r
356     return FALSE;\r
357 }\r
358 \r
359 // front end\r
360 void ChatPopUp(char *icsHandle)\r
361 {\r
362   FARPROC lpProc;\r
363   int i, partner = -1;\r
364 \r
365   CheckMenuItem(GetMenu(hwndMain), IDM_NewChat, MF_CHECKED);\r
366   for(i=0; i<MAX_CHAT; i++) if(chatHandle[i] == NULL) { partner = i; break; }\r
367   if(partner == -1) { DisplayError("You first have to close a Chat Box\nbefore you can open a new one", 0); return; }\r
368   if(icsHandle) // [HGM] clickbox set handle in advance\r
369     safeStrCpy(chatPartner[partner], icsHandle,\r
370                sizeof(chatPartner[partner])/sizeof(chatPartner[partner][0]) );\r
371   else chatPartner[partner][0] = NULLCHAR;\r
372   chatCount++;\r
373 \r
374     lpProc = MakeProcInstance( (FARPROC) ChatProc, hInst );\r
375 \r
376     /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */\r
377     CreateDialog( hInst, MAKEINTRESOURCE(DLG_Chat), hwndConsole, (DLGPROC)lpProc );\r
378 \r
379     FreeProcInstance(lpProc);\r
380 \r
381 }\r
382 \r
383 // front end\r
384 void ChatPopDown()\r
385 {\r
386   if(--chatCount <= 0)\r
387         CheckMenuItem(GetMenu(hwndMain), IDM_NewChat, MF_UNCHECKED);\r
388 }\r
389 \r
390 \r
391 //------------------------ pure back-end routines -------------------------------\r
392 \r
393 void OutputChatMessage(int partner, char *text)\r
394 {\r
395         int j, n = strlen(text);\r
396 \r
397         if(!chatHandle[partner]) return;\r
398         text[n+1] = 0; text[n] = '\n'; text[n-1] = '\r'; // Needs CR to not lose line breaks on copy-paste\r
399         InsertIntoMemo(chatHandle[partner], text);\r
400         if(partner != onTop) for(j=0; j<MAX_CHAT; j++) if(j != partner && chatHandle[j])\r
401             Button_SetState(GetDlgItem(chatHandle[j], IDC_Focus1+partner-(j<partner)), TRUE);\r
402 }\r