OSDN Git Service

Merge pull request #93 from GreyMerlin/master
[winmerge-jp/winmerge-jp.git] / Src / FileOrFolderSelect.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //
6 //    This program is free software; you can redistribute it and/or modify
7 //    it under the terms of the GNU General Public License as published by
8 //    the Free Software Foundation; either version 2 of the License, or
9 //    (at your option) any later version.
10 //
11 //    This program is distributed in the hope that it will be useful,
12 //    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //    GNU General Public License for more details.
15 //
16 //    You should have received a copy of the GNU General Public License
17 //    along with this program; if not, write to the Free Software
18 //    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 //
20 /////////////////////////////////////////////////////////////////////////////
21 /** 
22  * @file  FileOrFolderSelect.cpp
23  *
24  * @brief Implementation of the file and folder selection routines.
25  */
26
27 #include <windows.h>
28 #include "FileOrFolderSelect.h"
29 #pragma warning (push)                  // prevent "warning C4091: 'typedef ': ignored on left of 'tagGPFIDL_FLAGS' when no variable is declared"
30 #pragma warning (disable:4091)  // VC bug when using XP enabled toolsets.
31 #include <shlobj.h>
32 #pragma warning (pop)
33 #include <sys/stat.h>
34 #include "Environment.h"
35 #include "paths.h"
36 #include "MergeApp.h"
37
38 static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam,
39                 LPARAM lpData);
40 static void ConvertFilter(LPTSTR filterStr);
41
42 /** @brief Last selected folder for folder selection dialog. */
43 static String LastSelectedFolder;
44
45 /**
46  * @brief Helper function for selecting folder or file.
47  * This function shows standard Windows file selection dialog for selecting
48  * file or folder to open or file to save. The last parameter @p is_open selects
49  * between open or save modes. Biggest difference is that in save-mode Windows
50  * asks if user wants to override existing file.
51  * @param [in] parent Handle to parent window. Can be a NULL, but then
52  *     CMainFrame is used which can cause modality problems.
53  * @param [out] path Selected path is returned in this string
54  * @param [in] initialPath Initial path (and file) shown when dialog is opened
55  * @param [in] titleid Resource string ID for dialog title.
56  * @param [in] filterid 0 or STRING ID for filter string
57  *     - 0 means "All files (*.*)". Note the string formatting!
58  * @param [in] is_open Selects Open/Save -dialog (mode).
59  * @note Be careful when setting @p parent to NULL as there are potential
60  * modality problems with this. Dialog can be lost behind other windows!
61  * @param [in] defaultExtension Extension to append if user doesn't provide one
62  */
63 BOOL SelectFile(HWND parent, String& path,BOOL is_open /*=TRUE*/,
64                 LPCTSTR initialPath /*=NULL*/, const String& stitle /*=_T("")*/,
65                 const String& sfilter /*=_T("")*/, LPCTSTR defaultExtension /*=NULL*/)
66 {
67         path.clear(); // Clear output param
68
69         // This will tell common file dialog what to show
70         // and also this will hold its return value
71         TCHAR sSelectedFile[MAX_PATH_FULL] = {0};
72
73         // check if specified path is a file
74         if (initialPath && initialPath[0])
75         {
76                 // If initial path info includes a file
77                 // we put the bare filename into sSelectedFile
78                 // so the common file dialog will start up with that file selected
79                 if (paths::DoesPathExist(initialPath) == paths::IS_EXISTING_FILE)
80                 {
81                         String temp;
82                         paths::SplitFilename(initialPath, 0, &temp, 0);
83                         lstrcpy(sSelectedFile, temp.c_str());
84                 }
85         }
86
87         String filters = sfilter, title = stitle;
88         if (sfilter.empty())
89                 filters = _("All Files (*.*)|*.*||");
90         if (stitle.empty())
91         {
92                 title = is_open ? _("Open") : _("Save As");
93         }
94
95         // Convert extension mask from MFC style separators ('|')
96         //  to Win32 style separators ('\0')
97         LPTSTR filtersStr = &*filters.begin();
98         ConvertFilter(filtersStr);
99
100         OPENFILENAME_NT4 ofn = { sizeof OPENFILENAME_NT4 };
101         ofn.hwndOwner = parent;
102         ofn.lpstrFilter = filtersStr;
103         ofn.lpstrCustomFilter = NULL;
104         ofn.nFilterIndex = 1;
105         ofn.lpstrFile = sSelectedFile;
106         ofn.nMaxFile = MAX_PATH_FULL;
107         ofn.lpstrInitialDir = initialPath;
108         ofn.lpstrTitle = title.c_str();
109         ofn.lpstrFileTitle = NULL;
110         if (defaultExtension)
111                 ofn.lpstrDefExt = defaultExtension;
112         ofn.Flags = OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR;
113
114         BOOL bRetVal = FALSE;
115         if (is_open)
116                 bRetVal = GetOpenFileName((OPENFILENAME *)&ofn);
117         else
118                 bRetVal = GetSaveFileName((OPENFILENAME *)&ofn);
119         // common file dialog populated sSelectedFile variable's buffer
120
121         if (bRetVal)
122                 path = sSelectedFile;
123         
124         return bRetVal;
125 }
126
127 /** 
128  * @brief Helper function for selecting directory
129  * @param [out] path Selected path is returned in this string
130  * @param [in] root_path Initial path shown when dialog is opened
131  * @param [in] titleid Resource string ID for dialog title.
132  * @param [in] hwndOwner Handle to owner window or NULL
133  * @return TRUE if valid folder selected (not cancelled)
134  */
135 BOOL SelectFolder(String& path, LPCTSTR root_path /*=NULL*/, 
136                         const String& stitle /*=_T("")*/, 
137                         HWND hwndOwner /*=NULL*/) 
138 {
139         BROWSEINFO bi;
140         LPITEMIDLIST pidl;
141         TCHAR szPath[MAX_PATH_FULL] = {0};
142         BOOL bRet = FALSE;
143         String title = stitle;
144         if (root_path == NULL)
145                 LastSelectedFolder.clear();
146         else
147                 LastSelectedFolder = root_path;
148
149         bi.hwndOwner = hwndOwner;
150         bi.pidlRoot = NULL;  // Start from desktop folder
151         bi.pszDisplayName = szPath;
152         bi.lpszTitle = title.c_str();
153         bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI | BIF_VALIDATE;
154         bi.lpfn = BrowseCallbackProc;
155         bi.lParam = (LPARAM)root_path;
156
157         pidl = SHBrowseForFolder(&bi);
158         if (pidl)
159         {
160                 if (SHGetPathFromIDList(pidl, szPath))
161                 {
162                         path = szPath;
163                         bRet = TRUE;
164                 }
165                 CoTaskMemFree(pidl);
166         }
167         return bRet;
168 }
169
170 /**
171  * @brief Callback function for setting selected folder for folder dialog.
172  */
173 static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam,
174                 LPARAM lpData)
175 {
176         // Look for BFFM_INITIALIZED
177         if (uMsg == BFFM_INITIALIZED)
178         {
179                 if (lpData)
180                         SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
181                 else
182                         SendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM)LastSelectedFolder.c_str());
183         }
184         else if (uMsg == BFFM_VALIDATEFAILED)
185         {
186                 String strMessage = 
187                         strutils::format_string1(_("%1 does not exist. Do you want to create it?"), (TCHAR *)lParam);
188                 int answer = MessageBox(hwnd, strMessage.c_str(), NULL, MB_YESNO);
189                 if (answer == IDYES)
190                 {
191                         if (!paths::CreateIfNeeded((TCHAR*)lParam))
192                         {
193                                 MessageBox(hwnd, _("Failed to create folder.").c_str(), NULL, MB_OK | MB_ICONWARNING);
194                         }
195                 }
196                 return 1;
197         }
198         return 0;
199 }
200
201 /** 
202  * @brief Shows file/folder selection dialog.
203  *
204  * We need this custom function so we can select files and folders with the
205  * same dialog.
206  * - If existing filename is selected return it
207  * - If filename in (CFileDialog) editbox and current folder doesn't form
208  * a valid path to file, return current folder.
209  * @param [in] parent Handle to parent window. Can be a NULL, but then
210  *     CMainFrame is used which can cause modality problems.
211  * @param [out] path Selected folder/filename
212  * @param [in] initialPath Initial file or folder shown/selected.
213  * @return TRUE if user choosed a file/folder, FALSE if user canceled dialog.
214  */
215 BOOL SelectFileOrFolder(HWND parent, String& path, LPCTSTR initialPath /*=NULL*/)
216 {
217         String title = _("Open");
218
219         // This will tell common file dialog what to show
220         // and also this will hold its return value
221         TCHAR sSelectedFile[MAX_PATH_FULL];
222
223         // check if specified path is a file
224         if (initialPath && initialPath[0])
225         {
226                 // If initial path info includes a file
227                 // we put the bare filename into sSelectedFile
228                 // so the common file dialog will start up with that file selected
229                 if (paths::DoesPathExist(initialPath) == paths::IS_EXISTING_FILE)
230                 {
231                         String temp;
232                         paths::SplitFilename(initialPath, 0, &temp, 0);
233                         lstrcpy(sSelectedFile, temp.c_str());
234                 }
235         }
236
237         String filters = _("All Files (*.*)|*.*||");
238
239         // Convert extension mask from MFC style separators ('|')
240         //  to Win32 style separators ('\0')
241         LPTSTR filtersStr = &*filters.begin();
242         ConvertFilter(filtersStr);
243
244         String dirSelTag = _("Folder Selection");
245
246         // Set initial filename to folder selection tag
247         dirSelTag += _T("."); // Treat it as filename
248         lstrcpy(sSelectedFile, dirSelTag.c_str()); // What is assignment above good for?
249
250         OPENFILENAME_NT4 ofn = { sizeof OPENFILENAME_NT4 };
251         ofn.hwndOwner = parent;
252         ofn.lpstrFilter = filtersStr;
253         ofn.lpstrCustomFilter = NULL;
254         ofn.nFilterIndex = 1;
255         ofn.lpstrFile = sSelectedFile;
256         ofn.nMaxFile = MAX_PATH_FULL;
257         ofn.lpstrInitialDir = initialPath;
258         ofn.lpstrTitle = title.c_str();
259         ofn.lpstrFileTitle = NULL;
260         ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_NOTESTFILECREATE | OFN_NOCHANGEDIR;
261
262         BOOL bRetVal = GetOpenFileName((OPENFILENAME *)&ofn);
263
264         if (bRetVal)
265         {
266                 path = sSelectedFile;
267                 if (paths::DoesPathExist(path) == paths::DOES_NOT_EXIST)
268                 {
269                         // We have a valid folder name, but propably garbage as a filename.
270                         // Return folder name
271                         String folder = paths::GetPathOnly(sSelectedFile);
272                         path = paths::AddTrailingSlash(folder);
273                 }
274         }
275         return bRetVal;
276 }
277
278
279 /** 
280  * @brief Helper function for converting filter format.
281  *
282  * MFC functions separate filter strings with | char which is also
283  * good choice to safe into resource. But WinAPI32 functions we use
284  * needs '\0' as separator. This function replaces '|'s with '\0's.
285  *
286  * @param [in,out] filterStr
287  * - in Mask string to convert
288  * - out Converted string
289  */
290 static void ConvertFilter(LPTSTR filterStr)
291 {
292         while (TCHAR *ch = _tcschr(filterStr, '|'))
293         {
294                 filterStr = ch + 1;
295                 *ch = '\0';
296         }
297 }