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