msvcrt: Don't add '\r' character in fputws function.
[wine] / dlls / dinput / config.c
1 /*
2  * Copyright (c) 2011 Lucas Fialho Zawacki
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18
19 #define NONAMELESSUNION
20
21 #include "wine/unicode.h"
22 #include "objbase.h"
23 #include "dinput_private.h"
24 #include "device_private.h"
25 #include "resource.h"
26
27 typedef struct {
28     int nobjects;
29     IDirectInputDevice8W *lpdid;
30     DIDEVICEINSTANCEW ddi;
31     DIDEVICEOBJECTINSTANCEW ddo[256];
32 } DeviceData;
33
34 typedef struct {
35     int ndevices;
36     DeviceData *devices;
37 } DIDevicesData;
38
39 typedef struct {
40     IDirectInput8W *lpDI;
41     LPDIACTIONFORMATW lpdiaf;
42     LPDIACTIONFORMATW original_lpdiaf;
43     DIDevicesData devices_data;
44     int display_only;
45 } ConfigureDevicesData;
46
47 /*
48  * Enumeration callback functions
49  */
50 static BOOL CALLBACK collect_objects(LPCDIDEVICEOBJECTINSTANCEW lpddo, LPVOID pvRef)
51 {
52     DeviceData *data = (DeviceData*) pvRef;
53
54     data->ddo[data->nobjects] = *lpddo;
55
56     data->nobjects++;
57     return DIENUM_CONTINUE;
58 }
59
60 static BOOL CALLBACK count_devices(LPCDIDEVICEINSTANCEW lpddi, IDirectInputDevice8W *lpdid, DWORD dwFlags, DWORD dwRemaining, LPVOID pvRef)
61 {
62     DIDevicesData *data = (DIDevicesData*) pvRef;
63
64     data->ndevices++;
65     return DIENUM_CONTINUE;
66 }
67
68 static BOOL CALLBACK collect_devices(LPCDIDEVICEINSTANCEW lpddi, IDirectInputDevice8W *lpdid, DWORD dwFlags, DWORD dwRemaining, LPVOID pvRef)
69 {
70     DIDevicesData *data = (DIDevicesData*) pvRef;
71     DeviceData *device = &data->devices[data->ndevices];
72     device->lpdid = lpdid;
73     device->ddi = *lpddi;
74
75     IDirectInputDevice_AddRef(lpdid);
76
77     device->nobjects = 0;
78     IDirectInputDevice_EnumObjects(lpdid, collect_objects, (LPVOID) device, DIDFT_ALL);
79
80     data->ndevices++;
81     return DIENUM_CONTINUE;
82 }
83
84 /*
85  * Listview utility functions
86  */
87 static void init_listview_columns(HWND dialog)
88 {
89     HINSTANCE hinstance = (HINSTANCE) GetWindowLongPtrW(dialog, GWLP_HINSTANCE);
90     LVCOLUMNW listColumn;
91     RECT viewRect;
92     int width;
93     WCHAR column[MAX_PATH];
94
95     GetClientRect(GetDlgItem(dialog, IDC_DEVICEOBJECTSLIST), &viewRect);
96     width = (viewRect.right - viewRect.left)/2;
97
98     LoadStringW(hinstance, IDS_OBJECTCOLUMN, column, sizeof(column)/sizeof(column[0]));
99     listColumn.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
100     listColumn.pszText = column;
101     listColumn.cchTextMax = lstrlenW(listColumn.pszText);
102     listColumn.cx = width;
103
104     SendDlgItemMessageW (dialog, IDC_DEVICEOBJECTSLIST, LVM_INSERTCOLUMNW, 0, (LPARAM) &listColumn);
105
106     LoadStringW(hinstance, IDS_ACTIONCOLUMN, column, sizeof(column)/sizeof(column[0]));
107     listColumn.cx = width;
108     listColumn.pszText = column;
109     listColumn.cchTextMax = lstrlenW(listColumn.pszText);
110
111     SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_INSERTCOLUMNW, 1, (LPARAM) &listColumn);
112 }
113
114 static int lv_get_cur_item(HWND dialog)
115 {
116     return SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
117 }
118
119 static int lv_get_item_data(HWND dialog, int index)
120 {
121     LVITEMW item;
122
123     if (index < 0) return -1;
124
125     item.mask = LVIF_PARAM;
126     item.iItem = index;
127     item.iSubItem = 0;
128
129     SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_GETITEMW , 0, (LPARAM)&item);
130
131     return item.lParam;
132 }
133
134 static void lv_set_action(HWND dialog, int item, int action, LPDIACTIONFORMATW lpdiaf)
135 {
136     static const WCHAR no_action[] = {'-','\0'};
137     const WCHAR *action_text = no_action;
138     LVITEMW lvItem;
139
140     if (item < 0) return;
141
142     if (action != -1)
143         action_text = lpdiaf->rgoAction[action].u.lptszActionName;
144
145     /* Keep the action and text in the listview item */
146     lvItem.iItem = item;
147
148     lvItem.mask = LVIF_PARAM;
149     lvItem.iSubItem = 0;
150     lvItem.lParam = (LPARAM) action;
151
152     /* Action index */
153     SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_SETITEMW, 0, (LPARAM) &lvItem);
154
155     lvItem.mask = LVIF_TEXT;
156     lvItem.iSubItem = 1;
157     lvItem.pszText = (WCHAR *)action_text;
158     lvItem.cchTextMax = lstrlenW(lvItem.pszText);
159
160     /* Text */
161     SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_SETITEMW, 0, (LPARAM) &lvItem);
162 }
163
164 /*
165  * Utility functions
166  */
167 static DeviceData* get_cur_device(HWND dialog)
168 {
169     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
170     int sel = SendDlgItemMessageW(dialog, IDC_CONTROLLERCOMBO, CB_GETCURSEL, 0, 0);
171     return &data->devices_data.devices[sel];
172 }
173
174 static LPDIACTIONFORMATW get_cur_lpdiaf(HWND dialog)
175 {
176     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
177     return data->lpdiaf;
178 }
179
180 static int dialog_display_only(HWND dialog)
181 {
182     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
183     return data->display_only;
184 }
185
186 static void init_devices(HWND dialog, IDirectInput8W *lpDI, DIDevicesData *data, LPDIACTIONFORMATW lpdiaf)
187 {
188     int i;
189
190     /* Count devices */
191     data->ndevices = 0;
192     IDirectInput8_EnumDevicesBySemantics(lpDI, NULL, lpdiaf, count_devices, (LPVOID) data, 0);
193
194     /* Allocate devices */
195     data->devices = HeapAlloc(GetProcessHeap(), 0, sizeof(DeviceData) * data->ndevices);
196
197     /* Collect and insert */
198     data->ndevices = 0;
199     IDirectInput8_EnumDevicesBySemantics(lpDI, NULL, lpdiaf, collect_devices, (LPVOID) data, 0);
200
201     for (i=0; i < data->ndevices; i++)
202         SendDlgItemMessageW(dialog, IDC_CONTROLLERCOMBO, CB_ADDSTRING, 0, (LPARAM) data->devices[i].ddi.tszProductName );
203 }
204
205 static void destroy_data(HWND dialog)
206 {
207     int i;
208     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
209     DIDevicesData *devices_data = &data->devices_data;
210
211     /* Free the devices */
212     for (i=0; i < devices_data->ndevices; i++)
213         IDirectInputDevice8_Release(devices_data->devices[i].lpdid);
214
215     HeapFree(GetProcessHeap(), 0, devices_data->devices);
216
217     /* Free the backup LPDIACTIONFORMATW  */
218     HeapFree(GetProcessHeap(), 0, data->original_lpdiaf->rgoAction);
219     HeapFree(GetProcessHeap(), 0, data->original_lpdiaf);
220 }
221
222 static void fill_device_object_list(HWND dialog)
223 {
224     DeviceData *device = get_cur_device(dialog);
225     LPDIACTIONFORMATW lpdiaf = get_cur_lpdiaf(dialog);
226     LVITEMW item;
227     int i, j;
228
229     /* Clean the listview */
230     SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_DELETEALLITEMS, 0, 0);
231
232     /* Add each object */
233     for (i=0; i < device->nobjects; i++)
234     {
235         int action = -1;
236
237         item.mask = LVIF_TEXT | LVIF_PARAM;
238         item.iItem = i;
239         item.iSubItem = 0;
240         item.pszText = device->ddo[i].tszName;
241         item.cchTextMax = lstrlenW(item.pszText);
242
243         /* Add the item */
244         SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_INSERTITEMW, 0, (LPARAM) &item);
245
246         /* Search for an assigned action  for this device */
247         for (j=0; j < lpdiaf->dwNumActions; j++)
248         {
249             if (IsEqualGUID(&lpdiaf->rgoAction[j].guidInstance, &device->ddi.guidInstance) &&
250                 lpdiaf->rgoAction[j].dwObjID == device->ddo[i].dwType)
251             {
252                 action = j;
253                 break;
254             }
255         }
256
257         lv_set_action(dialog, i, action, lpdiaf);
258     }
259 }
260
261 static void show_suitable_actions(HWND dialog)
262 {
263     DeviceData *device = get_cur_device(dialog);
264     LPDIACTIONFORMATW lpdiaf = get_cur_lpdiaf(dialog);
265     int i, added = 0;
266     int obj = lv_get_cur_item(dialog);
267
268     if (obj < 0) return;
269
270     SendDlgItemMessageW(dialog, IDC_ACTIONLIST, LB_RESETCONTENT, 0, 0);
271
272     for (i=0; i < lpdiaf->dwNumActions; i++)
273     {
274         /* Skip keyboard actions for non keyboards */
275         if (GET_DIDEVICE_TYPE(device->ddi.dwDevType) != DI8DEVTYPE_KEYBOARD &&
276             (lpdiaf->rgoAction[i].dwSemantic & DIKEYBOARD_MASK) == DIKEYBOARD_MASK) continue;
277
278         /* Skip mouse actions for non mouses */
279         if (GET_DIDEVICE_TYPE(device->ddi.dwDevType) != DI8DEVTYPE_MOUSE &&
280             (lpdiaf->rgoAction[i].dwSemantic & DIMOUSE_MASK) == DIMOUSE_MASK) continue;
281
282         /* Add action string and index in the action format to the list entry */
283         if (DIDFT_GETINSTANCE(lpdiaf->rgoAction[i].dwSemantic) & DIDFT_GETTYPE(device->ddo[obj].dwType))
284         {
285             SendDlgItemMessageW(dialog, IDC_ACTIONLIST, LB_ADDSTRING, 0, (LPARAM)lpdiaf->rgoAction[i].u.lptszActionName);
286             SendDlgItemMessageW(dialog, IDC_ACTIONLIST, LB_SETITEMDATA, added, (LPARAM) i);
287             added++;
288         }
289     }
290 }
291
292 static void assign_action(HWND dialog)
293 {
294     DeviceData *device = get_cur_device(dialog);
295     LPDIACTIONFORMATW lpdiaf = get_cur_lpdiaf(dialog);
296     LVFINDINFOW lvFind;
297     int sel = SendDlgItemMessageW(dialog, IDC_ACTIONLIST, LB_GETCURSEL, 0, 0);
298     int action = SendDlgItemMessageW(dialog, IDC_ACTIONLIST, LB_GETITEMDATA, sel, 0);
299     int obj = lv_get_cur_item(dialog);
300     int old_action = lv_get_item_data(dialog, obj);
301     int used_obj;
302
303     DIDEVICEOBJECTINSTANCEW ddo = device->ddo[obj];
304
305     if (old_action == action) return;
306
307     /* Clear old action */
308     if (old_action != -1)
309     {
310         lpdiaf->rgoAction[old_action].dwObjID = 0;
311         lpdiaf->rgoAction[old_action].guidInstance = GUID_NULL;
312         lpdiaf->rgoAction[old_action].dwHow = DIAH_UNMAPPED;
313     }
314
315     /* Find if action text is already set for other object and unset it */
316     lvFind.flags = LVFI_PARAM;
317     lvFind.lParam = action;
318
319     used_obj = SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_FINDITEMW, -1, (LPARAM) &lvFind);
320
321     lv_set_action(dialog, used_obj, -1, lpdiaf);
322
323     /* Set new action */
324     lpdiaf->rgoAction[action].dwObjID = ddo.dwType;
325     lpdiaf->rgoAction[action].guidInstance = device->ddi.guidInstance;
326     lpdiaf->rgoAction[action].dwHow = DIAH_USERCONFIG;
327
328     /* Set new action in the list */
329     lv_set_action(dialog, obj, action, lpdiaf);
330 }
331
332 static void copy_actions(LPDIACTIONFORMATW to, LPDIACTIONFORMATW from)
333 {
334     int i;
335     for (i=0; i < from->dwNumActions; i++)
336     {
337         to->rgoAction[i].guidInstance = from->rgoAction[i].guidInstance;
338         to->rgoAction[i].dwObjID = from->rgoAction[i].dwObjID;
339         to->rgoAction[i].dwHow = from->rgoAction[i].dwHow;
340         to->rgoAction[i].u.lptszActionName = from->rgoAction[i].u.lptszActionName;
341     }
342 }
343
344 static void reset_actions(HWND dialog)
345 {
346     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
347     LPDIACTIONFORMATW to = data->lpdiaf, from = data->original_lpdiaf;
348
349     copy_actions(to, from);
350 }
351
352 static INT_PTR CALLBACK ConfigureDevicesDlgProc(HWND dialog, UINT uMsg, WPARAM wParam, LPARAM lParam)
353 {
354     switch(uMsg)
355     {
356         case WM_INITDIALOG:
357         {
358             ConfigureDevicesData *data = (ConfigureDevicesData*) lParam;
359
360             /* Initialize action format and enumerate devices */
361             init_devices(dialog, data->lpDI, &data->devices_data, data->lpdiaf);
362
363             /* Store information in the window */
364             SetWindowLongPtrW(dialog, DWLP_USER, (LONG_PTR) data);
365
366             init_listview_columns(dialog);
367
368             /* Create a backup action format for CANCEL and RESET operations */
369             data->original_lpdiaf = HeapAlloc(GetProcessHeap(), 0, sizeof(*data->original_lpdiaf));
370             data->original_lpdiaf->dwNumActions = data->lpdiaf->dwNumActions;
371             data->original_lpdiaf->rgoAction = HeapAlloc(GetProcessHeap(), 0, sizeof(DIACTIONW)*data->lpdiaf->dwNumActions);
372             copy_actions(data->original_lpdiaf, data->lpdiaf);
373
374             /* Select the first device and show its actions */
375             SendDlgItemMessageW(dialog, IDC_CONTROLLERCOMBO, CB_SETCURSEL, 0, 0);
376             fill_device_object_list(dialog);
377
378             break;
379         }
380
381         case WM_NOTIFY:
382
383             switch (((LPNMHDR)lParam)->code)
384             {
385                 case LVN_ITEMCHANGED:
386                     show_suitable_actions(dialog);
387                     break;
388             }
389             break;
390
391
392         case WM_COMMAND:
393
394             switch(LOWORD(wParam))
395             {
396
397                 case IDC_ACTIONLIST:
398
399                     switch (HIWORD(wParam))
400                     {
401                         case LBN_DBLCLK:
402                             /* Ignore this if app did not ask for editing */
403                             if (dialog_display_only(dialog)) break;
404
405                             assign_action(dialog);
406                             break;
407                     }
408                     break;
409
410                 case IDC_CONTROLLERCOMBO:
411
412                     switch (HIWORD(wParam))
413                     {
414                         case CBN_SELCHANGE:
415                             fill_device_object_list(dialog);
416                             break;
417                     }
418                     break;
419
420                 case IDOK:
421                     EndDialog(dialog, 0);
422                     destroy_data(dialog);
423                     break;
424
425                 case IDCANCEL:
426                     reset_actions(dialog);
427                     EndDialog(dialog, 0);
428                     destroy_data(dialog);
429                     break;
430
431                 case IDC_RESET:
432                     reset_actions(dialog);
433                     fill_device_object_list(dialog);
434                     break;
435             }
436         break;
437     }
438
439     return FALSE;
440 }
441
442 HRESULT _configure_devices(IDirectInput8W *iface,
443                            LPDICONFIGUREDEVICESCALLBACK lpdiCallback,
444                            LPDICONFIGUREDEVICESPARAMSW lpdiCDParams,
445                            DWORD dwFlags,
446                            LPVOID pvRefData
447 )
448 {
449     ConfigureDevicesData data;
450     data.lpDI = iface;
451     data.lpdiaf = lpdiCDParams->lprgFormats;
452     data.display_only = !(dwFlags & DICD_EDIT);
453
454     InitCommonControls();
455
456     DialogBoxParamW(GetModuleHandleA("dinput.dll"), (LPCWSTR) MAKEINTRESOURCE(IDD_CONFIGUREDEVICES), lpdiCDParams->hwnd, ConfigureDevicesDlgProc, (LPARAM) &data);
457
458     return DI_OK;
459 }