8d53d7c70c70168ca9cf292c9f5b8cb229a20f56
[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 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 \r
36 #include "common.h"\r
37 #include "frontend.h"\r
38 #include "winboard.h"\r
39 #include "backend.h"\r
40 \r
41 #include "wsnap.h"\r
42 \r
43 int chatCount;\r
44 extern char chatPartner[MAX_CHAT][MSG_SIZ];\r
45 HANDLE chatHandle[MAX_CHAT];\r
46 static WNDPROC chatInputWindowProc;\r
47 \r
48 void SendToICS P((char *s));\r
49 void ChatPopUp P((char *s));\r
50 void ChatPopDown();\r
51 \r
52 /* Imports from backend.c */\r
53 extern int opponentKibitzes;\r
54 \r
55 /* Imports from winboard.c */\r
56 VOID SaveInHistory(char *cmd);\r
57 char *PrevInHistory(char *cmd);\r
58 char *NextInHistory();\r
59 extern HWND ChatDialog;\r
60 \r
61 extern HINSTANCE hInst;\r
62 extern HWND hwndMain;\r
63 \r
64 extern WindowPlacement wpChat[MAX_CHAT];\r
65 \r
66 extern BoardSize boardSize;\r
67 \r
68 /* Module variables */\r
69 #define H_MARGIN            5\r
70 #define V_MARGIN            5\r
71 \r
72 // front end, although we might make GetWindowRect front end instead\r
73 static int GetControlWidth( HWND hDlg, int id )\r
74 {\r
75     RECT rc;\r
76 \r
77     GetWindowRect( GetDlgItem( hDlg, id ), &rc );\r
78 \r
79     return rc.right - rc.left;\r
80 }\r
81 \r
82 // front end?\r
83 static int GetControlHeight( HWND hDlg, int id )\r
84 {\r
85     RECT rc;\r
86 \r
87     GetWindowRect( GetDlgItem( hDlg, id ), &rc );\r
88 \r
89     return rc.bottom - rc.top;\r
90 }\r
91 \r
92 static void SetControlPos( HWND hDlg, int id, int x, int y, int width, int height )\r
93 {\r
94     HWND hControl = GetDlgItem( hDlg, id );\r
95 \r
96     SetWindowPos( hControl, HWND_TOP, x, y, width, height, SWP_NOZORDER );\r
97 }\r
98 \r
99 // Also here some of the size calculations should go to the back end, and their actual application to a front-end routine\r
100 static void ResizeWindowControls( HWND hDlg )\r
101 {\r
102     RECT rc;\r
103     int clientWidth;\r
104     int clientHeight;\r
105     int maxControlWidth;\r
106     int buttonWidth, buttonHeight;\r
107 \r
108     /* Initialize variables */\r
109     GetClientRect( hDlg, &rc );\r
110 \r
111     clientWidth = rc.right - rc.left;\r
112     clientHeight = rc.bottom - rc.top;\r
113 \r
114     maxControlWidth = clientWidth - 2*H_MARGIN;\r
115     buttonWidth  = GetControlWidth(hDlg, IDC_Send);\r
116     buttonHeight = GetControlHeight(hDlg, IDC_Send);\r
117 \r
118     /* Resize controls */\r
119     SetControlPos( hDlg, IDC_Clear, maxControlWidth+H_MARGIN-2*buttonWidth-5, V_MARGIN, buttonWidth, buttonHeight );\r
120     SetControlPos( hDlg, IDC_Send, maxControlWidth+H_MARGIN-buttonWidth, V_MARGIN, buttonWidth, buttonHeight );\r
121     SetControlPos( hDlg, IDC_ChatMemo, H_MARGIN, 2*V_MARGIN+buttonHeight, maxControlWidth, clientHeight-3*V_MARGIN-2*buttonHeight );\r
122     SetControlPos( hDlg, OPT_ChatInput, H_MARGIN, clientHeight-V_MARGIN-buttonHeight, maxControlWidth, buttonHeight );\r
123 \r
124 //    InvalidateRect( GetDlgItem(hDlg,IDC_EngineMemo1), NULL, FALSE );\r
125 //    InvalidateRect( GetDlgItem(hDlg,IDC_EngineMemo2), NULL, FALSE );\r
126 }\r
127 \r
128 // front end. Actual printing of PV lines into the output field\r
129 static void InsertIntoMemo( HANDLE hDlg, char * text )\r
130 {\r
131     HANDLE hMemo = GetDlgItem(hDlg, IDC_ChatMemo);\r
132 \r
133     SendMessage( hMemo, EM_SETSEL, 1000000, 1000000 );\r
134 \r
135     SendMessage( hMemo, EM_REPLACESEL, (WPARAM) FALSE, (LPARAM) text );\r
136     SendMessage( hMemo, EM_SCROLLCARET, 0, 0);\r
137 }\r
138 \r
139 LRESULT CALLBACK\r
140 InterceptArrowKeys(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)\r
141 {\r
142   char buf[MSG_SIZ];\r
143   char *p;\r
144   CHARRANGE sel;\r
145 \r
146   switch (message) {\r
147   case WM_KEYDOWN: // cloned from ConsoleInputSubClass()\r
148     switch (wParam) {\r
149     case VK_UP:\r
150       GetWindowText(hwnd, buf, MSG_SIZ);\r
151       p = PrevInHistory(buf);\r
152       if (p != NULL) {\r
153         SetWindowText(hwnd, p);\r
154         sel.cpMin = 999999;\r
155         sel.cpMax = 999999;\r
156         SendMessage(hwnd, EM_EXSETSEL, 0, (LPARAM)&sel);\r
157         return 0;\r
158       }\r
159       break;\r
160     case VK_DOWN:\r
161       p = NextInHistory();\r
162       if (p != NULL) {\r
163         SetWindowText(hwnd, p);\r
164         sel.cpMin = 999999;\r
165         sel.cpMax = 999999;\r
166         SendMessage(hwnd, EM_EXSETSEL, 0, (LPARAM)&sel);\r
167         return 0;\r
168       }\r
169       break;\r
170     }\r
171   }\r
172   return (*chatInputWindowProc)(hwnd, message, wParam, lParam);\r
173 }\r
174 \r
175 // This seems pure front end\r
176 LRESULT CALLBACK ChatProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )\r
177 {\r
178     static SnapData sd;\r
179     char buf[MSG_SIZ], mess[MSG_SIZ];\r
180     int partner = -1, i;\r
181     static BOOL filterHasFocus[MAX_CHAT];\r
182     WORD wMask;\r
183     HWND hMemo;\r
184 \r
185     for(i=0; i<MAX_CHAT; i++) if(hDlg == chatHandle[i]) { partner = i; break; }\r
186 \r
187     switch (message) {\r
188     case WM_INITDIALOG:\r
189         if(partner<0) {\r
190                 for(i=0; i<MAX_CHAT; i++) if(chatHandle[i] == NULL) { partner = i; break; }\r
191                 chatHandle[partner] = hDlg;\r
192                 sprintf(buf, "Chat Window %s", first.tidy);\r
193                 SetWindowText(hDlg, buf);\r
194         }\r
195 //      chatPartner[partner][0] = 0;\r
196         SendMessage( GetDlgItem(hDlg, IDC_ChatPartner), // [HGM] clickbox: initialize with requested handle\r
197                         WM_SETTEXT, 0, (LPARAM) chatPartner[partner] );\r
198         filterHasFocus[partner] = TRUE;\r
199         if(chatPartner[partner][0]) {\r
200             filterHasFocus[partner] = FALSE;\r
201             SetFocus( GetDlgItem(hDlg, OPT_ChatInput) );\r
202         }\r
203         hMemo = GetDlgItem(hDlg, IDC_ChatMemo);\r
204         wMask = (WORD) SendMessage(hMemo, EM_GETEVENTMASK, 0, 0L);\r
205         SendMessage(hMemo, EM_SETEVENTMASK, 0, wMask | ENM_LINK);\r
206         SendMessage(hMemo, EM_AUTOURLDETECT, TRUE, 0L);\r
207         chatInputWindowProc = (WNDPROC) // cloned from ConsoleWndProc(). Assume they all share same proc.\r
208               SetWindowLong(GetDlgItem(hDlg, OPT_ChatInput), GWL_WNDPROC, (LONG) InterceptArrowKeys);\r
209         return FALSE;\r
210 \r
211     case WM_NOTIFY:\r
212       if (((NMHDR*)lParam)->code == EN_LINK)\r
213       {\r
214         ENLINK *pLink = (ENLINK*)lParam;\r
215         if (pLink->msg == WM_LBUTTONUP)\r
216         {\r
217           TEXTRANGE tr;\r
218 \r
219           tr.chrg = pLink->chrg;\r
220           tr.lpstrText = malloc(1+tr.chrg.cpMax-tr.chrg.cpMin);\r
221           SendMessage( GetDlgItem(hDlg, IDC_ChatMemo), EM_GETTEXTRANGE, 0, (LPARAM)&tr);\r
222           ShellExecute(NULL, "open", tr.lpstrText, NULL, NULL, SW_SHOW);\r
223           free(tr.lpstrText);\r
224         }\r
225       }\r
226     break;\r
227 \r
228     case WM_COMMAND:\r
229       /* \r
230         [AS]\r
231         If <Enter> is pressed while editing the filter, it's better to apply\r
232         the filter rather than selecting the current game.\r
233       */\r
234       if( LOWORD(wParam) == IDC_ChatPartner ) {\r
235           switch( HIWORD(wParam) ) {\r
236           case EN_SETFOCUS:\r
237               filterHasFocus[partner] = TRUE;\r
238               break;\r
239           case EN_KILLFOCUS:\r
240               filterHasFocus[partner] = FALSE;\r
241               break;\r
242           }\r
243       }\r
244 \r
245       if( filterHasFocus[partner] && (LOWORD(wParam) == IDC_Send) ) {\r
246           SetFocus(GetDlgItem(hDlg, OPT_ChatInput));\r
247           wParam = IDC_Change;\r
248       }\r
249       /* [AS] End command replacement */\r
250 \r
251         switch (LOWORD(wParam)) {\r
252 \r
253         case IDCANCEL:\r
254             chatHandle[partner] = 0;\r
255             chatPartner[partner][0] = 0;\r
256             ChatPopDown();\r
257             EndDialog(hDlg, TRUE);\r
258             break;\r
259 \r
260         case IDC_Clear:\r
261             SendMessage( GetDlgItem(hDlg, IDC_ChatMemo), WM_SETTEXT, 0, (LPARAM) "" );\r
262             break;\r
263 \r
264         case IDC_Change:\r
265             GetDlgItemText(hDlg, IDC_ChatPartner, chatPartner[partner], MSG_SIZ);\r
266             break;\r
267 \r
268         case IDC_Send:\r
269             GetDlgItemText(hDlg, OPT_ChatInput, mess, MSG_SIZ);\r
270             SetDlgItemText(hDlg, OPT_ChatInput, "");\r
271             // from here on it could be back-end\r
272             SaveInHistory(mess);\r
273             if(!strcmp("WHISPER", chatPartner[partner]))\r
274                 sprintf(buf, "whisper %s\n", mess); // WHISPER box uses "whisper" to send\r
275             else {\r
276                 if(!atoi(chatPartner[partner])) {\r
277                     sprintf(buf, "> %s\r\n", mess); // echo only tells to handle, not channel\r
278                 InsertIntoMemo(hDlg, buf);\r
279                 sprintf(buf, "xtell %s %s\n", chatPartner[partner], mess);\r
280                 } else\r
281                 sprintf(buf, "tell %s %s\n", chatPartner[partner], mess);\r
282             }\r
283             SendToICS(buf);\r
284             break;\r
285 \r
286         default:\r
287           break;\r
288         }\r
289 \r
290         break;\r
291 \r
292     case WM_CLOSE:\r
293         chatHandle[partner] = 0;\r
294         chatPartner[partner][0] = 0;\r
295         ChatPopDown();\r
296         EndDialog(hDlg, TRUE);\r
297         break;\r
298 \r
299     case WM_SIZE:\r
300         ResizeWindowControls( hDlg );\r
301         break;\r
302 \r
303     case WM_ENTERSIZEMOVE:\r
304         return OnEnterSizeMove( &sd, hDlg, wParam, lParam );\r
305 \r
306     case WM_SIZING:\r
307         return OnSizing( &sd, hDlg, wParam, lParam );\r
308 \r
309     case WM_MOVING:\r
310         return OnMoving( &sd, hDlg, wParam, lParam );\r
311 \r
312     case WM_EXITSIZEMOVE:\r
313         return OnExitSizeMove( &sd, hDlg, wParam, lParam );\r
314     }\r
315 \r
316     return FALSE;\r
317 }\r
318 \r
319 // front end\r
320 void ChatPopUp(char *icsHandle)\r
321 {\r
322   FARPROC lpProc;\r
323   int i, partner = -1;\r
324   \r
325   CheckMenuItem(GetMenu(hwndMain), IDM_NewChat, MF_CHECKED);\r
326   for(i=0; i<MAX_CHAT; i++) if(chatHandle[i] == NULL) { partner = i; break; }\r
327   if(partner == -1) { DisplayError("You first have to close a Chat Box\nbefore you can open a new one", 0); return; }\r
328   if(icsHandle) // [HGM] clickbox set handle in advance\r
329        strcpy(chatPartner[partner], icsHandle);\r
330   else chatPartner[partner][0] = NULLCHAR;\r
331   chatCount++;\r
332 \r
333     lpProc = MakeProcInstance( (FARPROC) ChatProc, hInst );\r
334 \r
335     /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */\r
336     CreateDialog( hInst, MAKEINTRESOURCE(DLG_Chat), hwndMain, (DLGPROC)lpProc );\r
337 \r
338     FreeProcInstance(lpProc);\r
339 \r
340 }\r
341 \r
342 // front end\r
343 void ChatPopDown()\r
344 {\r
345   if(--chatCount <= 0)\r
346         CheckMenuItem(GetMenu(hwndMain), IDM_NewChat, MF_UNCHECKED);\r
347 }\r
348 \r
349 \r
350 //------------------------ pure back-end routines -------------------------------\r
351 \r
352 void OutputChatMessage(int partner, char *text)\r
353 {\r
354         if(!chatHandle[partner]) return;\r
355 \r
356         int n = strlen(text);\r
357         text[n+1] = 0; text[n] = '\n'; text[n-1] = '\r'; // Needs CR to not lose line breaks on copy-paste\r
358         InsertIntoMemo(chatHandle[partner], text);\r
359 }\r