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.
29 #include "FileOrFolderSelect.h"
30 #pragma warning (push) // prevent "warning C4091: 'typedef ': ignored on left of 'tagGPFIDL_FLAGS' when no variable is declared"
31 #pragma warning (disable:4091) // VC bug when using XP enabled toolsets.
35 #include "Environment.h"
39 static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam,
41 static void ConvertFilter(LPTSTR filterStr);
43 /** @brief Last selected folder for folder selection dialog. */
44 static String LastSelectedFolder;
47 * @brief Helper function for selecting folder or file.
48 * This function shows standard Windows file selection dialog for selecting
49 * file or folder to open or file to save. The last parameter @p is_open selects
50 * between open or save modes. Biggest difference is that in save-mode Windows
51 * asks if user wants to override existing file.
52 * @param [in] parent Handle to parent window. Can be `nullptr`, but then
53 * CMainFrame is used which can cause modality problems.
54 * @param [out] path Selected path is returned in this string
55 * @param [in] initialPath Initial path (and file) shown when dialog is opened
56 * @param [in] titleid Resource string ID for dialog title.
57 * @param [in] filterid 0 or STRING ID for filter string
58 * - 0 means "All files (*.*)". Note the string formatting!
59 * @param [in] is_open Selects Open/Save -dialog (mode).
60 * @note Be careful when setting @p parent to `nullptr` as there are potential
61 * modality problems with this. Dialog can be lost behind other windows!
62 * @param [in] defaultExtension Extension to append if user doesn't provide one
64 bool SelectFile(HWND parent, String& path, bool is_open /*= true*/,
65 LPCTSTR initialPath /*= nullptr*/, const String& stitle /*=_T("")*/,
66 const String& sfilter /*=_T("")*/, LPCTSTR defaultExtension /*= nullptr*/)
68 path.clear(); // Clear output param
70 // This will tell common file dialog what to show
71 // and also this will hold its return value
72 TCHAR sSelectedFile[MAX_PATH_FULL] = {0};
74 // check if specified path is a file
75 if (initialPath && initialPath[0])
77 // If initial path info includes a file
78 // we put the bare filename into sSelectedFile
79 // so the common file dialog will start up with that file selected
80 if (paths::DoesPathExist(initialPath) == paths::IS_EXISTING_FILE)
83 paths::SplitFilename(initialPath, 0, &temp, 0);
84 lstrcpy(sSelectedFile, temp.c_str());
88 String filters = sfilter, title = stitle;
90 filters = _("All Files (*.*)|*.*||");
93 title = is_open ? _("Open") : _("Save As");
96 // Convert extension mask from MFC style separators ('|')
97 // to Win32 style separators ('\0')
98 LPTSTR filtersStr = &*filters.begin();
99 ConvertFilter(filtersStr);
101 OPENFILENAME_NT4 ofn = { sizeof OPENFILENAME_NT4 };
102 ofn.hwndOwner = parent;
103 ofn.lpstrFilter = filtersStr;
104 ofn.lpstrCustomFilter = nullptr;
105 ofn.nFilterIndex = 1;
106 ofn.lpstrFile = sSelectedFile;
107 ofn.nMaxFile = MAX_PATH_FULL;
108 ofn.lpstrInitialDir = initialPath;
109 ofn.lpstrTitle = title.c_str();
110 ofn.lpstrFileTitle = nullptr;
111 if (defaultExtension)
112 ofn.lpstrDefExt = defaultExtension;
113 ofn.Flags = OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR;
115 bool bRetVal = false;
117 bRetVal = !!GetOpenFileName((OPENFILENAME *)&ofn);
119 bRetVal = !!GetSaveFileName((OPENFILENAME *)&ofn);
120 // common file dialog populated sSelectedFile variable's buffer
123 path = sSelectedFile;
129 * @brief Helper function for selecting directory
130 * @param [out] path Selected path is returned in this string
131 * @param [in] root_path Initial path shown when dialog is opened
132 * @param [in] titleid Resource string ID for dialog title.
133 * @param [in] hwndOwner Handle to owner window or `nullptr`
134 * @return `true` if valid folder selected (not cancelled)
136 bool SelectFolder(String& path, LPCTSTR root_path /*= nullptr*/,
137 const String& stitle /*=_T("")*/,
138 HWND hwndOwner /*= nullptr*/)
142 TCHAR szPath[MAX_PATH_FULL] = {0};
144 String title = stitle;
145 if (root_path == nullptr)
146 LastSelectedFolder.clear();
148 LastSelectedFolder = root_path;
150 bi.hwndOwner = hwndOwner;
151 bi.pidlRoot = nullptr; // Start from desktop folder
152 bi.pszDisplayName = szPath;
153 bi.lpszTitle = title.c_str();
154 bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI | BIF_VALIDATE;
155 bi.lpfn = BrowseCallbackProc;
156 bi.lParam = (LPARAM)root_path;
158 pidl = SHBrowseForFolder(&bi);
161 if (SHGetPathFromIDList(pidl, szPath))
172 * @brief Callback function for setting selected folder for folder dialog.
174 static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam,
177 // Look for BFFM_INITIALIZED
178 if (uMsg == BFFM_INITIALIZED)
181 SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
183 SendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM)LastSelectedFolder.c_str());
185 else if (uMsg == BFFM_VALIDATEFAILED)
188 strutils::format_string1(_("%1 does not exist. Do you want to create it?"), (TCHAR *)lParam);
189 int answer = MessageBox(hwnd, strMessage.c_str(), nullptr, MB_YESNO);
192 if (!paths::CreateIfNeeded((TCHAR*)lParam))
194 MessageBox(hwnd, _("Failed to create folder.").c_str(), nullptr, MB_OK | MB_ICONWARNING);
203 * @brief Shows file/folder selection dialog.
205 * We need this custom function so we can select files and folders with the
207 * - If existing filename is selected return it
208 * - If filename in (CFileDialog) editbox and current folder doesn't form
209 * a valid path to file, return current folder.
210 * @param [in] parent Handle to parent window. Can be `nullptr`, but then
211 * CMainFrame is used which can cause modality problems.
212 * @param [out] path Selected folder/filename
213 * @param [in] initialPath Initial file or folder shown/selected.
214 * @return `true` if user choosed a file/folder, `false` if user canceled dialog.
216 bool SelectFileOrFolder(HWND parent, String& path, LPCTSTR initialPath /*= nullptr*/)
218 String title = _("Open");
220 // This will tell common file dialog what to show
221 // and also this will hold its return value
222 TCHAR sSelectedFile[MAX_PATH_FULL];
224 // check if specified path is a file
225 if (initialPath!=nullptr && initialPath[0] != '\0')
227 // If initial path info includes a file
228 // we put the bare filename into sSelectedFile
229 // so the common file dialog will start up with that file selected
230 if (paths::DoesPathExist(initialPath) == paths::IS_EXISTING_FILE)
233 paths::SplitFilename(initialPath, 0, &temp, 0);
234 lstrcpy(sSelectedFile, temp.c_str());
238 String filters = _("All Files (*.*)|*.*||");
240 // Convert extension mask from MFC style separators ('|')
241 // to Win32 style separators ('\0')
242 LPTSTR filtersStr = &*filters.begin();
243 ConvertFilter(filtersStr);
245 String dirSelTag = _("Folder Selection");
247 // Set initial filename to folder selection tag
248 dirSelTag += _T("."); // Treat it as filename
249 lstrcpy(sSelectedFile, dirSelTag.c_str()); // What is assignment above good for?
251 OPENFILENAME_NT4 ofn = { sizeof OPENFILENAME_NT4 };
252 ofn.hwndOwner = parent;
253 ofn.lpstrFilter = filtersStr;
254 ofn.lpstrCustomFilter = nullptr;
255 ofn.nFilterIndex = 1;
256 ofn.lpstrFile = sSelectedFile;
257 ofn.nMaxFile = MAX_PATH_FULL;
258 ofn.lpstrInitialDir = initialPath;
259 ofn.lpstrTitle = title.c_str();
260 ofn.lpstrFileTitle = nullptr;
261 ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_NOTESTFILECREATE | OFN_NOCHANGEDIR;
263 bool bRetVal = !!GetOpenFileName((OPENFILENAME *)&ofn);
267 path = sSelectedFile;
268 if (paths::DoesPathExist(path) == paths::DOES_NOT_EXIST)
270 // We have a valid folder name, but propably garbage as a filename.
271 // Return folder name
272 String folder = paths::GetPathOnly(sSelectedFile);
273 path = paths::AddTrailingSlash(folder);
281 * @brief Helper function for converting filter format.
283 * MFC functions separate filter strings with | char which is also
284 * good choice to safe into resource. But WinAPI32 functions we use
285 * needs '\0' as separator. This function replaces '|'s with '\0's.
287 * @param [in,out] filterStr
288 * - in Mask string to convert
289 * - out Converted string
291 static void ConvertFilter(LPTSTR filterStr)
294 while ( (ch = _tcschr(filterStr, '|')) != nullptr)