Fix Debug building on Windows
[platform/framework/web/crosswalk-tizen.git] / atom / browser / ui / file_dialog_win.cc
1 // Copyright (c) 2013 GitHub, Inc.
2 // Use of this source code is governed by the MIT license that can be
3 // found in the LICENSE file.
4
5 #include "atom/browser/ui/file_dialog.h"
6
7 #include <windows.h>  // windows.h must be included first
8
9 #include <atlbase.h>
10 #include <commdlg.h>
11 #include <shlobj.h>
12
13 #include "atom/browser/native_window_views.h"
14 #include "atom/browser/unresponsive_suppressor.h"
15 #include "base/files/file_util.h"
16 #include "base/i18n/case_conversion.h"
17 #include "base/strings/string_split.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/threading/thread.h"
21 #include "base/threading/thread_task_runner_handle.h"
22 #include "base/win/registry.h"
23 #include "third_party/wtl/include/atlapp.h"
24 #include "third_party/wtl/include/atldlgs.h"
25
26 namespace file_dialog {
27
28 namespace {
29
30 // Distinguish directories from regular files.
31 bool IsDirectory(const base::FilePath& path) {
32   base::File::Info file_info;
33   return base::GetFileInfo(path, &file_info) ?
34       file_info.is_directory : path.EndsWithSeparator();
35 }
36
37 void ConvertFilters(const Filters& filters,
38                     std::vector<std::wstring>* buffer,
39                     std::vector<COMDLG_FILTERSPEC>* filterspec) {
40   if (filters.empty()) {
41     COMDLG_FILTERSPEC spec = { L"All Files (*.*)", L"*.*" };
42     filterspec->push_back(spec);
43     return;
44   }
45
46   buffer->reserve(filters.size() * 2);
47   for (size_t i = 0; i < filters.size(); ++i) {
48     const Filter& filter = filters[i];
49
50     COMDLG_FILTERSPEC spec;
51     buffer->push_back(base::UTF8ToWide(filter.first));
52     spec.pszName = buffer->back().c_str();
53
54     std::vector<std::string> extensions(filter.second);
55     for (size_t j = 0; j < extensions.size(); ++j)
56       extensions[j].insert(0, "*.");
57     buffer->push_back(base::UTF8ToWide(base::JoinString(extensions, ";")));
58     spec.pszSpec = buffer->back().c_str();
59
60     filterspec->push_back(spec);
61   }
62 }
63
64 // Generic class to delegate common open/save dialog's behaviours, users need to
65 // call interface methods via GetPtr().
66 template <typename T>
67 class FileDialog {
68  public:
69   FileDialog(const base::FilePath& default_path,
70              const std::string& title,
71              const std::string& button_label,
72              const Filters& filters, int options) {
73     std::wstring file_part;
74     if (!IsDirectory(default_path))
75       file_part = default_path.BaseName().value();
76
77     std::vector<std::wstring> buffer;
78     std::vector<COMDLG_FILTERSPEC> filterspec;
79     ConvertFilters(filters, &buffer, &filterspec);
80
81     dialog_.reset(new T(file_part.c_str(), options, NULL,
82                         filterspec.data(), filterspec.size()));
83
84     if (!title.empty())
85       GetPtr()->SetTitle(base::UTF8ToUTF16(title).c_str());
86
87     if (!button_label.empty())
88       GetPtr()->SetOkButtonLabel(base::UTF8ToUTF16(button_label).c_str());
89
90     // By default, *.* will be added to the file name if file type is "*.*". In
91     // Electron, we disable it to make a better experience.
92     //
93     // From MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/
94     // bb775970(v=vs.85).aspx
95     //
96     // If SetDefaultExtension is not called, the dialog will not update
97     // automatically when user choose a new file type in the file dialog.
98     //
99     // We set file extension to the first none-wildcard extension to make
100     // sure the dialog will update file extension automatically.
101     for (size_t i = 0; i < filterspec.size(); ++i) {
102       if (std::wstring(filterspec[i].pszSpec) != L"*.*") {
103         // SetFileTypeIndex is regarded as one-based index.
104         GetPtr()->SetFileTypeIndex(i+1);
105         GetPtr()->SetDefaultExtension(filterspec[i].pszSpec);
106         break;
107       }
108     }
109
110     SetDefaultFolder(default_path);
111   }
112
113   bool Show(atom::NativeWindow* parent_window) {
114     atom::UnresponsiveSuppressor suppressor;
115     HWND window = parent_window ? static_cast<atom::NativeWindowViews*>(
116         parent_window)->GetAcceleratedWidget() :
117         NULL;
118     return dialog_->DoModal(window) == IDOK;
119   }
120
121   T* GetDialog() { return dialog_.get(); }
122
123   IFileDialog* GetPtr() const { return dialog_->GetPtr(); }
124
125  private:
126   // Set up the initial directory for the dialog.
127   void SetDefaultFolder(const base::FilePath file_path) {
128     std::wstring directory = IsDirectory(file_path) ?
129         file_path.value() :
130         file_path.DirName().value();
131
132     ATL::CComPtr<IShellItem> folder_item;
133     HRESULT hr = SHCreateItemFromParsingName(directory.c_str(),
134                                              NULL,
135                                              IID_PPV_ARGS(&folder_item));
136     if (SUCCEEDED(hr))
137       GetPtr()->SetFolder(folder_item);
138   }
139
140   std::unique_ptr<T> dialog_;
141
142   DISALLOW_COPY_AND_ASSIGN(FileDialog);
143 };
144
145 struct RunState {
146   base::Thread* dialog_thread;
147   scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner;
148 };
149
150 bool CreateDialogThread(RunState* run_state) {
151   std::unique_ptr<base::Thread> thread(
152       new base::Thread(ATOM_PRODUCT_NAME "FileDialogThread"));
153   thread->init_com_with_mta(false);
154   if (!thread->Start())
155     return false;
156
157   run_state->dialog_thread = thread.release();
158   run_state->ui_task_runner = base::ThreadTaskRunnerHandle::Get();
159   return true;
160 }
161
162 void RunOpenDialogInNewThread(const RunState& run_state,
163                               atom::NativeWindow* parent,
164                               const std::string& title,
165                               const std::string& button_label,
166                               const base::FilePath& default_path,
167                               const Filters& filters,
168                               int properties,
169                               const OpenDialogCallback& callback) {
170   std::vector<base::FilePath> paths;
171   bool result = ShowOpenDialog(parent, title, button_label, default_path,
172                                filters, properties, &paths);
173   run_state.ui_task_runner->PostTask(FROM_HERE,
174                                       base::Bind(callback, result, paths));
175   run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread);
176 }
177
178 void RunSaveDialogInNewThread(const RunState& run_state,
179                               atom::NativeWindow* parent,
180                               const std::string& title,
181                               const std::string& button_label,
182                               const base::FilePath& default_path,
183                               const Filters& filters,
184                               const SaveDialogCallback& callback) {
185   base::FilePath path;
186   bool result = ShowSaveDialog(parent, title, button_label, default_path,
187                                filters, &path);
188   run_state.ui_task_runner->PostTask(FROM_HERE,
189                                      base::Bind(callback, result, path));
190   run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread);
191 }
192
193 }  // namespace
194
195 bool ShowOpenDialog(atom::NativeWindow* parent_window,
196                     const std::string& title,
197                     const std::string& button_label,
198                     const base::FilePath& default_path,
199                     const Filters& filters,
200                     int properties,
201                     std::vector<base::FilePath>* paths) {
202   int options = FOS_FORCEFILESYSTEM | FOS_FILEMUSTEXIST;
203   if (properties & FILE_DIALOG_OPEN_DIRECTORY)
204     options |= FOS_PICKFOLDERS;
205   if (properties & FILE_DIALOG_MULTI_SELECTIONS)
206     options |= FOS_ALLOWMULTISELECT;
207   if (properties & FILE_DIALOG_SHOW_HIDDEN_FILES)
208     options |= FOS_FORCESHOWHIDDEN;
209   if (properties & FILE_DIALOG_PROMPT_TO_CREATE)
210     options |= FOS_CREATEPROMPT;
211
212   FileDialog<CShellFileOpenDialog> open_dialog(
213       default_path, title, button_label, filters, options);
214   if (!open_dialog.Show(parent_window))
215     return false;
216
217   ATL::CComPtr<IShellItemArray> items;
218   HRESULT hr = static_cast<IFileOpenDialog*>(open_dialog.GetPtr())->GetResults(
219       &items);
220   if (FAILED(hr))
221     return false;
222
223   ATL::CComPtr<IShellItem> item;
224   DWORD count = 0;
225   hr = items->GetCount(&count);
226   if (FAILED(hr))
227     return false;
228
229   paths->reserve(count);
230   for (DWORD i = 0; i < count; ++i) {
231     hr = items->GetItemAt(i, &item);
232     if (FAILED(hr))
233       return false;
234
235     wchar_t file_name[MAX_PATH];
236     hr = CShellFileOpenDialog::GetFileNameFromShellItem(
237         item, SIGDN_FILESYSPATH, file_name, MAX_PATH);
238     if (FAILED(hr))
239       return false;
240
241     paths->push_back(base::FilePath(file_name));
242   }
243
244   return true;
245 }
246
247 void ShowOpenDialog(atom::NativeWindow* parent,
248                     const std::string& title,
249                     const std::string& button_label,
250                     const base::FilePath& default_path,
251                     const Filters& filters,
252                     int properties,
253                     const OpenDialogCallback& callback) {
254   RunState run_state;
255   if (!CreateDialogThread(&run_state)) {
256     callback.Run(false, std::vector<base::FilePath>());
257     return;
258   }
259
260   run_state.dialog_thread->task_runner()->PostTask(
261       FROM_HERE,
262       base::Bind(&RunOpenDialogInNewThread, run_state, parent, title,
263                  button_label, default_path, filters, properties, callback));
264 }
265
266 bool ShowSaveDialog(atom::NativeWindow* parent_window,
267                     const std::string& title,
268                     const std::string& button_label,
269                     const base::FilePath& default_path,
270                     const Filters& filters,
271                     base::FilePath* path) {
272   FileDialog<CShellFileSaveDialog> save_dialog(
273       default_path, title, button_label, filters,
274       FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | FOS_OVERWRITEPROMPT);
275   if (!save_dialog.Show(parent_window))
276     return false;
277
278   wchar_t buffer[MAX_PATH];
279   HRESULT hr = save_dialog.GetDialog()->GetFilePath(buffer, MAX_PATH);
280   if (FAILED(hr))
281     return false;
282
283   *path = base::FilePath(buffer);
284   return true;
285 }
286
287 void ShowSaveDialog(atom::NativeWindow* parent,
288                     const std::string& title,
289                     const std::string& button_label,
290                     const base::FilePath& default_path,
291                     const Filters& filters,
292                     const SaveDialogCallback& callback) {
293   RunState run_state;
294   if (!CreateDialogThread(&run_state)) {
295     callback.Run(false, base::FilePath());
296     return;
297   }
298
299   run_state.dialog_thread->task_runner()->PostTask(
300       FROM_HERE,
301       base::Bind(&RunSaveDialogInNewThread, run_state, parent, title,
302                  button_label, default_path, filters, callback));
303 }
304
305 }  // namespace file_dialog