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