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