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.
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/libgtk2ui/select_file_dialog_impl.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "grit/generated_resources.h"
22 #include "grit/ui_strings.h"
23 #include "ui/base/l10n/l10n_util.h"
25 // These conflict with base/tracked_objects.h, so need to come last.
29 using content::BrowserThread
;
33 std::string
GetTitle(const std::string
& title
, int message_id
) {
34 return title
.empty() ? l10n_util::GetStringUTF8(message_id
) : title
;
37 const char kKdialogBinary
[] = "kdialog";
43 // Implementation of SelectFileDialog that shows a KDE common dialog for
44 // choosing a file or folder. This acts as a modal dialog.
45 class SelectFileDialogImplKDE
: public SelectFileDialogImpl
{
47 SelectFileDialogImplKDE(Listener
* listener
,
48 ui::SelectFilePolicy
* policy
,
49 base::nix::DesktopEnvironment desktop
);
52 virtual ~SelectFileDialogImplKDE();
54 // SelectFileDialog implementation.
55 // |params| is user data we pass back via the Listener interface.
56 virtual void SelectFileImpl(Type type
,
57 const string16
& title
,
58 const FilePath
& default_path
,
59 const FileTypeInfo
* file_types
,
61 const FilePath::StringType
& default_extension
,
62 gfx::NativeWindow owning_window
,
63 void* params
) OVERRIDE
;
66 virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE
;
68 struct KDialogParams
{
69 KDialogParams(const std::string
& type
, const std::string
& title
,
70 const FilePath
& default_path
, gfx::NativeWindow parent
,
71 bool file_operation
, bool multiple_selection
,
73 void (SelectFileDialogImplKDE::*callback
)(const std::string
&,
75 : type(type
), title(title
), default_path(default_path
), parent(parent
),
76 file_operation(file_operation
),
77 multiple_selection(multiple_selection
),
78 kdialog_params(kdialog_params
), callback(callback
) {
83 FilePath default_path
;
84 gfx::NativeWindow parent
;
86 bool multiple_selection
;
88 void (SelectFileDialogImplKDE::*callback
)(const std::string
&, int, void*);
91 // Get the filters from |file_types_| and concatenate them into
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 FilePath
& default_path
, gfx::NativeWindow parent
,
98 bool file_operation
, bool multiple_selection
, CommandLine
* command_line
);
100 // Call KDialog on the FILE thread and post results back to the UI thread.
101 void CallKDialogOutput(const KDialogParams
& params
);
103 // Notifies the listener that a single file was chosen.
104 void FileSelected(const FilePath
& path
, void* params
);
106 // Notifies the listener that multiple files were chosen.
107 void MultiFilesSelected(const std::vector
<FilePath
>& files
, void* params
);
109 // Notifies the listener that no file was chosen (the action was canceled).
110 // Dialog is passed so we can find that |params| pointer that was passed to
111 // us when we were told to show the dialog.
112 void FileNotSelected(void *params
);
114 void CreateSelectFolderDialog(const std::string
& title
,
115 const FilePath
& default_path
,
116 gfx::NativeWindow parent
, void* params
);
118 void CreateFileOpenDialog(const std::string
& title
,
119 const FilePath
& default_path
,
120 gfx::NativeWindow parent
, void* params
);
122 void CreateMultiFileOpenDialog(const std::string
& title
,
123 const FilePath
& default_path
,
124 gfx::NativeWindow parent
, void* params
);
126 void CreateSaveAsDialog(const std::string
& title
,
127 const FilePath
& default_path
,
128 gfx::NativeWindow parent
, void* params
);
130 // Common function for OnSelectSingleFileDialogResponse and
131 // OnSelectSingleFolderDialogResponse.
132 void SelectSingleFileHelper(const std::string
& output
, int exit_code
,
133 void* params
, bool allow_folder
);
135 void OnSelectSingleFileDialogResponse(const std::string
& output
,
136 int exit_code
, void* params
);
137 void OnSelectMultiFileDialogResponse(const std::string
& output
,
138 int exit_code
, void* params
);
139 void OnSelectSingleFolderDialogResponse(const std::string
& output
,
140 int exit_code
, void* params
);
142 // Should be either DESKTOP_ENVIRONMENT_KDE3 or DESKTOP_ENVIRONMENT_KDE4.
143 base::nix::DesktopEnvironment desktop_
;
145 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplKDE
);
149 bool SelectFileDialogImpl::CheckKDEDialogWorksOnUIThread() {
150 // No choice. UI thread can't continue without an answer here. Fortunately we
151 // only do this once, the first time a file dialog is displayed.
152 base::ThreadRestrictions::ScopedAllowIO allow_io
;
154 CommandLine::StringVector cmd_vector
;
155 cmd_vector
.push_back(kKdialogBinary
);
156 cmd_vector
.push_back("--version");
157 CommandLine
command_line(cmd_vector
);
159 return base::GetAppOutput(command_line
, &dummy
);
163 SelectFileDialogImpl
* SelectFileDialogImpl::NewSelectFileDialogImplKDE(
165 ui::SelectFilePolicy
* policy
,
166 base::nix::DesktopEnvironment desktop
) {
167 return new SelectFileDialogImplKDE(listener
, policy
, desktop
);
170 SelectFileDialogImplKDE::SelectFileDialogImplKDE(
172 ui::SelectFilePolicy
* policy
,
173 base::nix::DesktopEnvironment desktop
)
174 : SelectFileDialogImpl(listener
, policy
),
176 DCHECK(desktop_
== base::nix::DESKTOP_ENVIRONMENT_KDE3
||
177 desktop_
== base::nix::DESKTOP_ENVIRONMENT_KDE4
);
180 SelectFileDialogImplKDE::~SelectFileDialogImplKDE() {
183 // We ignore |default_extension|.
184 void SelectFileDialogImplKDE::SelectFileImpl(
186 const string16
& title
,
187 const FilePath
& default_path
,
188 const FileTypeInfo
* file_types
,
190 const FilePath::StringType
& default_extension
,
191 gfx::NativeWindow owning_window
,
194 // |owning_window| can be null when user right-clicks on a downloadable item
195 // and chooses 'Open Link in New Tab' when 'Ask where to save each file
196 // before downloading.' preference is turned on. (http://crbug.com/29213)
198 parents_
.insert(owning_window
);
200 std::string title_string
= UTF16ToUTF8(title
);
202 file_type_index_
= file_type_index
;
204 file_types_
= *file_types
;
206 file_types_
.include_all_files
= true;
210 CreateSelectFolderDialog(title_string
, default_path
,
211 owning_window
, params
);
213 case SELECT_OPEN_FILE
:
214 CreateFileOpenDialog(title_string
, default_path
, owning_window
,
217 case SELECT_OPEN_MULTI_FILE
:
218 CreateMultiFileOpenDialog(title_string
, default_path
,
219 owning_window
, params
);
221 case SELECT_SAVEAS_FILE
:
222 CreateSaveAsDialog(title_string
, default_path
, owning_window
,
231 bool SelectFileDialogImplKDE::HasMultipleFileTypeChoicesImpl() {
232 return file_types_
.extensions
.size() > 1;
235 std::string
SelectFileDialogImplKDE::GetMimeTypeFilterString() {
236 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
237 std::string filter_string
;
238 // We need a filter set because the same mime type can appear multiple times.
239 std::set
<std::string
> filter_set
;
240 for (size_t i
= 0; i
< file_types_
.extensions
.size(); ++i
) {
241 for (size_t j
= 0; j
< file_types_
.extensions
[i
].size(); ++j
) {
242 if (!file_types_
.extensions
[i
][j
].empty()) {
243 std::string mime_type
= base::nix::GetFileMimeType(
244 FilePath("name").ReplaceExtension(file_types_
.extensions
[i
][j
]));
245 filter_set
.insert(mime_type
);
249 // Add the *.* filter, but only if we have added other filters (otherwise it
251 if (file_types_
.include_all_files
&& !file_types_
.extensions
.empty())
252 filter_set
.insert("application/octet-stream");
253 // Create the final output string.
254 filter_string
.clear();
255 for (std::set
<std::string
>::iterator it
= filter_set
.begin();
256 it
!= filter_set
.end(); ++it
) {
257 filter_string
.append(*it
+ " ");
259 return filter_string
;
262 void SelectFileDialogImplKDE::CallKDialogOutput(const KDialogParams
& params
) {
263 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
264 CommandLine::StringVector cmd_vector
;
265 cmd_vector
.push_back(kKdialogBinary
);
266 CommandLine
command_line(cmd_vector
);
267 GetKDialogCommandLine(params
.type
, params
.title
, params
.default_path
,
268 params
.parent
, params
.file_operation
,
269 params
.multiple_selection
, &command_line
);
272 // Get output from KDialog
273 base::GetAppOutputWithExitCode(command_line
, &output
, &exit_code
);
275 output
.erase(output
.size() - 1);
277 // Now the dialog is no longer showing. We can erase its parent from the
279 // TODO(erg): FIX THIS.
280 // std::set<GtkWindow*>::iterator iter = parents_.find(params.parent);
281 // if (iter != parents_.end())
282 // parents_.erase(iter);
284 BrowserThread::PostTask(
285 BrowserThread::UI
, FROM_HERE
,
286 base::Bind(params
.callback
, this, output
, exit_code
,
287 params
.kdialog_params
));
290 void SelectFileDialogImplKDE::GetKDialogCommandLine(const std::string
& type
,
291 const std::string
& title
, const FilePath
& path
,
292 gfx::NativeWindow parent
, bool file_operation
, bool multiple_selection
,
293 CommandLine
* command_line
) {
296 // Attach to the current Chrome window.
297 GdkWindow
* gdk_window
= gtk_widget_get_window(GTK_WIDGET((parent
)));
298 int window_id
= GDK_DRAWABLE_XID(gdk_window
);
299 command_line
->AppendSwitchNative(
300 desktop_
== base::nix::DESKTOP_ENVIRONMENT_KDE3
? "--embed" : "--attach",
301 base::IntToString(window_id
));
302 // Set the correct title for the dialog.
304 command_line
->AppendSwitchNative("--title", title
);
305 // Enable multiple file selection if we need to.
306 if (multiple_selection
) {
307 command_line
->AppendSwitch("--multiple");
308 command_line
->AppendSwitch("--separate-output");
310 command_line
->AppendSwitch(type
);
311 // The path should never be empty. If it is, set it to PWD.
313 command_line
->AppendArgPath(FilePath("."));
315 command_line
->AppendArgPath(path
);
316 // Depending on the type of the operation we need, get the path to the
317 // file/folder and set up mime type filters.
319 command_line
->AppendArg(GetMimeTypeFilterString());
320 VLOG(1) << "KDialog command line: " << command_line
->GetCommandLineString();
323 void SelectFileDialogImplKDE::FileSelected(const FilePath
& path
, void* params
) {
324 if (type_
== SELECT_SAVEAS_FILE
)
325 *last_saved_path_
= path
.DirName();
326 else if (type_
== SELECT_OPEN_FILE
)
327 *last_opened_path_
= path
.DirName();
328 else if (type_
== SELECT_FOLDER
)
329 *last_opened_path_
= path
;
332 if (listener_
) { // What does the filter index actually do?
333 // TODO(dfilimon): Get a reasonable index value from somewhere.
334 listener_
->FileSelected(path
, 1, params
);
338 void SelectFileDialogImplKDE::MultiFilesSelected(
339 const std::vector
<FilePath
>& files
, void* params
) {
340 *last_opened_path_
= files
[0].DirName();
342 listener_
->MultiFilesSelected(files
, params
);
345 void SelectFileDialogImplKDE::FileNotSelected(void* params
) {
347 listener_
->FileSelectionCanceled(params
);
350 void SelectFileDialogImplKDE::CreateSelectFolderDialog(
351 const std::string
& title
, const FilePath
& default_path
,
352 gfx::NativeWindow parent
, void *params
) {
353 BrowserThread::PostTask(
354 BrowserThread::FILE, FROM_HERE
,
356 &SelectFileDialogImplKDE::CallKDialogOutput
,
359 "--getexistingdirectory",
360 GetTitle(title
, IDS_SELECT_FOLDER_DIALOG_TITLE
),
361 default_path
.empty() ? *last_opened_path_
: default_path
,
362 parent
, false, false, params
,
363 &SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse
)));
366 void SelectFileDialogImplKDE::CreateFileOpenDialog(
367 const std::string
& title
, const FilePath
& default_path
,
368 gfx::NativeWindow parent
, void* params
) {
369 BrowserThread::PostTask(
370 BrowserThread::FILE, FROM_HERE
,
372 &SelectFileDialogImplKDE::CallKDialogOutput
,
376 GetTitle(title
, IDS_OPEN_FILE_DIALOG_TITLE
),
377 default_path
.empty() ? *last_opened_path_
: default_path
,
378 parent
, true, false, params
,
379 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse
)));
382 void SelectFileDialogImplKDE::CreateMultiFileOpenDialog(
383 const std::string
& title
, const FilePath
& default_path
,
384 gfx::NativeWindow parent
, void* params
) {
385 BrowserThread::PostTask(
386 BrowserThread::FILE, FROM_HERE
,
388 &SelectFileDialogImplKDE::CallKDialogOutput
,
392 GetTitle(title
, IDS_OPEN_FILES_DIALOG_TITLE
),
393 default_path
.empty() ? *last_opened_path_
: default_path
,
394 parent
, true, true, params
,
395 &SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse
)));
398 void SelectFileDialogImplKDE::CreateSaveAsDialog(
399 const std::string
& title
, const FilePath
& default_path
,
400 gfx::NativeWindow parent
, void* params
) {
401 BrowserThread::PostTask(
402 BrowserThread::FILE, FROM_HERE
,
404 &SelectFileDialogImplKDE::CallKDialogOutput
,
408 GetTitle(title
, IDS_SAVE_AS_DIALOG_TITLE
),
409 default_path
.empty() ? *last_saved_path_
: default_path
,
410 parent
, true, false, params
,
411 &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse
)));
414 void SelectFileDialogImplKDE::SelectSingleFileHelper(const std::string
& output
,
415 int exit_code
, void* params
, bool allow_folder
) {
416 VLOG(1) << "[kdialog] SingleFileResponse: " << output
;
417 if (exit_code
!= 0 || output
.empty()) {
418 FileNotSelected(params
);
422 FilePath
path(output
);
424 FileSelected(path
, params
);
428 if (CallDirectoryExistsOnUIThread(path
))
429 FileNotSelected(params
);
431 FileSelected(path
, params
);
434 void SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse(
435 const std::string
& output
, int exit_code
, void* params
) {
436 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
437 SelectSingleFileHelper(output
, exit_code
, params
, false);
440 void SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse(
441 const std::string
& output
, int exit_code
, void* params
) {
442 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
443 SelectSingleFileHelper(output
, exit_code
, params
, true);
446 void SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse(
447 const std::string
& output
, int exit_code
, void* params
) {
448 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
449 VLOG(1) << "[kdialog] MultiFileResponse: " << output
;
451 if (exit_code
!= 0 || output
.empty()) {
452 FileNotSelected(params
);
456 std::vector
<std::string
> filenames
;
457 Tokenize(output
, "\n", &filenames
);
458 std::vector
<FilePath
> filenames_fp
;
459 for (std::vector
<std::string
>::iterator iter
= filenames
.begin();
460 iter
!= filenames
.end(); ++iter
) {
461 FilePath
path(*iter
);
462 if (CallDirectoryExistsOnUIThread(path
))
464 filenames_fp
.push_back(path
);
467 if (filenames_fp
.empty()) {
468 FileNotSelected(params
);
471 MultiFilesSelected(filenames_fp
, params
);
474 } // namespace libgtk2ui