OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include <set> | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/bind_helpers.h" | |
9 #include "base/command_line.h" | |
10 #include "base/logging.h" | |
11 #include "base/nix/mime_util_xdg.h" | |
12 #include "base/nix/xdg_util.h" | |
13 #include "base/process_util.h" | |
14 #include "base/string_number_conversions.h" | |
15 #include "base/string_util.h" | |
16 #include "base/threading/thread_restrictions.h" | |
17 #include "base/utf_string_conversions.h" | |
18 #include "chrome/browser/ui/gtk/select_file_dialog_impl.h" | |
19 | |
20 // TODO(erg): Move all of this into WorkerPool. | |
21 #include "content/public/browser/browser_thread.h" | |
22 #include "grit/generated_resources.h" | |
23 #include "ui/base/l10n/l10n_util.h" | |
24 | |
25 // These conflict with base/tracked_objects.h, so need to come last. | |
26 #include <gdk/gdkx.h> | |
27 #include <gtk/gtk.h> | |
28 | |
29 using content::BrowserThread; | |
30 | |
31 namespace { | |
32 | |
33 std::string GetTitle(const std::string& title, int message_id) { | |
34 return title.empty() ? l10n_util::GetStringUTF8(message_id) : title; | |
35 } | |
36 | |
37 const char kKdialogBinary[] = "kdialog"; | |
38 | |
39 } // namespace | |
40 | |
41 // Implementation of SelectFileDialog that shows a KDE common dialog for | |
42 // choosing a file or folder. This acts as a modal dialog. | |
43 class SelectFileDialogImplKDE : public SelectFileDialogImpl { | |
44 public: | |
45 SelectFileDialogImplKDE(Listener* listener, | |
46 ui::SelectFilePolicy* policy, | |
47 base::nix::DesktopEnvironment desktop); | |
48 | |
49 protected: | |
50 virtual ~SelectFileDialogImplKDE(); | |
51 | |
52 // SelectFileDialog implementation. | |
53 // |params| is user data we pass back via the Listener interface. | |
54 virtual void SelectFileImpl(Type type, | |
55 const string16& title, | |
56 const FilePath& default_path, | |
57 const FileTypeInfo* file_types, | |
58 int file_type_index, | |
59 const FilePath::StringType& default_extension, | |
60 gfx::NativeWindow owning_window, | |
61 void* params) OVERRIDE; | |
62 | |
63 private: | |
64 virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE; | |
65 | |
66 struct KDialogParams { | |
67 KDialogParams(const std::string& type, const std::string& title, | |
68 const FilePath& default_path, gfx::NativeWindow parent, | |
69 bool file_operation, bool multiple_selection, | |
70 void* kdialog_params, | |
71 void (SelectFileDialogImplKDE::*callback)(const std::string&, | |
72 int, void*)) | |
73 : type(type), title(title), default_path(default_path), parent(parent), | |
74 file_operation(file_operation), | |
75 multiple_selection(multiple_selection), | |
76 kdialog_params(kdialog_params), callback(callback) { | |
77 } | |
78 | |
79 std::string type; | |
80 std::string title; | |
81 FilePath default_path; | |
82 gfx::NativeWindow parent; | |
83 bool file_operation; | |
84 bool multiple_selection; | |
85 void* kdialog_params; | |
86 void (SelectFileDialogImplKDE::*callback)(const std::string&, int, void*); | |
87 }; | |
88 | |
89 // Get the filters from |file_types_| and concatenate them into | |
90 // |filter_string|. | |
91 std::string GetMimeTypeFilterString(); | |
92 | |
93 // Get KDialog command line representing the Argv array for KDialog. | |
94 void GetKDialogCommandLine(const std::string& type, const std::string& title, | |
95 const FilePath& default_path, gfx::NativeWindow parent, | |
96 bool file_operation, bool multiple_selection, CommandLine* command_line); | |
97 | |
98 // Call KDialog on the FILE thread and post results back to the UI thread. | |
99 void CallKDialogOutput(const KDialogParams& params); | |
100 | |
101 // Notifies the listener that a single file was chosen. | |
102 void FileSelected(const FilePath& path, void* params); | |
103 | |
104 // Notifies the listener that multiple files were chosen. | |
105 void MultiFilesSelected(const std::vector<FilePath>& files, void* params); | |
106 | |
107 // Notifies the listener that no file was chosen (the action was canceled). | |
108 // Dialog is passed so we can find that |params| pointer that was passed to | |
109 // us when we were told to show the dialog. | |
110 void FileNotSelected(void *params); | |
111 | |
112 void CreateSelectFolderDialog(const std::string& title, | |
113 const FilePath& default_path, | |
114 gfx::NativeWindow parent, void* params); | |
115 | |
116 void CreateFileOpenDialog(const std::string& title, | |
117 const FilePath& default_path, | |
118 gfx::NativeWindow parent, void* params); | |
119 | |
120 void CreateMultiFileOpenDialog(const std::string& title, | |
121 const FilePath& default_path, | |
122 gfx::NativeWindow parent, void* params); | |
123 | |
124 void CreateSaveAsDialog(const std::string& title, | |
125 const FilePath& default_path, | |
126 gfx::NativeWindow parent, void* params); | |
127 | |
128 // Common function for OnSelectSingleFileDialogResponse and | |
129 // OnSelectSingleFolderDialogResponse. | |
130 void SelectSingleFileHelper(const std::string& output, int exit_code, | |
131 void* params, bool allow_folder); | |
132 | |
133 void OnSelectSingleFileDialogResponse(const std::string& output, | |
134 int exit_code, void* params); | |
135 void OnSelectMultiFileDialogResponse(const std::string& output, | |
136 int exit_code, void* params); | |
137 void OnSelectSingleFolderDialogResponse(const std::string& output, | |
138 int exit_code, void* params); | |
139 | |
140 // Should be either DESKTOP_ENVIRONMENT_KDE3 or DESKTOP_ENVIRONMENT_KDE4. | |
141 base::nix::DesktopEnvironment desktop_; | |
142 | |
143 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplKDE); | |
144 }; | |
145 | |
146 // static | |
147 bool SelectFileDialogImpl::CheckKDEDialogWorksOnUIThread() { | |
148 // No choice. UI thread can't continue without an answer here. Fortunately we | |
149 // only do this once, the first time a file dialog is displayed. | |
150 base::ThreadRestrictions::ScopedAllowIO allow_io; | |
151 | |
152 CommandLine::StringVector cmd_vector; | |
153 cmd_vector.push_back(kKdialogBinary); | |
154 cmd_vector.push_back("--version"); | |
155 CommandLine command_line(cmd_vector); | |
156 std::string dummy; | |
157 return base::GetAppOutput(command_line, &dummy); | |
158 } | |
159 | |
160 // static | |
161 SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplKDE( | |
162 Listener* listener, | |
163 ui::SelectFilePolicy* policy, | |
164 base::nix::DesktopEnvironment desktop) { | |
165 return new SelectFileDialogImplKDE(listener, policy, desktop); | |
166 } | |
167 | |
168 SelectFileDialogImplKDE::SelectFileDialogImplKDE( | |
169 Listener* listener, | |
170 ui::SelectFilePolicy* policy, | |
171 base::nix::DesktopEnvironment desktop) | |
172 : SelectFileDialogImpl(listener, policy), | |
173 desktop_(desktop) { | |
174 DCHECK(desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE3 || | |
175 desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE4); | |
176 } | |
177 | |
178 SelectFileDialogImplKDE::~SelectFileDialogImplKDE() { | |
179 } | |
180 | |
181 // We ignore |default_extension|. | |
182 void SelectFileDialogImplKDE::SelectFileImpl( | |
183 Type type, | |
184 const string16& title, | |
185 const FilePath& default_path, | |
186 const FileTypeInfo* file_types, | |
187 int file_type_index, | |
188 const FilePath::StringType& default_extension, | |
189 gfx::NativeWindow owning_window, | |
190 void* params) { | |
191 type_ = type; | |
192 // |owning_window| can be null when user right-clicks on a downloadable item | |
193 // and chooses 'Open Link in New Tab' when 'Ask where to save each file | |
194 // before downloading.' preference is turned on. (http://crbug.com/29213) | |
195 if (owning_window) | |
196 parents_.insert(owning_window); | |
197 | |
198 std::string title_string = UTF16ToUTF8(title); | |
199 | |
200 file_type_index_ = file_type_index; | |
201 if (file_types) | |
202 file_types_ = *file_types; | |
203 else | |
204 file_types_.include_all_files = true; | |
205 | |
206 switch (type) { | |
207 case SELECT_FOLDER: | |
208 CreateSelectFolderDialog(title_string, default_path, | |
209 owning_window, params); | |
210 return; | |
211 case SELECT_OPEN_FILE: | |
212 CreateFileOpenDialog(title_string, default_path, owning_window, | |
213 params); | |
214 return; | |
215 case SELECT_OPEN_MULTI_FILE: | |
216 CreateMultiFileOpenDialog(title_string, default_path, | |
217 owning_window, params); | |
218 return; | |
219 case SELECT_SAVEAS_FILE: | |
220 CreateSaveAsDialog(title_string, default_path, owning_window, | |
221 params); | |
222 return; | |
223 default: | |
224 NOTREACHED(); | |
225 return; | |
226 } | |
227 } | |
228 | |
229 bool SelectFileDialogImplKDE::HasMultipleFileTypeChoicesImpl() { | |
230 return file_types_.extensions.size() > 1; | |
231 } | |
232 | |
233 std::string SelectFileDialogImplKDE::GetMimeTypeFilterString() { | |
234 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
235 std::string filter_string; | |
236 // We need a filter set because the same mime type can appear multiple times. | |
237 std::set<std::string> filter_set; | |
238 for (size_t i = 0; i < file_types_.extensions.size(); ++i) { | |
239 for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) { | |
240 if (!file_types_.extensions[i][j].empty()) { | |
241 std::string mime_type = base::nix::GetFileMimeType( | |
242 FilePath("name").ReplaceExtension(file_types_.extensions[i][j])); | |
243 filter_set.insert(mime_type); | |
244 } | |
245 } | |
246 } | |
247 // Add the *.* filter, but only if we have added other filters (otherwise it | |
248 // is implied). | |
249 if (file_types_.include_all_files && !file_types_.extensions.empty()) | |
250 filter_set.insert("application/octet-stream"); | |
251 // Create the final output string. | |
252 filter_string.clear(); | |
253 for (std::set<std::string>::iterator it = filter_set.begin(); | |
254 it != filter_set.end(); ++it) { | |
255 filter_string.append(*it + " "); | |
256 } | |
257 return filter_string; | |
258 } | |
259 | |
260 void SelectFileDialogImplKDE::CallKDialogOutput(const KDialogParams& params) { | |
261 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
262 CommandLine::StringVector cmd_vector; | |
263 cmd_vector.push_back(kKdialogBinary); | |
264 CommandLine command_line(cmd_vector); | |
265 GetKDialogCommandLine(params.type, params.title, params.default_path, | |
266 params.parent, params.file_operation, | |
267 params.multiple_selection, &command_line); | |
268 std::string output; | |
269 int exit_code; | |
270 // Get output from KDialog | |
271 base::GetAppOutputWithExitCode(command_line, &output, &exit_code); | |
272 if (!output.empty()) | |
273 output.erase(output.size() - 1); | |
274 // Now the dialog is no longer showing. We can erase its parent from the | |
275 // parent set. | |
276 std::set<GtkWindow*>::iterator iter = parents_.find(params.parent); | |
277 if (iter != parents_.end()) | |
278 parents_.erase(iter); | |
279 BrowserThread::PostTask( | |
280 BrowserThread::UI, FROM_HERE, | |
281 base::Bind(params.callback, this, output, exit_code, | |
282 params.kdialog_params)); | |
283 } | |
284 | |
285 void SelectFileDialogImplKDE::GetKDialogCommandLine(const std::string& type, | |
286 const std::string& title, const FilePath& path, | |
287 gfx::NativeWindow parent, bool file_operation, bool multiple_selection, | |
288 CommandLine* command_line) { | |
289 CHECK(command_line); | |
290 | |
291 // Attach to the current Chrome window. | |
292 GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET((parent))); | |
293 int window_id = GDK_DRAWABLE_XID(gdk_window); | |
294 command_line->AppendSwitchNative( | |
295 desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE3 ? "--embed" : "--attach", | |
296 base::IntToString(window_id)); | |
297 // Set the correct title for the dialog. | |
298 if (!title.empty()) | |
299 command_line->AppendSwitchNative("--title", title); | |
300 // Enable multiple file selection if we need to. | |
301 if (multiple_selection) { | |
302 command_line->AppendSwitch("--multiple"); | |
303 command_line->AppendSwitch("--separate-output"); | |
304 } | |
305 command_line->AppendSwitch(type); | |
306 // The path should never be empty. If it is, set it to PWD. | |
307 if (path.empty()) | |
308 command_line->AppendArgPath(FilePath(".")); | |
309 else | |
310 command_line->AppendArgPath(path); | |
311 // Depending on the type of the operation we need, get the path to the | |
312 // file/folder and set up mime type filters. | |
313 if (file_operation) | |
314 command_line->AppendArg(GetMimeTypeFilterString()); | |
315 VLOG(1) << "KDialog command line: " << command_line->GetCommandLineString(); | |
316 } | |
317 | |
318 void SelectFileDialogImplKDE::FileSelected(const FilePath& path, void* params) { | |
319 if (type_ == SELECT_SAVEAS_FILE) | |
320 *last_saved_path_ = path.DirName(); | |
321 else if (type_ == SELECT_OPEN_FILE) | |
322 *last_opened_path_ = path.DirName(); | |
323 else if (type_ == SELECT_FOLDER) | |
324 *last_opened_path_ = path; | |
325 else | |
326 NOTREACHED(); | |
327 if (listener_) { // What does the filter index actually do? | |
328 // TODO(dfilimon): Get a reasonable index value from somewhere. | |
329 listener_->FileSelected(path, 1, params); | |
330 } | |
331 } | |
332 | |
333 void SelectFileDialogImplKDE::MultiFilesSelected( | |
334 const std::vector<FilePath>& files, void* params) { | |
335 *last_opened_path_ = files[0].DirName(); | |
336 if (listener_) | |
337 listener_->MultiFilesSelected(files, params); | |
338 } | |
339 | |
340 void SelectFileDialogImplKDE::FileNotSelected(void* params) { | |
341 if (listener_) | |
342 listener_->FileSelectionCanceled(params); | |
343 } | |
344 | |
345 void SelectFileDialogImplKDE::CreateSelectFolderDialog( | |
346 const std::string& title, const FilePath& default_path, | |
347 gfx::NativeWindow parent, void *params) { | |
348 BrowserThread::PostTask( | |
349 BrowserThread::FILE, FROM_HERE, | |
350 base::Bind( | |
351 &SelectFileDialogImplKDE::CallKDialogOutput, | |
352 this, | |
353 KDialogParams( | |
354 "--getexistingdirectory", | |
355 GetTitle(title, IDS_SELECT_FOLDER_DIALOG_TITLE), | |
356 default_path.empty() ? *last_opened_path_ : default_path, | |
357 parent, false, false, params, | |
358 &SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse))); | |
359 } | |
360 | |
361 void SelectFileDialogImplKDE::CreateFileOpenDialog( | |
362 const std::string& title, const FilePath& default_path, | |
363 gfx::NativeWindow parent, void* params) { | |
364 BrowserThread::PostTask( | |
365 BrowserThread::FILE, FROM_HERE, | |
366 base::Bind( | |
367 &SelectFileDialogImplKDE::CallKDialogOutput, | |
368 this, | |
369 KDialogParams( | |
370 "--getopenfilename", | |
371 GetTitle(title, IDS_OPEN_FILE_DIALOG_TITLE), | |
372 default_path.empty() ? *last_opened_path_ : default_path, | |
373 parent, true, false, params, | |
374 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse))); | |
375 } | |
376 | |
377 void SelectFileDialogImplKDE::CreateMultiFileOpenDialog( | |
378 const std::string& title, const FilePath& default_path, | |
379 gfx::NativeWindow parent, void* params) { | |
380 BrowserThread::PostTask( | |
381 BrowserThread::FILE, FROM_HERE, | |
382 base::Bind( | |
383 &SelectFileDialogImplKDE::CallKDialogOutput, | |
384 this, | |
385 KDialogParams( | |
386 "--getopenfilename", | |
387 GetTitle(title, IDS_OPEN_FILES_DIALOG_TITLE), | |
388 default_path.empty() ? *last_opened_path_ : default_path, | |
389 parent, true, true, params, | |
390 &SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse))); | |
391 } | |
392 | |
393 void SelectFileDialogImplKDE::CreateSaveAsDialog( | |
394 const std::string& title, const FilePath& default_path, | |
395 gfx::NativeWindow parent, void* params) { | |
396 BrowserThread::PostTask( | |
397 BrowserThread::FILE, FROM_HERE, | |
398 base::Bind( | |
399 &SelectFileDialogImplKDE::CallKDialogOutput, | |
400 this, | |
401 KDialogParams( | |
402 "--getsavefilename", | |
403 GetTitle(title, IDS_SAVE_AS_DIALOG_TITLE), | |
404 default_path.empty() ? *last_saved_path_ : default_path, | |
405 parent, true, false, params, | |
406 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse))); | |
407 } | |
408 | |
409 void SelectFileDialogImplKDE::SelectSingleFileHelper(const std::string& output, | |
410 int exit_code, void* params, bool allow_folder) { | |
411 VLOG(1) << "[kdialog] SingleFileResponse: " << output; | |
412 if (exit_code != 0 || output.empty()) { | |
413 FileNotSelected(params); | |
414 return; | |
415 } | |
416 | |
417 FilePath path(output); | |
418 if (allow_folder) { | |
419 FileSelected(path, params); | |
420 return; | |
421 } | |
422 | |
423 if (CallDirectoryExistsOnUIThread(path)) | |
424 FileNotSelected(params); | |
425 else | |
426 FileSelected(path, params); | |
427 } | |
428 | |
429 void SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse( | |
430 const std::string& output, int exit_code, void* params) { | |
431 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
432 SelectSingleFileHelper(output, exit_code, params, false); | |
433 } | |
434 | |
435 void SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse( | |
436 const std::string& output, int exit_code, void* params) { | |
437 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
438 SelectSingleFileHelper(output, exit_code, params, true); | |
439 } | |
440 | |
441 void SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse( | |
442 const std::string& output, int exit_code, void* params) { | |
443 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
444 VLOG(1) << "[kdialog] MultiFileResponse: " << output; | |
445 | |
446 if (exit_code != 0 || output.empty()) { | |
447 FileNotSelected(params); | |
448 return; | |
449 } | |
450 | |
451 std::vector<std::string> filenames; | |
452 Tokenize(output, "\n", &filenames); | |
453 std::vector<FilePath> filenames_fp; | |
454 for (std::vector<std::string>::iterator iter = filenames.begin(); | |
455 iter != filenames.end(); ++iter) { | |
456 FilePath path(*iter); | |
457 if (CallDirectoryExistsOnUIThread(path)) | |
458 continue; | |
459 filenames_fp.push_back(path); | |
460 } | |
461 | |
462 if (filenames_fp.empty()) { | |
463 FileNotSelected(params); | |
464 return; | |
465 } | |
466 MultiFilesSelected(filenames_fp, params); | |
467 } | |
OLD | NEW |