OSDN Git Service

Reduce compilation time (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 //
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 "pch.h"
28 #include <windows.h>
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.
32 #include <shlobj.h>
33 #pragma warning (pop)
34 #include <sys/stat.h>
35 #include "Environment.h"
36 #include "paths.h"
37 #include "MergeApp.h"
38
39 static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam,
40                 LPARAM lpData);
41 static void ConvertFilter(LPTSTR filterStr);
42
43 /** @brief Last selected folder for folder selection dialog. */
44 static String LastSelectedFolder;
45
46 /**
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
63  */
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*/)
67 {
68         path.clear(); // Clear output param
69
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};
73
74         // check if specified path is a file
75         if (initialPath && initialPath[0])
76         {
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)
81                 {
82                         String temp;
83                         paths::SplitFilename(initialPath, 0, &temp, 0);
84                         lstrcpy(sSelectedFile, temp.c_str());
85                 }
86         }
87
88         String filters = sfilter, title = stitle;
89         if (sfilter.empty())
90                 filters = _("All Files (*.*)|*.*||");
91         if (stitle.empty())
92         {
93                 title = is_open ? _("Open") : _("Save As");
94         }
95
96         // Convert extension mask from MFC style separators ('|')
97         //  to Win32 style separators ('\0')
98         LPTSTR filtersStr = &*filters.begin();
99         ConvertFilter(filtersStr);
100
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;
114
115         bool bRetVal = false;
116         if (is_open)
117                 bRetVal = !!GetOpenFileName((OPENFILENAME *)&ofn);
118         else
119                 bRetVal = !!GetSaveFileName((OPENFILENAME *)&ofn);
120         // common file dialog populated sSelectedFile variable's buffer
121
122         if (bRetVal)
123                 path = sSelectedFile;
124         
125         return bRetVal;
126 }
127
128 /** 
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)
135  */
136 bool SelectFolder(String& path, LPCTSTR root_path /*= nullptr*/, 
137                         const String& stitle /*=_T("")*/, 
138                         HWND hwndOwner /*= nullptr*/) 
139 {
140         BROWSEINFO bi;
141         LPITEMIDLIST pidl;
142         TCHAR szPath[MAX_PATH_FULL] = {0};
143         bool bRet = false;
144         String title = stitle;
145         if (root_path == nullptr)
146                 LastSelectedFolder.clear();
147         else
148                 LastSelectedFolder = root_path;
149
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;
157
158         pidl = SHBrowseForFolder(&bi);
159         if (pidl != nullptr)
160         {
161                 if (SHGetPathFromIDList(pidl, szPath))
162                 {
163                         path = szPath;
164                         bRet = true;
165                 }
166                 CoTaskMemFree(pidl);
167         }
168         return bRet;
169 }
170
171 /**
172  * @brief Callback function for setting selected folder for folder dialog.
173  */
174 static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam,
175                 LPARAM lpData)
176 {
177         // Look for BFFM_INITIALIZED
178         if (uMsg == BFFM_INITIALIZED)
179         {
180                 if (lpData != NULL)
181                         SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
182                 else
183                         SendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM)LastSelectedFolder.c_str());
184         }
185         else if (uMsg == BFFM_VALIDATEFAILED)
186         {
187                 String strMessage = 
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);
190                 if (answer == IDYES)
191                 {
192                         if (!paths::CreateIfNeeded((TCHAR*)lParam))
193                         {
194                                 MessageBox(hwnd, _("Failed to create folder.").c_str(), nullptr, MB_OK | MB_ICONWARNING);
195                         }
196                 }
197                 return 1;
198         }
199         return 0;
200 }
201
202 /** 
203  * @brief Shows file/folder selection dialog.
204  *
205  * We need this custom function so we can select files and folders with the
206  * same dialog.
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.
215  */
216 bool SelectFileOrFolder(HWND parent, String& path, LPCTSTR initialPath /*= nullptr*/)
217 {
218         String title = _("Open");
219
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];
223
224         // check if specified path is a file
225         if (initialPath!=nullptr && initialPath[0] != '\0')
226         {
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)
231                 {
232                         String temp;
233                         paths::SplitFilename(initialPath, 0, &temp, 0);
234                         lstrcpy(sSelectedFile, temp.c_str());
235                 }
236         }
237
238         String filters = _("All Files (*.*)|*.*||");
239
240         // Convert extension mask from MFC style separators ('|')
241         //  to Win32 style separators ('\0')
242         LPTSTR filtersStr = &*filters.begin();
243         ConvertFilter(filtersStr);
244
245         String dirSelTag = _("Folder Selection");
246
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?
250
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;
262
263         bool bRetVal = !!GetOpenFileName((OPENFILENAME *)&ofn);
264
265         if (bRetVal)
266         {
267                 path = sSelectedFile;
268                 if (paths::DoesPathExist(path) == paths::DOES_NOT_EXIST)
269                 {
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);
274                 }
275         }
276         return bRetVal;
277 }
278
279
280 /** 
281  * @brief Helper function for converting filter format.
282  *
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.
286  *
287  * @param [in,out] filterStr
288  * - in Mask string to convert
289  * - out Converted string
290  */
291 static void ConvertFilter(LPTSTR filterStr)
292 {
293         TCHAR *ch;
294         while ( (ch = _tcschr(filterStr, '|')) != nullptr)
295         {
296                 filterStr = ch + 1;
297                 *ch = '\0';
298         }
299 }