1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
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.
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.
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.
20 /////////////////////////////////////////////////////////////////////////////
22 * @file FileOrFolderSelect.cpp
24 * @brief Implementation of the file and folder selection routines.
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.
34 #include "Environment.h"
38 static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam,
40 static void ConvertFilter(LPTSTR filterStr);
42 /** @brief Last selected folder for folder selection dialog. */
43 static String LastSelectedFolder;
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
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*/)
67 path.clear(); // Clear output param
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};
73 // check if specified path is a file
74 if (initialPath && initialPath[0])
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)
82 paths::SplitFilename(initialPath, 0, &temp, 0);
83 lstrcpy(sSelectedFile, temp.c_str());
87 String filters = sfilter, title = stitle;
89 filters = _("All Files (*.*)|*.*||");
92 title = is_open ? _("Open") : _("Save As");
95 // Convert extension mask from MFC style separators ('|')
96 // to Win32 style separators ('\0')
97 LPTSTR filtersStr = &*filters.begin();
98 ConvertFilter(filtersStr);
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;
114 bool bRetVal = false;
116 bRetVal = !!GetOpenFileName((OPENFILENAME *)&ofn);
118 bRetVal = !!GetSaveFileName((OPENFILENAME *)&ofn);
119 // common file dialog populated sSelectedFile variable's buffer
122 path = sSelectedFile;
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)
135 bool SelectFolder(String& path, LPCTSTR root_path /*=NULL*/,
136 const String& stitle /*=_T("")*/,
137 HWND hwndOwner /*=NULL*/)
141 TCHAR szPath[MAX_PATH_FULL] = {0};
143 String title = stitle;
144 if (root_path == NULL)
145 LastSelectedFolder.clear();
147 LastSelectedFolder = root_path;
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;
157 pidl = SHBrowseForFolder(&bi);
160 if (SHGetPathFromIDList(pidl, szPath))
171 * @brief Callback function for setting selected folder for folder dialog.
173 static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam,
176 // Look for BFFM_INITIALIZED
177 if (uMsg == BFFM_INITIALIZED)
180 SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
182 SendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM)LastSelectedFolder.c_str());
184 else if (uMsg == BFFM_VALIDATEFAILED)
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);
191 if (!paths::CreateIfNeeded((TCHAR*)lParam))
193 MessageBox(hwnd, _("Failed to create folder.").c_str(), NULL, MB_OK | MB_ICONWARNING);
202 * @brief Shows file/folder selection dialog.
204 * We need this custom function so we can select files and folders with the
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.
215 bool SelectFileOrFolder(HWND parent, String& path, LPCTSTR initialPath /*=NULL*/)
217 String title = _("Open");
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];
223 // check if specified path is a file
224 if (initialPath && initialPath[0])
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)
232 paths::SplitFilename(initialPath, 0, &temp, 0);
233 lstrcpy(sSelectedFile, temp.c_str());
237 String filters = _("All Files (*.*)|*.*||");
239 // Convert extension mask from MFC style separators ('|')
240 // to Win32 style separators ('\0')
241 LPTSTR filtersStr = &*filters.begin();
242 ConvertFilter(filtersStr);
244 String dirSelTag = _("Folder Selection");
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?
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;
262 bool bRetVal = !!GetOpenFileName((OPENFILENAME *)&ofn);
266 path = sSelectedFile;
267 if (paths::DoesPathExist(path) == paths::DOES_NOT_EXIST)
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);
280 * @brief Helper function for converting filter format.
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.
286 * @param [in,out] filterStr
287 * - in Mask string to convert
288 * - out Converted string
290 static void ConvertFilter(LPTSTR filterStr)
292 while (TCHAR *ch = _tcschr(filterStr, '|'))