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