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