OSDN Git Service

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