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