msxml3: Implement ISAXAttributes::getLength() for MXAttributes.
[wine] / dlls / comdlg32 / filedlg31.c
1 /*
2  * COMMDLG - File Dialogs
3  *
4  * Copyright 1994 Martin Ayotte
5  * Copyright 1996 Albrecht Kleine
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 #include <ctype.h>
22 #include <stdlib.h>
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include "windef.h"
27 #include "winbase.h"
28 #include "winnls.h"
29 #include "wingdi.h"
30 #include "winuser.h"
31 #include "wine/unicode.h"
32 #include "wine/debug.h"
33 #include "winreg.h"
34 #include "winternl.h"
35 #include "commdlg.h"
36 #include "shlwapi.h"
37
38 WINE_DEFAULT_DEBUG_CHANNEL(commdlg);
39
40 #include "cdlg.h"
41 #include "filedlg31.h"
42
43 #define BUFFILE 512
44 #define BUFFILEALLOC 512 * sizeof(WCHAR)
45
46 static const WCHAR FILE_star[] = {'*','.','*', 0};
47 static const WCHAR FILE_bslash[] = {'\\', 0};
48 static const WCHAR FILE_specc[] = {'%','c',':', 0};
49 static const int fldrHeight = 16;
50 static const int fldrWidth = 20;
51
52 static HICON hFolder = 0;
53 static HICON hFolder2 = 0;
54 static HICON hFloppy = 0;
55 static HICON hHDisk = 0;
56 static HICON hCDRom = 0;
57 static HICON hNet = 0;
58
59 /***********************************************************************
60  *                              FD31_Init                       [internal]
61  */
62 BOOL FD31_Init(void)
63 {
64     static BOOL initialized = 0;
65
66     if (!initialized) {
67         hFolder  = LoadImageA( COMDLG32_hInstance, "FOLDER", IMAGE_ICON, 16, 16, LR_SHARED );
68         hFolder2 = LoadImageA( COMDLG32_hInstance, "FOLDER2", IMAGE_ICON, 16, 16, LR_SHARED );
69         hFloppy  = LoadImageA( COMDLG32_hInstance, "FLOPPY", IMAGE_ICON, 16, 16, LR_SHARED );
70         hHDisk   = LoadImageA( COMDLG32_hInstance, "HDISK", IMAGE_ICON, 16, 16, LR_SHARED );
71         hCDRom   = LoadImageA( COMDLG32_hInstance, "CDROM", IMAGE_ICON, 16, 16, LR_SHARED );
72         hNet     = LoadImageA( COMDLG32_hInstance, "NETWORK", IMAGE_ICON, 16, 16, LR_SHARED );
73         if (hFolder == 0 || hFolder2 == 0 || hFloppy == 0 ||
74             hHDisk == 0 || hCDRom == 0 || hNet == 0)
75         {
76             ERR("Error loading icons !\n");
77             return FALSE;
78         }
79         initialized = TRUE;
80     }
81     return TRUE;
82 }
83
84 /***********************************************************************
85  *                              FD31_StripEditControl        [internal]
86  * Strip pathnames off the contents of the edit control.
87  */
88 static void FD31_StripEditControl(HWND hwnd)
89 {
90     WCHAR temp[BUFFILE], *cp;
91
92     GetDlgItemTextW( hwnd, edt1, temp, sizeof(temp)/sizeof(WCHAR));
93     cp = strrchrW(temp, '\\');
94     if (cp != NULL) {
95         strcpyW(temp, cp+1);
96     }
97     cp = strrchrW(temp, ':');
98     if (cp != NULL) {
99         strcpyW(temp, cp+1);
100     }
101     /* FIXME: shouldn't we do something with the result here? ;-) */
102 }
103
104 /***********************************************************************
105  *                              FD31_CallWindowProc          [internal]
106  *
107  *      Call the appropriate hook
108  */
109 BOOL FD31_CallWindowProc(const FD31_DATA *lfs, UINT wMsg, WPARAM wParam,
110                          LPARAM lParam)
111 {
112     BOOL ret;
113
114     if (lfs->ofnA)
115     {
116         TRACE("Call hookA %p (%p, %04x, %08lx, %08lx)\n",
117                lfs->ofnA->lpfnHook, lfs->hwnd, wMsg, wParam, lParam);
118         ret = lfs->ofnA->lpfnHook(lfs->hwnd, wMsg, wParam, lParam);
119         TRACE("ret hookA %p (%p, %04x, %08lx, %08lx)\n",
120                lfs->ofnA->lpfnHook, lfs->hwnd, wMsg, wParam, lParam);
121         return ret;
122     }
123
124     TRACE("Call hookW %p (%p, %04x, %08lx, %08lx)\n",
125            lfs->ofnW->lpfnHook, lfs->hwnd, wMsg, wParam, lParam);
126     ret = lfs->ofnW->lpfnHook(lfs->hwnd, wMsg, wParam, lParam);
127     TRACE("Ret hookW %p (%p, %04x, %08lx, %08lx)\n",
128            lfs->ofnW->lpfnHook, lfs->hwnd, wMsg, wParam, lParam);
129     return ret;
130 }
131
132 /***********************************************************************
133  *                              FD31_GetFileType                [internal]
134  */
135 static LPCWSTR FD31_GetFileType(LPCWSTR cfptr, LPCWSTR fptr, const WORD index)
136 {
137   int n, i;
138   i = 0;
139   if (cfptr)
140     for ( ;(n = lstrlenW(cfptr)) != 0; i++)
141       {
142         cfptr += n + 1;
143         if (i == index)
144           return cfptr;
145         cfptr += lstrlenW(cfptr) + 1;
146       }
147   if (fptr)
148     for ( ;(n = lstrlenW(fptr)) != 0; i++)
149       {
150         fptr += n + 1;
151         if (i == index)
152           return fptr;
153         fptr += lstrlenW(fptr) + 1;
154     }
155   return FILE_star; /* FIXME */
156 }
157
158 /***********************************************************************
159  *                              FD31_ScanDir                 [internal]
160  */
161 static BOOL FD31_ScanDir(const OPENFILENAMEW *ofn, HWND hWnd, LPCWSTR newPath)
162 {
163     WCHAR   buffer[BUFFILE];
164     HWND    hdlg;
165     LRESULT lRet = TRUE;
166     HCURSOR hCursorWait, oldCursor;
167
168     TRACE("Trying to change to %s\n", debugstr_w(newPath));
169     if  ( newPath[0] && !SetCurrentDirectoryW( newPath ))
170         return FALSE;
171
172     /* get the list of spec files */
173     lstrcpynW(buffer, FD31_GetFileType(ofn->lpstrCustomFilter,
174               ofn->lpstrFilter, ofn->nFilterIndex - 1), BUFFILE);
175
176     hCursorWait = LoadCursorA(0, (LPSTR)IDC_WAIT);
177     oldCursor = SetCursor(hCursorWait);
178
179     /* list of files */
180     if ((hdlg = GetDlgItem(hWnd, lst1)) != 0) {
181         WCHAR*  scptr; /* ptr on semi-colon */
182         WCHAR*  filter = buffer;
183
184         TRACE("Using filter %s\n", debugstr_w(filter));
185         SendMessageW(hdlg, LB_RESETCONTENT, 0, 0);
186         while (filter) {
187             scptr = strchrW(filter, ';');
188             if (scptr)  *scptr = 0;
189             while (*filter == ' ') filter++;
190             TRACE("Using file spec %s\n", debugstr_w(filter));
191             SendMessageW(hdlg, LB_DIR, 0, (LPARAM)filter);
192             if (scptr) *scptr = ';';
193                 filter = (scptr) ? (scptr + 1) : 0;
194          }
195     }
196
197     /* list of directories */
198     strcpyW(buffer, FILE_star);
199
200     if (GetDlgItem(hWnd, lst2) != 0) {
201         lRet = DlgDirListW(hWnd, buffer, lst2, stc1, DDL_EXCLUSIVE | DDL_DIRECTORY);
202     }
203     SetCursor(oldCursor);
204     return lRet;
205 }
206
207 /***********************************************************************
208  *                              FD31_WMDrawItem              [internal]
209  */
210 LONG FD31_WMDrawItem(HWND hWnd, WPARAM wParam, LPARAM lParam,
211        int savedlg, const DRAWITEMSTRUCT *lpdis)
212 {
213     WCHAR *str;
214     HICON hIcon;
215     COLORREF oldText = 0, oldBk = 0;
216
217     if (lpdis->CtlType == ODT_LISTBOX && lpdis->CtlID == lst1)
218     {
219         if (!(str = HeapAlloc(GetProcessHeap(), 0, BUFFILEALLOC))) return FALSE;
220         SendMessageW(lpdis->hwndItem, LB_GETTEXT, lpdis->itemID,
221                       (LPARAM)str);
222
223         if ((lpdis->itemState & ODS_SELECTED) && !savedlg)
224         {
225             oldBk = SetBkColor( lpdis->hDC, GetSysColor( COLOR_HIGHLIGHT ) );
226             oldText = SetTextColor( lpdis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
227         }
228         if (savedlg)
229             SetTextColor(lpdis->hDC,GetSysColor(COLOR_GRAYTEXT) );
230
231         ExtTextOutW(lpdis->hDC, lpdis->rcItem.left + 1,
232                   lpdis->rcItem.top + 1, ETO_OPAQUE | ETO_CLIPPED,
233                   &(lpdis->rcItem), str, lstrlenW(str), NULL);
234
235         if (lpdis->itemState & ODS_SELECTED)
236             DrawFocusRect( lpdis->hDC, &(lpdis->rcItem) );
237
238         if ((lpdis->itemState & ODS_SELECTED) && !savedlg)
239         {
240             SetBkColor( lpdis->hDC, oldBk );
241             SetTextColor( lpdis->hDC, oldText );
242         }
243         HeapFree(GetProcessHeap(), 0, str);
244         return TRUE;
245     }
246
247     if (lpdis->CtlType == ODT_LISTBOX && lpdis->CtlID == lst2)
248     {
249         if (!(str = HeapAlloc(GetProcessHeap(), 0, BUFFILEALLOC)))
250             return FALSE;
251         SendMessageW(lpdis->hwndItem, LB_GETTEXT, lpdis->itemID,
252                       (LPARAM)str);
253
254         if (lpdis->itemState & ODS_SELECTED)
255         {
256             oldBk = SetBkColor( lpdis->hDC, GetSysColor( COLOR_HIGHLIGHT ) );
257             oldText = SetTextColor( lpdis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
258         }
259         ExtTextOutW(lpdis->hDC, lpdis->rcItem.left + fldrWidth,
260                   lpdis->rcItem.top + 1, ETO_OPAQUE | ETO_CLIPPED,
261                   &(lpdis->rcItem), str, lstrlenW(str), NULL);
262
263         if (lpdis->itemState & ODS_SELECTED)
264             DrawFocusRect( lpdis->hDC, &(lpdis->rcItem) );
265
266         if (lpdis->itemState & ODS_SELECTED)
267         {
268             SetBkColor( lpdis->hDC, oldBk );
269             SetTextColor( lpdis->hDC, oldText );
270         }
271         DrawIconEx( lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top, hFolder, 16, 16, 0, 0, DI_NORMAL );
272         HeapFree(GetProcessHeap(), 0, str);
273         return TRUE;
274     }
275     if (lpdis->CtlType == ODT_COMBOBOX && lpdis->CtlID == cmb2)
276     {
277         char root[] = "a:";
278         if (!(str = HeapAlloc(GetProcessHeap(), 0, BUFFILEALLOC)))
279             return FALSE;
280         SendMessageW(lpdis->hwndItem, CB_GETLBTEXT, lpdis->itemID,
281                       (LPARAM)str);
282         root[0] += str[2] - 'a';
283         switch(GetDriveTypeA(root))
284         {
285         case DRIVE_REMOVABLE: hIcon = hFloppy; break;
286         case DRIVE_CDROM:     hIcon = hCDRom; break;
287         case DRIVE_REMOTE:    hIcon = hNet; break;
288         case DRIVE_FIXED:
289         default:           hIcon = hHDisk; break;
290         }
291         if (lpdis->itemState & ODS_SELECTED)
292         {
293             oldBk = SetBkColor( lpdis->hDC, GetSysColor( COLOR_HIGHLIGHT ) );
294             oldText = SetTextColor( lpdis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
295         }
296         ExtTextOutW(lpdis->hDC, lpdis->rcItem.left + fldrWidth,
297                   lpdis->rcItem.top + 1, ETO_OPAQUE | ETO_CLIPPED,
298                   &(lpdis->rcItem), str, lstrlenW(str), NULL);
299
300         if (lpdis->itemState & ODS_SELECTED)
301         {
302             SetBkColor( lpdis->hDC, oldBk );
303             SetTextColor( lpdis->hDC, oldText );
304         }
305         DrawIconEx( lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top, hIcon, 16, 16, 0, 0, DI_NORMAL );
306         HeapFree(GetProcessHeap(), 0, str);
307         return TRUE;
308     }
309     return FALSE;
310 }
311
312 /***********************************************************************
313  *                              FD31_UpdateResult            [internal]
314  *      update the displayed file name (with path)
315  */
316 static void FD31_UpdateResult(const FD31_DATA *lfs, const WCHAR *tmpstr)
317 {
318     int lenstr2;
319     LPOPENFILENAMEW ofnW = lfs->ofnW;
320     LPOPENFILENAMEA ofnA = lfs->ofnA;
321     WCHAR tmpstr2[BUFFILE];
322     WCHAR *p;
323
324     TRACE("%s\n", debugstr_w(tmpstr));
325     if(ofnW->Flags & OFN_NOVALIDATE)
326         tmpstr2[0] = '\0';
327     else
328         GetCurrentDirectoryW(BUFFILE, tmpstr2);
329     lenstr2 = strlenW(tmpstr2);
330     if (lenstr2 > 3)
331         tmpstr2[lenstr2++]='\\';
332     lstrcpynW(tmpstr2+lenstr2, tmpstr, BUFFILE-lenstr2);
333     if (!ofnW->lpstrFile)
334         return;
335
336     lstrcpynW(ofnW->lpstrFile, tmpstr2, ofnW->nMaxFile);
337
338     /* set filename offset */
339     p = PathFindFileNameW(ofnW->lpstrFile);
340     ofnW->nFileOffset = (p - ofnW->lpstrFile);
341
342     /* set extension offset */
343     p = PathFindExtensionW(ofnW->lpstrFile);
344     ofnW->nFileExtension = (*p) ? (p - ofnW->lpstrFile) + 1 : 0;
345
346     TRACE("file %s, file offset %d, ext offset %d\n",
347           debugstr_w(ofnW->lpstrFile), ofnW->nFileOffset, ofnW->nFileExtension);
348
349     /* update the real client structures if any */
350     if (ofnA)
351     {
352         LPSTR lpszTemp;
353         if (ofnW->nMaxFile &&
354             !WideCharToMultiByte( CP_ACP, 0, ofnW->lpstrFile, -1,
355                                   ofnA->lpstrFile, ofnA->nMaxFile, NULL, NULL ))
356             ofnA->lpstrFile[ofnA->nMaxFile-1] = 0;
357
358         /* offsets are not guaranteed to be the same in WCHAR to MULTIBYTE conversion */
359         /* set filename offset */
360         lpszTemp = PathFindFileNameA(ofnA->lpstrFile);
361         ofnA->nFileOffset = (lpszTemp - ofnA->lpstrFile);
362
363         /* set extension offset */
364         lpszTemp = PathFindExtensionA(ofnA->lpstrFile);
365         ofnA->nFileExtension = (*lpszTemp) ? (lpszTemp - ofnA->lpstrFile) + 1 : 0;
366     }
367 }
368
369 /***********************************************************************
370  *                              FD31_UpdateFileTitle         [internal]
371  *      update the displayed file name (without path)
372  */
373 static void FD31_UpdateFileTitle(const FD31_DATA *lfs)
374 {
375   LONG lRet;
376   LPOPENFILENAMEW ofnW = lfs->ofnW;
377   LPOPENFILENAMEA ofnA = lfs->ofnA;
378
379   if (ofnW->lpstrFileTitle != NULL)
380   {
381     lRet = SendDlgItemMessageW(lfs->hwnd, lst1, LB_GETCURSEL, 0, 0);
382     SendDlgItemMessageW(lfs->hwnd, lst1, LB_GETTEXT, lRet,
383                              (LPARAM)ofnW->lpstrFileTitle );
384     if (ofnA)
385     {
386         if (!WideCharToMultiByte( CP_ACP, 0, ofnW->lpstrFileTitle, -1,
387                                   ofnA->lpstrFileTitle, ofnA->nMaxFileTitle, NULL, NULL ))
388             ofnA->lpstrFileTitle[ofnA->nMaxFileTitle-1] = 0;
389     }
390   }
391 }
392
393 /***********************************************************************
394  *                              FD31_DirListDblClick         [internal]
395  */
396 static LRESULT FD31_DirListDblClick( const FD31_DATA *lfs )
397 {
398   LONG lRet;
399   HWND hWnd = lfs->hwnd;
400   LPWSTR pstr;
401   WCHAR tmpstr[BUFFILE];
402
403   /* get the raw string (with brackets) */
404   lRet = SendDlgItemMessageW(hWnd, lst2, LB_GETCURSEL, 0, 0);
405   if (lRet == LB_ERR) return TRUE;
406   pstr = HeapAlloc(GetProcessHeap(), 0, BUFFILEALLOC);
407   SendDlgItemMessageW(hWnd, lst2, LB_GETTEXT, lRet,
408                      (LPARAM)pstr);
409   strcpyW( tmpstr, pstr );
410   HeapFree(GetProcessHeap(), 0, pstr);
411   /* get the selected directory in tmpstr */
412   if (tmpstr[0] == '[')
413     {
414       tmpstr[lstrlenW(tmpstr) - 1] = 0;
415       strcpyW(tmpstr,tmpstr+1);
416     }
417   strcatW(tmpstr, FILE_bslash);
418
419   FD31_ScanDir(lfs->ofnW, hWnd, tmpstr);
420   /* notify the app */
421   if (lfs->hook)
422     {
423       if (FD31_CallWindowProc(lfs, lfs->lbselchstring, lst2,
424               MAKELONG(lRet,CD_LBSELCHANGE)))
425         return TRUE;
426     }
427   return TRUE;
428 }
429
430 /***********************************************************************
431  *                              FD31_FileListSelect         [internal]
432  *    called when a new item is picked in the file list
433  */
434 static LRESULT FD31_FileListSelect( const FD31_DATA *lfs )
435 {
436     LONG lRet;
437     HWND hWnd = lfs->hwnd;
438     LPWSTR pstr;
439
440     lRet =  SendDlgItemMessageW(lfs->hwnd, lst1, LB_GETCURSEL, 0, 0);
441     if (lRet == LB_ERR)
442         return TRUE;
443
444     /* set the edit control to the chosen file */
445     if ((pstr = HeapAlloc(GetProcessHeap(), 0, BUFFILEALLOC)))
446     {
447         SendDlgItemMessageW(hWnd, lst1, LB_GETTEXT, lRet,
448                        (LPARAM)pstr);
449         SetDlgItemTextW( hWnd, edt1, pstr );
450         HeapFree(GetProcessHeap(), 0, pstr);
451     }
452     if (lfs->hook)
453     {
454         FD31_CallWindowProc(lfs, lfs->lbselchstring, lst1,
455                            MAKELONG(lRet,CD_LBSELCHANGE));
456     }
457     /* FIXME: for OFN_ALLOWMULTISELECT we need CD_LBSELSUB, CD_SELADD,
458            CD_LBSELNOITEMS */
459     return TRUE;
460 }
461
462 /***********************************************************************
463  *                              FD31_TestPath      [internal]
464  *      before accepting the file name, test if it includes wild cards
465  *      tries to scan the directory and returns TRUE if no error.
466  */
467 static LRESULT FD31_TestPath( const FD31_DATA *lfs, LPWSTR path )
468 {
469     HWND hWnd = lfs->hwnd;
470     LPWSTR pBeginFileName, pstr2;
471     WCHAR tmpstr2[BUFFILE];
472
473     pBeginFileName = strrchrW(path, '\\');
474     if (pBeginFileName == NULL)
475         pBeginFileName = strrchrW(path, ':');
476
477     if (strchrW(path,'*') != NULL || strchrW(path,'?') != NULL)
478     {
479         /* edit control contains wildcards */
480         if (pBeginFileName != NULL)
481         {
482             lstrcpynW(tmpstr2, pBeginFileName + 1, BUFFILE);
483             *(pBeginFileName + 1) = 0;
484         }
485         else
486         {
487             strcpyW(tmpstr2, path);
488             if(!(lfs->ofnW->Flags & OFN_NOVALIDATE))
489                 *path = 0;
490         }
491
492         TRACE("path=%s, tmpstr2=%s\n", debugstr_w(path), debugstr_w(tmpstr2));
493         SetDlgItemTextW( hWnd, edt1, tmpstr2 );
494         FD31_ScanDir(lfs->ofnW, hWnd, path);
495         return (lfs->ofnW->Flags & OFN_NOVALIDATE) ? TRUE : FALSE;
496     }
497
498     /* no wildcards, we might have a directory or a filename */
499     /* try appending a wildcard and reading the directory */
500
501     pstr2 = path + lstrlenW(path);
502     if (pBeginFileName == NULL || *(pBeginFileName + 1) != 0)
503         strcatW(path, FILE_bslash);
504
505     /* if ScanDir succeeds, we have changed the directory */
506     if (FD31_ScanDir(lfs->ofnW, hWnd, path))
507         return FALSE; /* and path is not a valid file name */
508
509     /* if not, this must be a filename */
510
511     *pstr2 = 0; /* remove the wildcard added before */
512
513     if (pBeginFileName != NULL)
514     {
515         /* strip off the pathname */
516         *pBeginFileName = 0;
517         SetDlgItemTextW( hWnd, edt1, pBeginFileName + 1 );
518
519         lstrcpynW(tmpstr2, pBeginFileName + 1, sizeof(tmpstr2)/sizeof(WCHAR) );
520         /* Should we MessageBox() if this fails? */
521         if (!FD31_ScanDir(lfs->ofnW, hWnd, path))
522         {
523             return FALSE;
524         }
525         strcpyW(path, tmpstr2);
526     }
527     else
528         SetDlgItemTextW( hWnd, edt1, path );
529     return TRUE;
530 }
531
532 /***********************************************************************
533  *                              FD31_Validate               [internal]
534  *   called on: click Ok button, Enter in edit, DoubleClick in file list
535  */
536 static LRESULT FD31_Validate( const FD31_DATA *lfs, LPCWSTR path, UINT control, INT itemIndex,
537                                  BOOL internalUse )
538 {
539     LONG lRet;
540     HWND hWnd = lfs->hwnd;
541     OPENFILENAMEW ofnsav;
542     LPOPENFILENAMEW ofnW = lfs->ofnW;
543     WCHAR filename[BUFFILE];
544
545     ofnsav = *ofnW; /* for later restoring */
546
547     /* get current file name */
548     if (path)
549         lstrcpynW(filename, path, sizeof(filename)/sizeof(WCHAR));
550     else
551         GetDlgItemTextW( hWnd, edt1, filename, sizeof(filename)/sizeof(WCHAR));
552
553     TRACE("got filename = %s\n", debugstr_w(filename));
554     /* if we did not click in file list to get there */
555     if (control != lst1)
556     {
557         if (!FD31_TestPath( lfs, filename) )
558            return FALSE;
559     }
560     FD31_UpdateResult(lfs, filename);
561
562     if (internalUse)
563     { /* called internally after a change in a combo */
564         if (lfs->hook)
565         {
566              FD31_CallWindowProc(lfs, lfs->lbselchstring, control,
567                              MAKELONG(itemIndex,CD_LBSELCHANGE));
568         }
569         return TRUE;
570     }
571
572     FD31_UpdateFileTitle(lfs);
573     if (lfs->hook)
574     {
575         lRet = FD31_CallWindowProc(lfs, lfs->fileokstring,
576                   0, lfs->lParam );
577         if (lRet)
578         {
579             *ofnW = ofnsav; /* restore old state */
580             return FALSE;
581         }
582     }
583     if ((ofnW->Flags & OFN_ALLOWMULTISELECT) && (ofnW->Flags & OFN_EXPLORER))
584     {
585         if (ofnW->lpstrFile)
586         {
587             LPWSTR str = ofnW->lpstrFile;
588             LPWSTR ptr = strrchrW(str, '\\');
589             str[lstrlenW(str) + 1] = '\0';
590             *ptr = 0;
591         }
592     }
593     return TRUE;
594 }
595
596 /***********************************************************************
597  *                              FD31_DiskChange             [internal]
598  *    called when a new item is picked in the disk selection combo
599  */
600 static LRESULT FD31_DiskChange( const FD31_DATA *lfs )
601 {
602     LONG lRet;
603     HWND hWnd = lfs->hwnd;
604     LPWSTR pstr;
605     WCHAR diskname[BUFFILE];
606
607     FD31_StripEditControl(hWnd);
608     lRet = SendDlgItemMessageW(hWnd, cmb2, CB_GETCURSEL, 0, 0L);
609     if (lRet == LB_ERR)
610         return 0;
611     pstr = HeapAlloc(GetProcessHeap(), 0, BUFFILEALLOC);
612     SendDlgItemMessageW(hWnd, cmb2, CB_GETLBTEXT, lRet,
613                          (LPARAM)pstr);
614     wsprintfW(diskname, FILE_specc, pstr[2]);
615     HeapFree(GetProcessHeap(), 0, pstr);
616
617     return FD31_Validate( lfs, diskname, cmb2, lRet, TRUE );
618 }
619
620 /***********************************************************************
621  *                              FD31_FileTypeChange         [internal]
622  *    called when a new item is picked in the file type combo
623  */
624 static LRESULT FD31_FileTypeChange( const FD31_DATA *lfs )
625 {
626     LONG lRet;
627     LPWSTR pstr;
628
629     lRet = SendDlgItemMessageW(lfs->hwnd, cmb1, CB_GETCURSEL, 0, 0);
630     if (lRet == LB_ERR)
631         return TRUE;
632     lfs->ofnW->nFilterIndex = lRet + 1;
633     if (lfs->ofnA)
634         lfs->ofnA->nFilterIndex = lRet + 1;
635     pstr = (LPWSTR)SendDlgItemMessageW(lfs->hwnd, cmb1, CB_GETITEMDATA, lRet, 0);
636     TRACE("Selected filter : %s\n", debugstr_w(pstr));
637
638     return FD31_Validate( lfs, pstr, cmb1, lRet, TRUE );
639 }
640
641 /***********************************************************************
642  *                              FD31_WMCommand               [internal]
643  */
644 LRESULT FD31_WMCommand(HWND hWnd, LPARAM lParam, UINT notification,
645        UINT control, const FD31_DATA *lfs )
646 {
647     switch (control)
648     {
649         case lst1: /* file list */
650         FD31_StripEditControl(hWnd);
651         if (notification == LBN_DBLCLK)
652         {
653             return SendMessageW(hWnd, WM_COMMAND, IDOK, 0);
654         }
655         else if (notification == LBN_SELCHANGE)
656             return FD31_FileListSelect( lfs );
657         break;
658
659         case lst2: /* directory list */
660         FD31_StripEditControl(hWnd);
661         if (notification == LBN_DBLCLK)
662             return FD31_DirListDblClick( lfs );
663         break;
664
665         case cmb1: /* file type drop list */
666         if (notification == CBN_SELCHANGE)
667             return FD31_FileTypeChange( lfs );
668         break;
669
670         case chx1:
671         break;
672
673         case pshHelp:
674         break;
675
676         case cmb2: /* disk dropdown combo */
677         if (notification == CBN_SELCHANGE)
678             return FD31_DiskChange( lfs );
679         break;
680
681         case IDOK:
682         TRACE("OK pressed\n");
683         if (FD31_Validate( lfs, NULL, control, 0, FALSE ))
684             EndDialog(hWnd, TRUE);
685         return TRUE;
686
687         case IDCANCEL:
688         EndDialog(hWnd, FALSE);
689         return TRUE;
690
691         case IDABORT: /* can be sent by the hook procedure */
692         EndDialog(hWnd, TRUE);
693         return TRUE;
694     }
695     return FALSE;
696 }
697
698 /************************************************************************
699  *                              FD31_MapStringPairsToW       [internal]
700  *      map string pairs to Unicode
701  */
702 static LPWSTR FD31_MapStringPairsToW(LPCSTR strA, UINT size)
703 {
704     LPCSTR s;
705     LPWSTR x;
706     unsigned int n, len;
707
708     s = strA;
709     while (*s)
710         s = s+strlen(s)+1;
711     s++;
712     n = s + 1 - strA; /* Don't forget the other \0 */
713     if (n < size) n = size;
714
715     len = MultiByteToWideChar( CP_ACP, 0, strA, n, NULL, 0 );
716     x = HeapAlloc(GetProcessHeap(),0, len * sizeof(WCHAR));
717     MultiByteToWideChar( CP_ACP, 0, strA, n, x, len );
718     return x;
719 }
720
721
722 /************************************************************************
723  *                              FD31_DupToW                  [internal]
724  *      duplicates an Ansi string to unicode, with a buffer size
725  */
726 static LPWSTR FD31_DupToW(LPCSTR str, DWORD size)
727 {
728     LPWSTR strW = NULL;
729     if (str && (size > 0))
730     {
731         strW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
732         if (strW) MultiByteToWideChar( CP_ACP, 0, str, -1, strW, size );
733     }
734     return strW;
735 }
736
737 /************************************************************************
738  *                              FD31_MapOfnStructA          [internal]
739  *      map a 32 bits Ansi structure to a Unicode one
740  */
741 static void FD31_MapOfnStructA(const OPENFILENAMEA *ofnA, LPOPENFILENAMEW ofnW, BOOL open)
742 {
743     UNICODE_STRING usBuffer;
744
745     ofnW->lStructSize = sizeof(OPENFILENAMEW);
746     ofnW->hwndOwner = ofnA->hwndOwner;
747     ofnW->hInstance = ofnA->hInstance;
748     if (ofnA->lpstrFilter)
749         ofnW->lpstrFilter = FD31_MapStringPairsToW(ofnA->lpstrFilter, 0);
750
751     if ((ofnA->lpstrCustomFilter) && (*(ofnA->lpstrCustomFilter)))
752         ofnW->lpstrCustomFilter = FD31_MapStringPairsToW(ofnA->lpstrCustomFilter, ofnA->nMaxCustFilter);
753     ofnW->nMaxCustFilter = ofnA->nMaxCustFilter;
754     ofnW->nFilterIndex = ofnA->nFilterIndex;
755     ofnW->nMaxFile = ofnA->nMaxFile;
756     ofnW->lpstrFile = FD31_DupToW(ofnA->lpstrFile, ofnW->nMaxFile);
757     ofnW->nMaxFileTitle = ofnA->nMaxFileTitle;
758     ofnW->lpstrFileTitle = FD31_DupToW(ofnA->lpstrFileTitle, ofnW->nMaxFileTitle);
759     if (ofnA->lpstrInitialDir)
760     {
761         RtlCreateUnicodeStringFromAsciiz (&usBuffer,ofnA->lpstrInitialDir);
762         ofnW->lpstrInitialDir = usBuffer.Buffer;
763     }
764     if (ofnA->lpstrTitle) {
765         RtlCreateUnicodeStringFromAsciiz (&usBuffer, ofnA->lpstrTitle);
766         ofnW->lpstrTitle = usBuffer.Buffer;
767     } else {
768         WCHAR buf[16];
769         LPWSTR title_tmp;
770         int len;
771         LoadStringW(COMDLG32_hInstance, open ? IDS_OPEN_FILE : IDS_SAVE_AS,
772                     buf, sizeof(buf)/sizeof(WCHAR));
773         len = lstrlenW(buf)+1;
774         title_tmp = HeapAlloc(GetProcessHeap(), 0, len*sizeof(WCHAR));
775         memcpy(title_tmp, buf, len * sizeof(WCHAR));
776         ofnW->lpstrTitle = title_tmp;
777     }
778     ofnW->Flags = ofnA->Flags;
779     ofnW->nFileOffset = ofnA->nFileOffset;
780     ofnW->nFileExtension = ofnA->nFileExtension;
781     ofnW->lpstrDefExt = FD31_DupToW(ofnA->lpstrDefExt, 3);
782     if ((ofnA->Flags & OFN_ENABLETEMPLATE) && (ofnA->lpTemplateName))
783     {
784         if (!IS_INTRESOURCE(ofnA->lpTemplateName))
785         {
786             RtlCreateUnicodeStringFromAsciiz (&usBuffer,ofnA->lpTemplateName);
787             ofnW->lpTemplateName = usBuffer.Buffer;
788         }
789         else /* numbered resource */
790             ofnW->lpTemplateName = (LPCWSTR) ofnA->lpTemplateName;
791     }
792 }
793
794
795 /************************************************************************
796  *                              FD31_FreeOfnW          [internal]
797  *      Undo all allocations done by FD31_MapOfnStructA
798  */
799 static void FD31_FreeOfnW(OPENFILENAMEW *ofnW)
800 {
801    HeapFree(GetProcessHeap(), 0, (LPWSTR) ofnW->lpstrFilter);
802    HeapFree(GetProcessHeap(), 0, ofnW->lpstrCustomFilter);
803    HeapFree(GetProcessHeap(), 0, ofnW->lpstrFile);
804    HeapFree(GetProcessHeap(), 0, ofnW->lpstrFileTitle);
805    HeapFree(GetProcessHeap(), 0, (LPWSTR) ofnW->lpstrInitialDir);
806    HeapFree(GetProcessHeap(), 0, (LPWSTR) ofnW->lpstrTitle);
807    if (!IS_INTRESOURCE(ofnW->lpTemplateName))
808        HeapFree(GetProcessHeap(), 0, (LPWSTR) ofnW->lpTemplateName);
809 }
810
811 /************************************************************************
812  *                              FD31_DestroyPrivate            [internal]
813  *      destroys the private object
814  */
815 void FD31_DestroyPrivate(PFD31_DATA lfs)
816 {
817     HWND hwnd;
818     if (!lfs) return;
819     hwnd = lfs->hwnd;
820     TRACE("destroying private allocation %p\n", lfs);
821
822     /* if ofnW has been allocated, have to free everything in it */
823     if (lfs->ofnA)
824     {
825         FD31_FreeOfnW(lfs->ofnW);
826         HeapFree(GetProcessHeap(), 0, lfs->ofnW);
827     }
828     HeapFree(GetProcessHeap(), 0, lfs);
829     RemovePropA(hwnd, FD31_OFN_PROP);
830 }
831
832 /************************************************************************
833  *                              FD31_AllocPrivate            [internal]
834  *      allocate a private object to hold 32 bits Unicode
835  *      structure that will be used throughout the calls, while
836  *      keeping available the original structures and a few variables
837  *      On entry : type = dialog procedure type (16,32A,32W)
838  *                 dlgType = dialog type (open or save)
839  */
840 PFD31_DATA FD31_AllocPrivate(LPARAM lParam, UINT dlgType, BOOL IsUnicode)
841 {
842     PFD31_DATA lfs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(FD31_DATA));
843
844     TRACE("alloc private buf %p\n", lfs);
845     if (!lfs) return NULL;
846     lfs->hook = FALSE;
847     lfs->lParam = lParam;
848     lfs->open = (dlgType == OPEN_DIALOG);
849
850     if (IsUnicode)
851     {
852         lfs->ofnA = NULL;
853         lfs->ofnW = (LPOPENFILENAMEW) lParam;
854         if (lfs->ofnW->Flags & OFN_ENABLEHOOK)
855             if (lfs->ofnW->lpfnHook)
856                 lfs->hook = TRUE;
857     }
858     else
859     {
860         lfs->ofnA = (LPOPENFILENAMEA) lParam;
861         if (lfs->ofnA->Flags & OFN_ENABLEHOOK)
862             if (lfs->ofnA->lpfnHook)
863                 lfs->hook = TRUE;
864         lfs->ofnW = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*lfs->ofnW));
865         FD31_MapOfnStructA(lfs->ofnA, lfs->ofnW, lfs->open);
866     }
867
868     if (! FD32_GetTemplate(lfs))
869     {
870         FD31_DestroyPrivate(lfs);
871         return NULL;
872     }
873     lfs->lbselchstring = RegisterWindowMessageA(LBSELCHSTRINGA);
874     lfs->fileokstring = RegisterWindowMessageA(FILEOKSTRINGA);
875
876     return lfs;
877 }
878
879 /***********************************************************************
880  *                              FD31_WMInitDialog            [internal]
881  */
882
883 LONG FD31_WMInitDialog(HWND hWnd, WPARAM wParam, LPARAM lParam)
884 {
885   int i, n;
886   WCHAR tmpstr[BUFFILE];
887   LPWSTR pstr, old_pstr;
888   LPOPENFILENAMEW ofn;
889   PFD31_DATA lfs = (PFD31_DATA) lParam;
890
891   if (!lfs) return FALSE;
892   SetPropA(hWnd, FD31_OFN_PROP, lfs);
893   lfs->hwnd = hWnd;
894   ofn = lfs->ofnW;
895
896   TRACE("flags=%x initialdir=%s\n", ofn->Flags, debugstr_w(ofn->lpstrInitialDir));
897
898   SetWindowTextW( hWnd, ofn->lpstrTitle );
899   /* read custom filter information */
900   if (ofn->lpstrCustomFilter)
901     {
902       pstr = ofn->lpstrCustomFilter;
903       n = 0;
904       TRACE("lpstrCustomFilter = %p\n", pstr);
905       while(*pstr)
906         {
907           old_pstr = pstr;
908           i = SendDlgItemMessageW(hWnd, cmb1, CB_ADDSTRING, 0,
909                                    (LPARAM)(ofn->lpstrCustomFilter) + n );
910           n += lstrlenW(pstr) + 1;
911           pstr += lstrlenW(pstr) + 1;
912           TRACE("add str=%s associated to %s\n",
913                 debugstr_w(old_pstr), debugstr_w(pstr));
914           SendDlgItemMessageW(hWnd, cmb1, CB_SETITEMDATA, i, (LPARAM)pstr);
915           n += lstrlenW(pstr) + 1;
916           pstr += lstrlenW(pstr) + 1;
917         }
918     }
919   /* read filter information */
920   if (ofn->lpstrFilter) {
921         pstr = (LPWSTR) ofn->lpstrFilter;
922         n = 0;
923         while(*pstr) {
924           old_pstr = pstr;
925           i = SendDlgItemMessageW(hWnd, cmb1, CB_ADDSTRING, 0,
926                                        (LPARAM)(ofn->lpstrFilter + n) );
927           n += lstrlenW(pstr) + 1;
928           pstr += lstrlenW(pstr) + 1;
929           TRACE("add str=%s associated to %s\n",
930                 debugstr_w(old_pstr), debugstr_w(pstr));
931           SendDlgItemMessageW(hWnd, cmb1, CB_SETITEMDATA, i, (LPARAM)pstr);
932           n += lstrlenW(pstr) + 1;
933           pstr += lstrlenW(pstr) + 1;
934         }
935   }
936   /* set default filter */
937   if (ofn->nFilterIndex == 0 && ofn->lpstrCustomFilter == NULL)
938         ofn->nFilterIndex = 1;
939   SendDlgItemMessageW(hWnd, cmb1, CB_SETCURSEL, ofn->nFilterIndex - 1, 0);
940   if (ofn->lpstrFile && ofn->lpstrFile[0])
941   {
942     TRACE( "SetText of edt1 to %s\n", debugstr_w(ofn->lpstrFile) );
943     SetDlgItemTextW( hWnd, edt1, ofn->lpstrFile );
944   }
945   else
946   {
947     lstrcpynW(tmpstr, FD31_GetFileType(ofn->lpstrCustomFilter,
948              ofn->lpstrFilter, ofn->nFilterIndex - 1),BUFFILE);
949     TRACE("nFilterIndex = %d, SetText of edt1 to %s\n",
950                         ofn->nFilterIndex, debugstr_w(tmpstr));
951     SetDlgItemTextW( hWnd, edt1, tmpstr );
952   }
953   /* get drive list */
954   *tmpstr = 0;
955   DlgDirListComboBoxW(hWnd, tmpstr, cmb2, 0, DDL_DRIVES | DDL_EXCLUSIVE);
956   /* read initial directory */
957   /* FIXME: Note that this is now very version-specific (See MSDN description of
958    * the OPENFILENAME structure).  For example under 2000/XP any path in the
959    * lpstrFile overrides the lpstrInitialDir, but not under 95/98/ME
960    */
961   if (ofn->lpstrInitialDir != NULL)
962     {
963       int len;
964       lstrcpynW(tmpstr, ofn->lpstrInitialDir, 511);
965       len = lstrlenW(tmpstr);
966       if (len > 0 && tmpstr[len-1] != '\\'  && tmpstr[len-1] != ':') {
967         tmpstr[len]='\\';
968         tmpstr[len+1]='\0';
969       }
970     }
971   else
972     *tmpstr = 0;
973   if (!FD31_ScanDir(ofn, hWnd, tmpstr)) {
974     *tmpstr = 0;
975     if (!FD31_ScanDir(ofn, hWnd, tmpstr))
976       WARN("Couldn't read initial directory %s!\n", debugstr_w(tmpstr));
977   }
978   /* select current drive in combo 2, omit missing drives */
979   {
980       char dir[MAX_PATH];
981       char str[4] = "a:\\";
982       GetCurrentDirectoryA( sizeof(dir), dir );
983       for(i = 0, n = -1; i < 26; i++)
984       {
985           str[0] = 'a' + i;
986           if (GetDriveTypeA(str) > DRIVE_NO_ROOT_DIR) n++;
987           if (toupper(str[0]) == toupper(dir[0])) break;
988       }
989   }
990   SendDlgItemMessageW(hWnd, cmb2, CB_SETCURSEL, n, 0);
991   if (!(ofn->Flags & OFN_SHOWHELP))
992     ShowWindow(GetDlgItem(hWnd, pshHelp), SW_HIDE);
993   if (ofn->Flags & OFN_HIDEREADONLY)
994     ShowWindow(GetDlgItem(hWnd, chx1), SW_HIDE);
995   if (lfs->hook)
996       return FD31_CallWindowProc(lfs, WM_INITDIALOG, wParam, lfs->lParam);
997   return TRUE;
998 }
999
1000 int FD31_GetFldrHeight(void)
1001 {
1002   return fldrHeight;
1003 }