summaryrefslogtreecommitdiffstats
path: root/ui/shell_dialogs
diff options
context:
space:
mode:
authorananta@chromium.org <ananta@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-16 23:08:27 +0000
committerananta@chromium.org <ananta@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-16 23:08:27 +0000
commite8c62b968fba790e682b0d77416131a2e916f475 (patch)
treee4755e37cf6208c182aa8c5290923e16b5f53cf3 /ui/shell_dialogs
parentfdc9eec417bb1eaf98485ab7e1285fcb432bcbcd (diff)
downloadchromium_src-e8c62b968fba790e682b0d77416131a2e916f475.zip
chromium_src-e8c62b968fba790e682b0d77416131a2e916f475.tar.gz
chromium_src-e8c62b968fba790e682b0d77416131a2e916f475.tar.bz2
Bring back the file dialogs for chrome AURA.
This CL copies the ui\base\dialogs sources to a new directory ui\shell_folders. This is done due to problems with gcl not working correctly in conjunction with svn mv and local edits leading to tryjob issues. The code in ui\shell_folders is not compiled at the moment. This will be compiled when CL https://codereview.chromium.org/11878031 lands. BUG=170483 TBR=ben git-svn-id: svn://svn.chromium.org/chrome/trunk/src@177251 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui/shell_dialogs')
-rw-r--r--ui/shell_dialogs/base_shell_dialog.cc11
-rw-r--r--ui/shell_dialogs/base_shell_dialog.h30
-rw-r--r--ui/shell_dialogs/base_shell_dialog_win.cc78
-rw-r--r--ui/shell_dialogs/base_shell_dialog_win.h97
-rw-r--r--ui/shell_dialogs/gtk/OWNERS2
-rw-r--r--ui/shell_dialogs/gtk/select_file_dialog_impl.cc91
-rw-r--r--ui/shell_dialogs/gtk/select_file_dialog_impl.h88
-rw-r--r--ui/shell_dialogs/gtk/select_file_dialog_impl_gtk.cc580
-rw-r--r--ui/shell_dialogs/gtk/select_file_dialog_impl_kde.cc470
-rw-r--r--ui/shell_dialogs/select_file_dialog.cc141
-rw-r--r--ui/shell_dialogs/select_file_dialog.h201
-rw-r--r--ui/shell_dialogs/select_file_dialog_android.cc113
-rw-r--r--ui/shell_dialogs/select_file_dialog_android.h66
-rw-r--r--ui/shell_dialogs/select_file_dialog_factory.cc11
-rw-r--r--ui/shell_dialogs/select_file_dialog_factory.h29
-rw-r--r--ui/shell_dialogs/select_file_dialog_mac.h19
-rw-r--r--ui/shell_dialogs/select_file_dialog_mac.mm424
-rw-r--r--ui/shell_dialogs/select_file_dialog_win.cc864
-rw-r--r--ui/shell_dialogs/select_file_dialog_win.h29
-rw-r--r--ui/shell_dialogs/select_file_dialog_win_unittest.cc45
-rw-r--r--ui/shell_dialogs/select_file_policy.cc11
-rw-r--r--ui/shell_dialogs/select_file_policy.h27
-rw-r--r--ui/shell_dialogs/selected_file_info.cc22
-rw-r--r--ui/shell_dialogs/selected_file_info.h43
24 files changed, 3492 insertions, 0 deletions
diff --git a/ui/shell_dialogs/base_shell_dialog.cc b/ui/shell_dialogs/base_shell_dialog.cc
new file mode 100644
index 0000000..ae8912a
--- /dev/null
+++ b/ui/shell_dialogs/base_shell_dialog.cc
@@ -0,0 +1,11 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/dialogs/base_shell_dialog.h"
+
+namespace ui {
+
+BaseShellDialog::~BaseShellDialog() {}
+
+} // namespace ui
diff --git a/ui/shell_dialogs/base_shell_dialog.h b/ui/shell_dialogs/base_shell_dialog.h
new file mode 100644
index 0000000..a4b954f
--- /dev/null
+++ b/ui/shell_dialogs/base_shell_dialog.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_DIALOGS_BASE_SHELL_DIALOG_H_
+#define UI_BASE_DIALOGS_BASE_SHELL_DIALOG_H_
+
+#include "ui/base/ui_export.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace ui {
+
+// A base class for shell dialogs.
+class UI_EXPORT BaseShellDialog {
+ public:
+ // Returns true if a shell dialog box is currently being shown modally
+ // to the specified owner.
+ virtual bool IsRunning(gfx::NativeWindow owning_window) const = 0;
+
+ // Notifies the dialog box that the listener has been destroyed and it should
+ // no longer be sent notifications.
+ virtual void ListenerDestroyed() = 0;
+
+ protected:
+ virtual ~BaseShellDialog();
+};
+
+} // namespace ui
+
+#endif // UI_BASE_DIALOGS_BASE_SHELL_DIALOG_H_
diff --git a/ui/shell_dialogs/base_shell_dialog_win.cc b/ui/shell_dialogs/base_shell_dialog_win.cc
new file mode 100644
index 0000000..39bbbb7
--- /dev/null
+++ b/ui/shell_dialogs/base_shell_dialog_win.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/dialogs/base_shell_dialog_win.h"
+
+#include <algorithm>
+
+#include "base/threading/thread.h"
+#include "base/win/scoped_com_initializer.h"
+
+namespace ui {
+
+// static
+BaseShellDialogImpl::Owners BaseShellDialogImpl::owners_;
+int BaseShellDialogImpl::instance_count_ = 0;
+
+BaseShellDialogImpl::BaseShellDialogImpl() {
+ ++instance_count_;
+}
+
+BaseShellDialogImpl::~BaseShellDialogImpl() {
+ // All runs should be complete by the time this is called!
+ if (--instance_count_ == 0)
+ DCHECK(owners_.empty());
+}
+
+BaseShellDialogImpl::RunState BaseShellDialogImpl::BeginRun(HWND owner) {
+ // Cannot run a modal shell dialog if one is already running for this owner.
+ DCHECK(!IsRunningDialogForOwner(owner));
+ // The owner must be a top level window, otherwise we could end up with two
+ // entries in our map for the same top level window.
+ DCHECK(!owner || owner == GetAncestor(owner, GA_ROOT));
+ RunState run_state;
+ run_state.dialog_thread = CreateDialogThread();
+ run_state.owner = owner;
+ if (owner) {
+ owners_.insert(owner);
+ DisableOwner(owner);
+ }
+ return run_state;
+}
+
+void BaseShellDialogImpl::EndRun(RunState run_state) {
+ if (run_state.owner) {
+ DCHECK(IsRunningDialogForOwner(run_state.owner));
+ EnableOwner(run_state.owner);
+ DCHECK(owners_.find(run_state.owner) != owners_.end());
+ owners_.erase(run_state.owner);
+ }
+ DCHECK(run_state.dialog_thread);
+ delete run_state.dialog_thread;
+}
+
+bool BaseShellDialogImpl::IsRunningDialogForOwner(HWND owner) const {
+ return (owner && owners_.find(owner) != owners_.end());
+}
+
+void BaseShellDialogImpl::DisableOwner(HWND owner) {
+ if (IsWindow(owner))
+ EnableWindow(owner, FALSE);
+}
+
+// static
+base::Thread* BaseShellDialogImpl::CreateDialogThread() {
+ base::Thread* thread = new base::Thread("Chrome_ShellDialogThread");
+ thread->init_com_with_mta(false);
+ bool started = thread->Start();
+ DCHECK(started);
+ return thread;
+}
+
+void BaseShellDialogImpl::EnableOwner(HWND owner) {
+ if (IsWindow(owner))
+ EnableWindow(owner, TRUE);
+}
+
+} // namespace ui
diff --git a/ui/shell_dialogs/base_shell_dialog_win.h b/ui/shell_dialogs/base_shell_dialog_win.h
new file mode 100644
index 0000000..dcac33b
--- /dev/null
+++ b/ui/shell_dialogs/base_shell_dialog_win.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_DIALOGS_BASE_SHELL_DIALOG_WIN_H_
+#define UI_BASE_DIALOGS_BASE_SHELL_DIALOG_WIN_H_
+
+#include <shlobj.h>
+#include <set>
+
+#include "ui/base/ui_export.h"
+#include "ui/base/dialogs/base_shell_dialog.h"
+
+namespace base {
+class Thread;
+}
+
+namespace ui {
+
+///////////////////////////////////////////////////////////////////////////////
+// A base class for all shell dialog implementations that handles showing a
+// shell dialog modally on its own thread.
+class UI_EXPORT BaseShellDialogImpl {
+ public:
+ BaseShellDialogImpl();
+ virtual ~BaseShellDialogImpl();
+
+ protected:
+ // Represents a run of a dialog.
+ struct UI_EXPORT RunState {
+ // Owning HWND, may be null.
+ HWND owner;
+
+ // Thread dialog is run on.
+ base::Thread* dialog_thread;
+ };
+
+ // Called at the beginning of a modal dialog run. Disables the owner window
+ // and tracks it. Returns the message loop of the thread that the dialog will
+ // be run on.
+ RunState BeginRun(HWND owner);
+
+ // Cleans up after a dialog run. If the run_state has a valid HWND this makes
+ // sure that the window is enabled. This is essential because BeginRun
+ // aggressively guards against multiple modal dialogs per HWND. Must be called
+ // on the UI thread after the result of the dialog has been determined.
+ //
+ // In addition this deletes the Thread in RunState.
+ void EndRun(RunState run_state);
+
+ // Returns true if a modal shell dialog is currently active for the specified
+ // owner. Must be called on the UI thread.
+ bool IsRunningDialogForOwner(HWND owner) const;
+
+ // Disables the window |owner|. Can be run from either the ui or the dialog
+ // thread. Can be called on either the UI or the dialog thread. This function
+ // is called on the dialog thread after the modal Windows Common dialog
+ // functions return because Windows automatically re-enables the owning
+ // window when those functions return, but we don't actually want them to be
+ // re-enabled until the response of the dialog propagates back to the UI
+ // thread, so we disable the owner manually after the Common dialog function
+ // returns.
+ void DisableOwner(HWND owner);
+
+ private:
+ typedef std::set<HWND> Owners;
+
+ // Creates a thread to run a shell dialog on. Each dialog requires its own
+ // thread otherwise in some situations where a singleton owns a single
+ // instance of this object we can have a situation where a modal dialog in
+ // one window blocks the appearance of a modal dialog in another.
+ static base::Thread* CreateDialogThread();
+
+ // Enables the window |owner_|. Can only be run from the ui thread.
+ void EnableOwner(HWND owner);
+
+ // A list of windows that currently own active shell dialogs for this
+ // instance. For example, if the DownloadManager owns an instance of this
+ // object and there are two browser windows open both with Save As dialog
+ // boxes active, this list will consist of the two browser windows' HWNDs.
+ // The derived class must call EndRun once the dialog is done showing to
+ // remove the owning HWND from this list.
+ // This object is static since it is maintained for all instances of this
+ // object - i.e. you can't have two file pickers open for the
+ // same owner, even though they might be represented by different instances
+ // of this object.
+ // This set only contains non-null HWNDs. NULL hwnds are not added to this
+ // list.
+ static Owners owners_;
+ static int instance_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(BaseShellDialogImpl);
+};
+
+} // namespace ui
+
+#endif // UI_BASE_DIALOGS_BASE_SHELL_DIALOG_WIN_H_
diff --git a/ui/shell_dialogs/gtk/OWNERS b/ui/shell_dialogs/gtk/OWNERS
new file mode 100644
index 0000000..0573e6b
--- /dev/null
+++ b/ui/shell_dialogs/gtk/OWNERS
@@ -0,0 +1,2 @@
+erg@chromium.org
+estade@chromium.org
diff --git a/ui/shell_dialogs/gtk/select_file_dialog_impl.cc b/ui/shell_dialogs/gtk/select_file_dialog_impl.cc
new file mode 100644
index 0000000..0ec03e9
--- /dev/null
+++ b/ui/shell_dialogs/gtk/select_file_dialog_impl.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file implements common select dialog functionality between GTK and KDE.
+
+#include "ui/base/dialogs/gtk/select_file_dialog_impl.h"
+
+#include "base/environment.h"
+#include "base/file_util.h"
+#include "base/nix/xdg_util.h"
+#include "base/threading/thread_restrictions.h"
+
+namespace {
+
+enum UseKdeFileDialogStatus {
+ UNKNOWN,
+ NO_KDE,
+ YES_KDE
+};
+
+UseKdeFileDialogStatus use_kde_ = UNKNOWN;
+
+} // namespace
+
+namespace ui {
+
+FilePath* SelectFileDialogImpl::last_saved_path_ = NULL;
+FilePath* SelectFileDialogImpl::last_opened_path_ = NULL;
+
+// static
+SelectFileDialog* CreateLinuxSelectFileDialog(
+ SelectFileDialog::Listener* listener,
+ SelectFilePolicy* policy) {
+ if (use_kde_ == UNKNOWN) {
+ // Start out assumimg we are not going to use KDE.
+ use_kde_ = NO_KDE;
+
+ // Check to see if KDE is the desktop environment.
+ scoped_ptr<base::Environment> env(base::Environment::Create());
+ base::nix::DesktopEnvironment desktop =
+ base::nix::GetDesktopEnvironment(env.get());
+ if (desktop == base::nix::DESKTOP_ENVIRONMENT_KDE3 ||
+ desktop == base::nix::DESKTOP_ENVIRONMENT_KDE4) {
+ // Check to see if the user dislikes the KDE file dialog.
+ if (!env->HasVar("NO_CHROME_KDE_FILE_DIALOG")) {
+ // Check to see if the KDE dialog works.
+ if (SelectFileDialogImpl::CheckKDEDialogWorksOnUIThread()) {
+ use_kde_ = YES_KDE;
+ }
+ }
+ }
+ }
+
+ if (use_kde_ == NO_KDE)
+ return SelectFileDialogImpl::NewSelectFileDialogImplGTK(listener, policy);
+
+ scoped_ptr<base::Environment> env(base::Environment::Create());
+ base::nix::DesktopEnvironment desktop =
+ base::nix::GetDesktopEnvironment(env.get());
+ return SelectFileDialogImpl::NewSelectFileDialogImplKDE(
+ listener, policy, desktop);
+}
+
+SelectFileDialogImpl::SelectFileDialogImpl(Listener* listener,
+ ui::SelectFilePolicy* policy)
+ : SelectFileDialog(listener, policy),
+ file_type_index_(0),
+ type_(SELECT_NONE) {
+ if (!last_saved_path_) {
+ last_saved_path_ = new FilePath();
+ last_opened_path_ = new FilePath();
+ }
+}
+
+SelectFileDialogImpl::~SelectFileDialogImpl() { }
+
+void SelectFileDialogImpl::ListenerDestroyed() {
+ listener_ = NULL;
+}
+
+bool SelectFileDialogImpl::IsRunning(gfx::NativeWindow parent_window) const {
+ return parents_.find(parent_window) != parents_.end();
+}
+
+bool SelectFileDialogImpl::CallDirectoryExistsOnUIThread(const FilePath& path) {
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ return file_util::DirectoryExists(path);
+}
+
+} // namespace ui
diff --git a/ui/shell_dialogs/gtk/select_file_dialog_impl.h b/ui/shell_dialogs/gtk/select_file_dialog_impl.h
new file mode 100644
index 0000000..24a5061
--- /dev/null
+++ b/ui/shell_dialogs/gtk/select_file_dialog_impl.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file implements common select dialog functionality between GTK and KDE.
+
+#ifndef UI_BASE_DIALOGS_GTK_SELECT_FILE_DIALOG_IMPL_H_
+#define UI_BASE_DIALOGS_GTK_SELECT_FILE_DIALOG_IMPL_H_
+
+#include <set>
+
+#include "base/compiler_specific.h"
+#include "base/nix/xdg_util.h"
+#include "ui/base/dialogs/select_file_dialog.h"
+
+namespace ui {
+
+// Shared implementation SelectFileDialog used by SelectFileDialogImplGTK
+class SelectFileDialogImpl : public SelectFileDialog {
+ public:
+ // Factory method for creating a GTK-styled SelectFileDialogImpl
+ static SelectFileDialogImpl* NewSelectFileDialogImplGTK(
+ Listener* listener,
+ ui::SelectFilePolicy* policy);
+ // Factory method for creating a KDE-styled SelectFileDialogImpl
+ static SelectFileDialogImpl* NewSelectFileDialogImplKDE(
+ Listener* listener,
+ ui::SelectFilePolicy* policy,
+ base::nix::DesktopEnvironment desktop);
+
+ // Returns true if the SelectFileDialog class returned by
+ // NewSelectFileDialogImplKDE will actually work.
+ static bool CheckKDEDialogWorksOnUIThread();
+
+ // BaseShellDialog implementation.
+ virtual bool IsRunning(gfx::NativeWindow parent_window) const OVERRIDE;
+ virtual void ListenerDestroyed() OVERRIDE;
+
+ protected:
+ explicit SelectFileDialogImpl(Listener* listener,
+ ui::SelectFilePolicy* policy);
+ virtual ~SelectFileDialogImpl();
+
+ // SelectFileDialog implementation.
+ // |params| is user data we pass back via the Listener interface.
+ virtual void SelectFileImpl(Type type,
+ const string16& title,
+ const FilePath& default_path,
+ const FileTypeInfo* file_types,
+ int file_type_index,
+ const FilePath::StringType& default_extension,
+ gfx::NativeWindow owning_window,
+ void* params) = 0;
+
+ // Wrapper for file_util::DirectoryExists() that allow access on the UI
+ // thread. Use this only in the file dialog functions, where it's ok
+ // because the file dialog has to do many stats anyway. One more won't
+ // hurt too badly and it's likely already cached.
+ bool CallDirectoryExistsOnUIThread(const FilePath& path);
+
+ // The file filters.
+ FileTypeInfo file_types_;
+
+ // The index of the default selected file filter.
+ // Note: This starts from 1, not 0.
+ size_t file_type_index_;
+
+ // The set of all parent windows for which we are currently running dialogs.
+ std::set<GtkWindow*> parents_;
+
+ // The type of dialog we are showing the user.
+ Type type_;
+
+ // These two variables track where the user last saved a file or opened a
+ // file so that we can display future dialogs with the same starting path.
+ static FilePath* last_saved_path_;
+ static FilePath* last_opened_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImpl);
+};
+
+SelectFileDialog* CreateLinuxSelectFileDialog(
+ SelectFileDialog::Listener* listener,
+ SelectFilePolicy* policy);
+
+} // namespace ui
+
+#endif // UI_BASE_DIALOGS_GTK_SELECT_FILE_DIALOG_IMPL_H_
diff --git a/ui/shell_dialogs/gtk/select_file_dialog_impl_gtk.cc b/ui/shell_dialogs/gtk/select_file_dialog_impl_gtk.cc
new file mode 100644
index 0000000..a61be4d
--- /dev/null
+++ b/ui/shell_dialogs/gtk/select_file_dialog_impl_gtk.cc
@@ -0,0 +1,580 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <gtk/gtk.h>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "base/sys_string_conversions.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/utf_string_conversions.h"
+#include "grit/ui_strings.h"
+#include "ui/base/dialogs/gtk/select_file_dialog_impl.h"
+#include "ui/base/dialogs/select_file_dialog.h"
+#include "ui/base/gtk/gtk_signal.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+// Makes sure that .jpg also shows .JPG.
+gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info,
+ std::string* file_extension) {
+ return EndsWith(file_info->filename, *file_extension, false);
+}
+
+// Deletes |data| when gtk_file_filter_add_custom() is done with it.
+void OnFileFilterDataDestroyed(std::string* file_extension) {
+ delete file_extension;
+}
+
+// Implementation of SelectFileDialog that shows a Gtk common dialog for
+// choosing a file or folder. This acts as a modal dialog.
+class SelectFileDialogImplGTK : public ui::SelectFileDialogImpl {
+ public:
+ explicit SelectFileDialogImplGTK(Listener* listener,
+ ui::SelectFilePolicy* policy);
+
+ protected:
+ virtual ~SelectFileDialogImplGTK();
+
+ // SelectFileDialog implementation.
+ // |params| is user data we pass back via the Listener interface.
+ virtual void SelectFileImpl(Type type,
+ const string16& title,
+ const FilePath& default_path,
+ const FileTypeInfo* file_types,
+ int file_type_index,
+ const FilePath::StringType& default_extension,
+ gfx::NativeWindow owning_window,
+ void* params) OVERRIDE;
+
+ private:
+ virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE;
+
+ // Add the filters from |file_types_| to |chooser|.
+ void AddFilters(GtkFileChooser* chooser);
+
+ // Notifies the listener that a single file was chosen.
+ void FileSelected(GtkWidget* dialog, const FilePath& path);
+
+ // Notifies the listener that multiple files were chosen.
+ void MultiFilesSelected(GtkWidget* dialog,
+ const std::vector<FilePath>& files);
+
+ // Notifies the listener that no file was chosen (the action was canceled).
+ // Dialog is passed so we can find that |params| pointer that was passed to
+ // us when we were told to show the dialog.
+ void FileNotSelected(GtkWidget* dialog);
+
+ GtkWidget* CreateSelectFolderDialog(const std::string& title,
+ const FilePath& default_path, gfx::NativeWindow parent);
+
+ GtkWidget* CreateFileOpenDialog(const std::string& title,
+ const FilePath& default_path, gfx::NativeWindow parent);
+
+ GtkWidget* CreateMultiFileOpenDialog(const std::string& title,
+ const FilePath& default_path, gfx::NativeWindow parent);
+
+ GtkWidget* CreateSaveAsDialog(const std::string& title,
+ const FilePath& default_path, gfx::NativeWindow parent);
+
+ // Removes and returns the |params| associated with |dialog| from
+ // |params_map_|.
+ void* PopParamsForDialog(GtkWidget* dialog);
+
+ // Take care of internal data structures when a file dialog is destroyed.
+ void FileDialogDestroyed(GtkWidget* dialog);
+
+ // Check whether response_id corresponds to the user cancelling/closing the
+ // dialog. Used as a helper for the below callbacks.
+ bool IsCancelResponse(gint response_id);
+
+ // Common function for OnSelectSingleFileDialogResponse and
+ // OnSelectSingleFolderDialogResponse.
+ void SelectSingleFileHelper(GtkWidget* dialog,
+ gint response_id,
+ bool allow_folder);
+
+ // Common function for CreateFileOpenDialog and CreateMultiFileOpenDialog.
+ GtkWidget* CreateFileOpenHelper(const std::string& title,
+ const FilePath& default_path,
+ gfx::NativeWindow parent);
+
+ // Callback for when the user responds to a Save As or Open File dialog.
+ CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
+ OnSelectSingleFileDialogResponse, int);
+
+ // Callback for when the user responds to a Select Folder dialog.
+ CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
+ OnSelectSingleFolderDialogResponse, int);
+
+ // Callback for when the user responds to a Open Multiple Files dialog.
+ CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
+ OnSelectMultiFileDialogResponse, int);
+
+ // Callback for when the file chooser gets destroyed.
+ CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnFileChooserDestroy);
+
+ // Callback for when we update the preview for the selection.
+ CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnUpdatePreview);
+
+ // A map from dialog windows to the |params| user data associated with them.
+ std::map<GtkWidget*, void*> params_map_;
+
+ // The GtkImage widget for showing previews of selected images.
+ GtkWidget* preview_;
+
+ // All our dialogs.
+ std::set<GtkWidget*> dialogs_;
+
+ DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplGTK);
+};
+
+// The size of the preview we display for selected image files. We set height
+// larger than width because generally there is more free space vertically
+// than horiztonally (setting the preview image will alway expand the width of
+// the dialog, but usually not the height). The image's aspect ratio will always
+// be preserved.
+static const int kPreviewWidth = 256;
+static const int kPreviewHeight = 512;
+
+SelectFileDialogImplGTK::SelectFileDialogImplGTK(Listener* listener,
+ ui::SelectFilePolicy* policy)
+ : SelectFileDialogImpl(listener, policy),
+ preview_(NULL) {
+}
+
+SelectFileDialogImplGTK::~SelectFileDialogImplGTK() {
+ while (dialogs_.begin() != dialogs_.end()) {
+ gtk_widget_destroy(*(dialogs_.begin()));
+ }
+}
+
+bool SelectFileDialogImplGTK::HasMultipleFileTypeChoicesImpl() {
+ return file_types_.extensions.size() > 1;
+}
+
+// We ignore |default_extension|.
+void SelectFileDialogImplGTK::SelectFileImpl(
+ Type type,
+ const string16& title,
+ const FilePath& default_path,
+ const FileTypeInfo* file_types,
+ int file_type_index,
+ const FilePath::StringType& default_extension,
+ gfx::NativeWindow owning_window,
+ void* params) {
+ type_ = type;
+ // |owning_window| can be null when user right-clicks on a downloadable item
+ // and chooses 'Open Link in New Tab' when 'Ask where to save each file
+ // before downloading.' preference is turned on. (http://crbug.com/29213)
+ if (owning_window)
+ parents_.insert(owning_window);
+
+ std::string title_string = UTF16ToUTF8(title);
+
+ file_type_index_ = file_type_index;
+ if (file_types)
+ file_types_ = *file_types;
+ else
+ file_types_.include_all_files = true;
+
+ GtkWidget* dialog = NULL;
+ switch (type) {
+ case SELECT_FOLDER:
+ dialog = CreateSelectFolderDialog(title_string, default_path,
+ owning_window);
+ break;
+ case SELECT_OPEN_FILE:
+ dialog = CreateFileOpenDialog(title_string, default_path, owning_window);
+ break;
+ case SELECT_OPEN_MULTI_FILE:
+ dialog = CreateMultiFileOpenDialog(title_string, default_path,
+ owning_window);
+ break;
+ case SELECT_SAVEAS_FILE:
+ dialog = CreateSaveAsDialog(title_string, default_path, owning_window);
+ break;
+ default:
+ NOTREACHED();
+ return;
+ }
+ g_signal_connect(dialog, "delete-event",
+ G_CALLBACK(gtk_widget_hide_on_delete), NULL);
+ dialogs_.insert(dialog);
+
+ preview_ = gtk_image_new();
+ g_signal_connect(dialog, "destroy",
+ G_CALLBACK(OnFileChooserDestroyThunk), this);
+ g_signal_connect(dialog, "update-preview",
+ G_CALLBACK(OnUpdatePreviewThunk), this);
+ gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview_);
+
+ params_map_[dialog] = params;
+
+ // Set window-to-parent modality by adding the dialog to the same window
+ // group as the parent.
+ gtk_window_group_add_window(gtk_window_get_group(owning_window),
+ GTK_WINDOW(dialog));
+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+
+ gtk_widget_show_all(dialog);
+}
+
+void SelectFileDialogImplGTK::AddFilters(GtkFileChooser* chooser) {
+ for (size_t i = 0; i < file_types_.extensions.size(); ++i) {
+ GtkFileFilter* filter = NULL;
+ std::set<std::string> fallback_labels;
+
+ for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) {
+ const std::string& current_extension = file_types_.extensions[i][j];
+ if (!current_extension.empty()) {
+ if (!filter)
+ filter = gtk_file_filter_new();
+ scoped_ptr<std::string> file_extension(
+ new std::string("." + current_extension));
+ fallback_labels.insert(std::string("*").append(*file_extension));
+ gtk_file_filter_add_custom(
+ filter,
+ GTK_FILE_FILTER_FILENAME,
+ reinterpret_cast<GtkFileFilterFunc>(FileFilterCaseInsensitive),
+ file_extension.release(),
+ reinterpret_cast<GDestroyNotify>(OnFileFilterDataDestroyed));
+ }
+ }
+ // We didn't find any non-empty extensions to filter on.
+ if (!filter)
+ continue;
+
+ // The description vector may be blank, in which case we are supposed to
+ // use some sort of default description based on the filter.
+ if (i < file_types_.extension_description_overrides.size()) {
+ gtk_file_filter_set_name(filter, UTF16ToUTF8(
+ file_types_.extension_description_overrides[i]).c_str());
+ } else {
+ // There is no system default filter description so we use
+ // the extensions themselves if the description is blank.
+ std::vector<std::string> fallback_labels_vector(fallback_labels.begin(),
+ fallback_labels.end());
+ std::string fallback_label = JoinString(fallback_labels_vector, ',');
+ gtk_file_filter_set_name(filter, fallback_label.c_str());
+ }
+
+ gtk_file_chooser_add_filter(chooser, filter);
+ if (i == file_type_index_ - 1)
+ gtk_file_chooser_set_filter(chooser, filter);
+ }
+
+ // Add the *.* filter, but only if we have added other filters (otherwise it
+ // is implied).
+ if (file_types_.include_all_files && !file_types_.extensions.empty()) {
+ GtkFileFilter* filter = gtk_file_filter_new();
+ gtk_file_filter_add_pattern(filter, "*");
+ gtk_file_filter_set_name(filter,
+ l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES).c_str());
+ gtk_file_chooser_add_filter(chooser, filter);
+ }
+}
+
+void SelectFileDialogImplGTK::FileSelected(GtkWidget* dialog,
+ const FilePath& path) {
+ if (type_ == SELECT_SAVEAS_FILE)
+ *last_saved_path_ = path.DirName();
+ else if (type_ == SELECT_OPEN_FILE || type_ == SELECT_FOLDER)
+ *last_opened_path_ = path.DirName();
+ else
+ NOTREACHED();
+
+ if (listener_) {
+ GtkFileFilter* selected_filter =
+ gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
+ GSList* filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog));
+ int idx = g_slist_index(filters, selected_filter);
+ g_slist_free(filters);
+ listener_->FileSelected(path, idx + 1, PopParamsForDialog(dialog));
+ }
+ gtk_widget_destroy(dialog);
+}
+
+void SelectFileDialogImplGTK::MultiFilesSelected(GtkWidget* dialog,
+ const std::vector<FilePath>& files) {
+ *last_opened_path_ = files[0].DirName();
+
+ if (listener_)
+ listener_->MultiFilesSelected(files, PopParamsForDialog(dialog));
+ gtk_widget_destroy(dialog);
+}
+
+void SelectFileDialogImplGTK::FileNotSelected(GtkWidget* dialog) {
+ void* params = PopParamsForDialog(dialog);
+ if (listener_)
+ listener_->FileSelectionCanceled(params);
+ gtk_widget_destroy(dialog);
+}
+
+GtkWidget* SelectFileDialogImplGTK::CreateFileOpenHelper(
+ const std::string& title,
+ const FilePath& default_path,
+ gfx::NativeWindow parent) {
+ GtkWidget* dialog =
+ gtk_file_chooser_dialog_new(title.c_str(), parent,
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+ NULL);
+ AddFilters(GTK_FILE_CHOOSER(dialog));
+
+ if (!default_path.empty()) {
+ if (CallDirectoryExistsOnUIThread(default_path)) {
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
+ default_path.value().c_str());
+ } else {
+ // If the file doesn't exist, this will just switch to the correct
+ // directory. That's good enough.
+ gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
+ default_path.value().c_str());
+ }
+ } else if (!last_opened_path_->empty()) {
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
+ last_opened_path_->value().c_str());
+ }
+ return dialog;
+}
+
+GtkWidget* SelectFileDialogImplGTK::CreateSelectFolderDialog(
+ const std::string& title,
+ const FilePath& default_path,
+ gfx::NativeWindow parent) {
+ std::string title_string = !title.empty() ? title :
+ l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE);
+
+ GtkWidget* dialog =
+ gtk_file_chooser_dialog_new(title_string.c_str(), parent,
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ if (!default_path.empty()) {
+ gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
+ default_path.value().c_str());
+ } else if (!last_opened_path_->empty()) {
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
+ last_opened_path_->value().c_str());
+ }
+ gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
+ g_signal_connect(dialog, "response",
+ G_CALLBACK(OnSelectSingleFolderDialogResponseThunk), this);
+ return dialog;
+}
+
+GtkWidget* SelectFileDialogImplGTK::CreateFileOpenDialog(
+ const std::string& title,
+ const FilePath& default_path,
+ gfx::NativeWindow parent) {
+ std::string title_string = !title.empty() ? title :
+ l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE);
+
+ GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
+ gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
+ g_signal_connect(dialog, "response",
+ G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
+ return dialog;
+}
+
+GtkWidget* SelectFileDialogImplGTK::CreateMultiFileOpenDialog(
+ const std::string& title,
+ const FilePath& default_path,
+ gfx::NativeWindow parent) {
+ std::string title_string = !title.empty() ? title :
+ l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE);
+
+ GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
+ gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
+ g_signal_connect(dialog, "response",
+ G_CALLBACK(OnSelectMultiFileDialogResponseThunk), this);
+ return dialog;
+}
+
+GtkWidget* SelectFileDialogImplGTK::CreateSaveAsDialog(const std::string& title,
+ const FilePath& default_path, gfx::NativeWindow parent) {
+ std::string title_string = !title.empty() ? title :
+ l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE);
+
+ GtkWidget* dialog =
+ gtk_file_chooser_dialog_new(title_string.c_str(), parent,
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ AddFilters(GTK_FILE_CHOOSER(dialog));
+ if (!default_path.empty()) {
+ // Since the file may not already exist, we use
+ // set_current_folder() followed by set_current_name(), as per the
+ // recommendation of the GTK docs.
+ if (CallDirectoryExistsOnUIThread(default_path)) {
+ gtk_file_chooser_set_current_folder(
+ GTK_FILE_CHOOSER(dialog), default_path.value().c_str());
+ gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "");
+ } else {
+ gtk_file_chooser_set_current_folder(
+ GTK_FILE_CHOOSER(dialog), default_path.DirName().value().c_str());
+ gtk_file_chooser_set_current_name(
+ GTK_FILE_CHOOSER(dialog), default_path.BaseName().value().c_str());
+ }
+ } else if (!last_saved_path_->empty()) {
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
+ last_saved_path_->value().c_str());
+ }
+ gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
+ gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),
+ TRUE);
+ g_signal_connect(dialog, "response",
+ G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
+ return dialog;
+}
+
+void* SelectFileDialogImplGTK::PopParamsForDialog(GtkWidget* dialog) {
+ std::map<GtkWidget*, void*>::iterator iter = params_map_.find(dialog);
+ DCHECK(iter != params_map_.end());
+ void* params = iter->second;
+ params_map_.erase(iter);
+ return params;
+}
+
+void SelectFileDialogImplGTK::FileDialogDestroyed(GtkWidget* dialog) {
+ dialogs_.erase(dialog);
+
+ // Parent may be NULL in a few cases: 1) on shutdown when
+ // AllBrowsersClosed() trigger this handler after all the browser
+ // windows got destroyed, or 2) when the parent tab has been opened by
+ // 'Open Link in New Tab' context menu on a downloadable item and
+ // the tab has no content (see the comment in SelectFile as well).
+ GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(dialog));
+ if (!parent)
+ return;
+ std::set<GtkWindow*>::iterator iter = parents_.find(parent);
+ if (iter != parents_.end())
+ parents_.erase(iter);
+ else
+ NOTREACHED();
+}
+
+bool SelectFileDialogImplGTK::IsCancelResponse(gint response_id) {
+ bool is_cancel = response_id == GTK_RESPONSE_CANCEL ||
+ response_id == GTK_RESPONSE_DELETE_EVENT;
+ if (is_cancel)
+ return true;
+
+ DCHECK(response_id == GTK_RESPONSE_ACCEPT);
+ return false;
+}
+
+void SelectFileDialogImplGTK::SelectSingleFileHelper(GtkWidget* dialog,
+ gint response_id,
+ bool allow_folder) {
+ if (IsCancelResponse(response_id)) {
+ FileNotSelected(dialog);
+ return;
+ }
+
+ gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+ if (!filename) {
+ FileNotSelected(dialog);
+ return;
+ }
+
+ FilePath path(filename);
+ g_free(filename);
+
+ if (allow_folder) {
+ FileSelected(dialog, path);
+ return;
+ }
+
+ if (CallDirectoryExistsOnUIThread(path))
+ FileNotSelected(dialog);
+ else
+ FileSelected(dialog, path);
+}
+
+void SelectFileDialogImplGTK::OnSelectSingleFileDialogResponse(
+ GtkWidget* dialog, int response_id) {
+ SelectSingleFileHelper(dialog, response_id, false);
+}
+
+void SelectFileDialogImplGTK::OnSelectSingleFolderDialogResponse(
+ GtkWidget* dialog, int response_id) {
+ SelectSingleFileHelper(dialog, response_id, true);
+}
+
+void SelectFileDialogImplGTK::OnSelectMultiFileDialogResponse(GtkWidget* dialog,
+ int response_id) {
+ if (IsCancelResponse(response_id)) {
+ FileNotSelected(dialog);
+ return;
+ }
+
+ GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
+ if (!filenames) {
+ FileNotSelected(dialog);
+ return;
+ }
+
+ std::vector<FilePath> filenames_fp;
+ for (GSList* iter = filenames; iter != NULL; iter = g_slist_next(iter)) {
+ FilePath path(static_cast<char*>(iter->data));
+ g_free(iter->data);
+ if (CallDirectoryExistsOnUIThread(path))
+ continue;
+ filenames_fp.push_back(path);
+ }
+ g_slist_free(filenames);
+
+ if (filenames_fp.empty()) {
+ FileNotSelected(dialog);
+ return;
+ }
+ MultiFilesSelected(dialog, filenames_fp);
+}
+
+void SelectFileDialogImplGTK::OnFileChooserDestroy(GtkWidget* dialog) {
+ FileDialogDestroyed(dialog);
+}
+
+void SelectFileDialogImplGTK::OnUpdatePreview(GtkWidget* chooser) {
+ gchar* filename = gtk_file_chooser_get_preview_filename(
+ GTK_FILE_CHOOSER(chooser));
+ if (!filename)
+ return;
+ // This will preserve the image's aspect ratio.
+ GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth,
+ kPreviewHeight, NULL);
+ g_free(filename);
+ if (pixbuf) {
+ gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf);
+ g_object_unref(pixbuf);
+ }
+ gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
+ pixbuf ? TRUE : FALSE);
+}
+
+} // namespace
+
+namespace ui {
+
+SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplGTK(
+ Listener* listener, ui::SelectFilePolicy* policy) {
+ return new SelectFileDialogImplGTK(listener, policy);
+}
+
+} // namespace ui
diff --git a/ui/shell_dialogs/gtk/select_file_dialog_impl_kde.cc b/ui/shell_dialogs/gtk/select_file_dialog_impl_kde.cc
new file mode 100644
index 0000000..6ba9b01
--- /dev/null
+++ b/ui/shell_dialogs/gtk/select_file_dialog_impl_kde.cc
@@ -0,0 +1,470 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <set>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/message_loop_proxy.h"
+#include "base/nix/mime_util_xdg.h"
+#include "base/nix/xdg_util.h"
+#include "base/process_util.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/threading/worker_pool.h"
+#include "base/utf_string_conversions.h"
+#include "grit/ui_strings.h"
+#include "ui/base/dialogs/gtk/select_file_dialog_impl.h"
+#include "ui/base/l10n/l10n_util.h"
+
+// These conflict with base/tracked_objects.h, so need to come last.
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+namespace {
+
+std::string GetTitle(const std::string& title, int message_id) {
+ return title.empty() ? l10n_util::GetStringUTF8(message_id) : title;
+}
+
+const char kKdialogBinary[] = "kdialog";
+
+// Implementation of SelectFileDialog that shows a KDE common dialog for
+// choosing a file or folder. This acts as a modal dialog.
+class SelectFileDialogImplKDE : public ui::SelectFileDialogImpl {
+ public:
+ SelectFileDialogImplKDE(Listener* listener,
+ ui::SelectFilePolicy* policy,
+ base::nix::DesktopEnvironment desktop);
+
+ protected:
+ virtual ~SelectFileDialogImplKDE();
+
+ // SelectFileDialog implementation.
+ // |params| is user data we pass back via the Listener interface.
+ virtual void SelectFileImpl(Type type,
+ const string16& title,
+ const FilePath& default_path,
+ const FileTypeInfo* file_types,
+ int file_type_index,
+ const FilePath::StringType& default_extension,
+ gfx::NativeWindow owning_window,
+ void* params) OVERRIDE;
+
+ private:
+ virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE;
+
+ struct KDialogParams {
+ // This constructor can only be run from the UI thread.
+ KDialogParams(const std::string& type, const std::string& title,
+ const FilePath& default_path, gfx::NativeWindow parent,
+ bool file_operation, bool multiple_selection,
+ void* kdialog_params,
+ void (SelectFileDialogImplKDE::*callback)(const std::string&,
+ int, void*))
+ : type(type), title(title), default_path(default_path), parent(parent),
+ file_operation(file_operation),
+ multiple_selection(multiple_selection),
+ kdialog_params(kdialog_params),
+ ui_loop_proxy(MessageLoopForUI::current()->message_loop_proxy()),
+ callback(callback) {
+ }
+
+ std::string type;
+ std::string title;
+ FilePath default_path;
+ gfx::NativeWindow parent;
+ bool file_operation;
+ bool multiple_selection;
+ void* kdialog_params;
+ scoped_refptr<base::MessageLoopProxy> ui_loop_proxy;
+
+ void (SelectFileDialogImplKDE::*callback)(const std::string&, int, void*);
+ };
+
+ // Get the filters from |file_types_| and concatenate them into
+ // |filter_string|.
+ std::string GetMimeTypeFilterString();
+
+ // Get KDialog command line representing the Argv array for KDialog.
+ void GetKDialogCommandLine(const std::string& type, const std::string& title,
+ const FilePath& default_path, gfx::NativeWindow parent,
+ bool file_operation, bool multiple_selection, CommandLine* command_line);
+
+ // Call KDialog on a worker thread and post results back to the caller
+ // thread.
+ void CallKDialogOutput(const KDialogParams& params);
+
+ // Notifies the listener that a single file was chosen.
+ void FileSelected(const FilePath& path, void* params);
+
+ // Notifies the listener that multiple files were chosen.
+ void MultiFilesSelected(const std::vector<FilePath>& files, void* params);
+
+ // Notifies the listener that no file was chosen (the action was canceled).
+ // Dialog is passed so we can find that |params| pointer that was passed to
+ // us when we were told to show the dialog.
+ void FileNotSelected(void *params);
+
+ void CreateSelectFolderDialog(const std::string& title,
+ const FilePath& default_path,
+ gfx::NativeWindow parent, void* params);
+
+ void CreateFileOpenDialog(const std::string& title,
+ const FilePath& default_path,
+ gfx::NativeWindow parent, void* params);
+
+ void CreateMultiFileOpenDialog(const std::string& title,
+ const FilePath& default_path,
+ gfx::NativeWindow parent, void* params);
+
+ void CreateSaveAsDialog(const std::string& title,
+ const FilePath& default_path,
+ gfx::NativeWindow parent, void* params);
+
+ // Common function for OnSelectSingleFileDialogResponse and
+ // OnSelectSingleFolderDialogResponse.
+ void SelectSingleFileHelper(const std::string& output, int exit_code,
+ void* params, bool allow_folder);
+
+ void OnSelectSingleFileDialogResponse(const std::string& output,
+ int exit_code, void* params);
+ void OnSelectMultiFileDialogResponse(const std::string& output,
+ int exit_code, void* params);
+ void OnSelectSingleFolderDialogResponse(const std::string& output,
+ int exit_code, void* params);
+
+ // Should be either DESKTOP_ENVIRONMENT_KDE3 or DESKTOP_ENVIRONMENT_KDE4.
+ base::nix::DesktopEnvironment desktop_;
+
+ DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplKDE);
+};
+
+SelectFileDialogImplKDE::SelectFileDialogImplKDE(
+ Listener* listener,
+ ui::SelectFilePolicy* policy,
+ base::nix::DesktopEnvironment desktop)
+ : SelectFileDialogImpl(listener, policy),
+ desktop_(desktop) {
+ DCHECK(desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE3 ||
+ desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE4);
+}
+
+SelectFileDialogImplKDE::~SelectFileDialogImplKDE() {
+}
+
+// We ignore |default_extension|.
+void SelectFileDialogImplKDE::SelectFileImpl(
+ Type type,
+ const string16& title,
+ const FilePath& default_path,
+ const FileTypeInfo* file_types,
+ int file_type_index,
+ const FilePath::StringType& default_extension,
+ gfx::NativeWindow owning_window,
+ void* params) {
+ type_ = type;
+ // |owning_window| can be null when user right-clicks on a downloadable item
+ // and chooses 'Open Link in New Tab' when 'Ask where to save each file
+ // before downloading.' preference is turned on. (http://crbug.com/29213)
+ if (owning_window)
+ parents_.insert(owning_window);
+
+ std::string title_string = UTF16ToUTF8(title);
+
+ file_type_index_ = file_type_index;
+ if (file_types)
+ file_types_ = *file_types;
+ else
+ file_types_.include_all_files = true;
+
+ switch (type) {
+ case SELECT_FOLDER:
+ CreateSelectFolderDialog(title_string, default_path,
+ owning_window, params);
+ return;
+ case SELECT_OPEN_FILE:
+ CreateFileOpenDialog(title_string, default_path, owning_window,
+ params);
+ return;
+ case SELECT_OPEN_MULTI_FILE:
+ CreateMultiFileOpenDialog(title_string, default_path,
+ owning_window, params);
+ return;
+ case SELECT_SAVEAS_FILE:
+ CreateSaveAsDialog(title_string, default_path, owning_window,
+ params);
+ return;
+ default:
+ NOTREACHED();
+ return;
+ }
+}
+
+bool SelectFileDialogImplKDE::HasMultipleFileTypeChoicesImpl() {
+ return file_types_.extensions.size() > 1;
+}
+
+std::string SelectFileDialogImplKDE::GetMimeTypeFilterString() {
+ std::string filter_string;
+ // We need a filter set because the same mime type can appear multiple times.
+ std::set<std::string> filter_set;
+ for (size_t i = 0; i < file_types_.extensions.size(); ++i) {
+ for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) {
+ if (!file_types_.extensions[i][j].empty()) {
+ std::string mime_type = base::nix::GetFileMimeType(
+ FilePath("name").ReplaceExtension(file_types_.extensions[i][j]));
+ filter_set.insert(mime_type);
+ }
+ }
+ }
+ // Add the *.* filter, but only if we have added other filters (otherwise it
+ // is implied).
+ if (file_types_.include_all_files && !file_types_.extensions.empty())
+ filter_set.insert("application/octet-stream");
+ // Create the final output string.
+ filter_string.clear();
+ for (std::set<std::string>::iterator it = filter_set.begin();
+ it != filter_set.end(); ++it) {
+ filter_string.append(*it + " ");
+ }
+ return filter_string;
+}
+
+void SelectFileDialogImplKDE::CallKDialogOutput(const KDialogParams& params) {
+ CommandLine::StringVector cmd_vector;
+ cmd_vector.push_back(kKdialogBinary);
+ CommandLine command_line(cmd_vector);
+ GetKDialogCommandLine(params.type, params.title, params.default_path,
+ params.parent, params.file_operation,
+ params.multiple_selection, &command_line);
+ std::string output;
+ int exit_code;
+ // Get output from KDialog
+ base::GetAppOutputWithExitCode(command_line, &output, &exit_code);
+ if (!output.empty())
+ output.erase(output.size() - 1);
+ // Now the dialog is no longer showing. We can erase its parent from the
+ // parent set.
+ std::set<GtkWindow*>::iterator iter = parents_.find(params.parent);
+ if (iter != parents_.end())
+ parents_.erase(iter);
+ params.ui_loop_proxy->PostTask(FROM_HERE,
+ base::Bind(params.callback, this, output, exit_code,
+ params.kdialog_params));
+}
+
+void SelectFileDialogImplKDE::GetKDialogCommandLine(const std::string& type,
+ const std::string& title, const FilePath& path,
+ gfx::NativeWindow parent, bool file_operation, bool multiple_selection,
+ CommandLine* command_line) {
+ CHECK(command_line);
+
+ // Attach to the current Chrome window.
+ GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET((parent)));
+ int window_id = GDK_DRAWABLE_XID(gdk_window);
+ command_line->AppendSwitchNative(
+ desktop_ == base::nix::DESKTOP_ENVIRONMENT_KDE3 ? "--embed" : "--attach",
+ base::IntToString(window_id));
+ // Set the correct title for the dialog.
+ if (!title.empty())
+ command_line->AppendSwitchNative("--title", title);
+ // Enable multiple file selection if we need to.
+ if (multiple_selection) {
+ command_line->AppendSwitch("--multiple");
+ command_line->AppendSwitch("--separate-output");
+ }
+ command_line->AppendSwitch(type);
+ // The path should never be empty. If it is, set it to PWD.
+ if (path.empty())
+ command_line->AppendArgPath(FilePath("."));
+ else
+ command_line->AppendArgPath(path);
+ // Depending on the type of the operation we need, get the path to the
+ // file/folder and set up mime type filters.
+ if (file_operation)
+ command_line->AppendArg(GetMimeTypeFilterString());
+ VLOG(1) << "KDialog command line: " << command_line->GetCommandLineString();
+}
+
+void SelectFileDialogImplKDE::FileSelected(const FilePath& path, void* params) {
+ if (type_ == SELECT_SAVEAS_FILE)
+ *last_saved_path_ = path.DirName();
+ else if (type_ == SELECT_OPEN_FILE)
+ *last_opened_path_ = path.DirName();
+ else if (type_ == SELECT_FOLDER)
+ *last_opened_path_ = path;
+ else
+ NOTREACHED();
+ if (listener_) { // What does the filter index actually do?
+ // TODO(dfilimon): Get a reasonable index value from somewhere.
+ listener_->FileSelected(path, 1, params);
+ }
+}
+
+void SelectFileDialogImplKDE::MultiFilesSelected(
+ const std::vector<FilePath>& files, void* params) {
+ *last_opened_path_ = files[0].DirName();
+ if (listener_)
+ listener_->MultiFilesSelected(files, params);
+}
+
+void SelectFileDialogImplKDE::FileNotSelected(void* params) {
+ if (listener_)
+ listener_->FileSelectionCanceled(params);
+}
+
+void SelectFileDialogImplKDE::CreateSelectFolderDialog(
+ const std::string& title, const FilePath& default_path,
+ gfx::NativeWindow parent, void *params) {
+ base::WorkerPool::PostTask(FROM_HERE,
+ base::Bind(
+ &SelectFileDialogImplKDE::CallKDialogOutput,
+ this,
+ KDialogParams(
+ "--getexistingdirectory",
+ GetTitle(title, IDS_SELECT_FOLDER_DIALOG_TITLE),
+ default_path.empty() ? *last_opened_path_ : default_path,
+ parent, false, false, params,
+ &SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse)),
+ true);
+}
+
+void SelectFileDialogImplKDE::CreateFileOpenDialog(
+ const std::string& title, const FilePath& default_path,
+ gfx::NativeWindow parent, void* params) {
+ base::WorkerPool::PostTask(FROM_HERE,
+ base::Bind(
+ &SelectFileDialogImplKDE::CallKDialogOutput,
+ this,
+ KDialogParams(
+ "--getopenfilename",
+ GetTitle(title, IDS_OPEN_FILE_DIALOG_TITLE),
+ default_path.empty() ? *last_opened_path_ : default_path,
+ parent, true, false, params,
+ &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse)),
+ true);
+}
+
+void SelectFileDialogImplKDE::CreateMultiFileOpenDialog(
+ const std::string& title, const FilePath& default_path,
+ gfx::NativeWindow parent, void* params) {
+ base::WorkerPool::PostTask(FROM_HERE,
+ base::Bind(
+ &SelectFileDialogImplKDE::CallKDialogOutput,
+ this,
+ KDialogParams(
+ "--getopenfilename",
+ GetTitle(title, IDS_OPEN_FILES_DIALOG_TITLE),
+ default_path.empty() ? *last_opened_path_ : default_path,
+ parent, true, true, params,
+ &SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse)),
+ true);
+}
+
+void SelectFileDialogImplKDE::CreateSaveAsDialog(
+ const std::string& title, const FilePath& default_path,
+ gfx::NativeWindow parent, void* params) {
+ base::WorkerPool::PostTask(FROM_HERE,
+ base::Bind(
+ &SelectFileDialogImplKDE::CallKDialogOutput,
+ this,
+ KDialogParams(
+ "--getsavefilename",
+ GetTitle(title, IDS_SAVE_AS_DIALOG_TITLE),
+ default_path.empty() ? *last_saved_path_ : default_path,
+ parent, true, false, params,
+ &SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse)),
+ true);
+}
+
+void SelectFileDialogImplKDE::SelectSingleFileHelper(const std::string& output,
+ int exit_code, void* params, bool allow_folder) {
+ VLOG(1) << "[kdialog] SingleFileResponse: " << output;
+ if (exit_code != 0 || output.empty()) {
+ FileNotSelected(params);
+ return;
+ }
+
+ FilePath path(output);
+ if (allow_folder) {
+ FileSelected(path, params);
+ return;
+ }
+
+ if (CallDirectoryExistsOnUIThread(path))
+ FileNotSelected(params);
+ else
+ FileSelected(path, params);
+}
+
+void SelectFileDialogImplKDE::OnSelectSingleFileDialogResponse(
+ const std::string& output, int exit_code, void* params) {
+ SelectSingleFileHelper(output, exit_code, params, false);
+}
+
+void SelectFileDialogImplKDE::OnSelectSingleFolderDialogResponse(
+ const std::string& output, int exit_code, void* params) {
+ SelectSingleFileHelper(output, exit_code, params, true);
+}
+
+void SelectFileDialogImplKDE::OnSelectMultiFileDialogResponse(
+ const std::string& output, int exit_code, void* params) {
+ VLOG(1) << "[kdialog] MultiFileResponse: " << output;
+
+ if (exit_code != 0 || output.empty()) {
+ FileNotSelected(params);
+ return;
+ }
+
+ std::vector<std::string> filenames;
+ Tokenize(output, "\n", &filenames);
+ std::vector<FilePath> filenames_fp;
+ for (std::vector<std::string>::iterator iter = filenames.begin();
+ iter != filenames.end(); ++iter) {
+ FilePath path(*iter);
+ if (CallDirectoryExistsOnUIThread(path))
+ continue;
+ filenames_fp.push_back(path);
+ }
+
+ if (filenames_fp.empty()) {
+ FileNotSelected(params);
+ return;
+ }
+ MultiFilesSelected(filenames_fp, params);
+}
+
+} // namespace
+
+namespace ui {
+
+// static
+bool SelectFileDialogImpl::CheckKDEDialogWorksOnUIThread() {
+ // No choice. UI thread can't continue without an answer here. Fortunately we
+ // only do this once, the first time a file dialog is displayed.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+
+ CommandLine::StringVector cmd_vector;
+ cmd_vector.push_back(kKdialogBinary);
+ cmd_vector.push_back("--version");
+ CommandLine command_line(cmd_vector);
+ std::string dummy;
+ return base::GetAppOutput(command_line, &dummy);
+}
+
+// static
+SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplKDE(
+ Listener* listener,
+ ui::SelectFilePolicy* policy,
+ base::nix::DesktopEnvironment desktop) {
+ return new SelectFileDialogImplKDE(listener, policy, desktop);
+}
+
+} // namespace ui
+
diff --git a/ui/shell_dialogs/select_file_dialog.cc b/ui/shell_dialogs/select_file_dialog.cc
new file mode 100644
index 0000000..ac88cc4
--- /dev/null
+++ b/ui/shell_dialogs/select_file_dialog.cc
@@ -0,0 +1,141 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/dialogs/select_file_dialog.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "build/build_config.h"
+#include "ui/base/dialogs/selected_file_info.h"
+#include "ui/base/dialogs/select_file_dialog_factory.h"
+#include "ui/base/dialogs/select_file_policy.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/linux_ui.h"
+
+#if defined(OS_WIN)
+#include "ui/base/dialogs/select_file_dialog_win.h"
+#elif defined(OS_MACOSX)
+#include "ui/base/dialogs/select_file_dialog_mac.h"
+#elif defined(TOOLKIT_GTK)
+#include "ui/base/dialogs/gtk/select_file_dialog_impl.h"
+#elif defined(OS_ANDROID)
+#include "ui/base/dialogs/select_file_dialog_android.h"
+#endif
+
+namespace {
+
+// Optional dialog factory. Leaked.
+ui::SelectFileDialogFactory* dialog_factory_ = NULL;
+
+} // namespace
+
+namespace ui {
+
+SelectFileDialog::FileTypeInfo::FileTypeInfo()
+ : include_all_files(false),
+ support_gdata(false) {}
+
+SelectFileDialog::FileTypeInfo::~FileTypeInfo() {}
+
+void SelectFileDialog::Listener::FileSelectedWithExtraInfo(
+ const ui::SelectedFileInfo& file,
+ int index,
+ void* params) {
+ // Most of the dialogs need actual local path, so default to it.
+ FileSelected(file.local_path, index, params);
+}
+
+void SelectFileDialog::Listener::MultiFilesSelectedWithExtraInfo(
+ const std::vector<ui::SelectedFileInfo>& files,
+ void* params) {
+ std::vector<FilePath> file_paths;
+ for (size_t i = 0; i < files.size(); ++i)
+ file_paths.push_back(files[i].local_path);
+
+ MultiFilesSelected(file_paths, params);
+}
+
+// static
+void SelectFileDialog::SetFactory(ui::SelectFileDialogFactory* factory) {
+ delete dialog_factory_;
+ dialog_factory_ = factory;
+}
+
+// static
+SelectFileDialog* SelectFileDialog::Create(Listener* listener,
+ ui::SelectFilePolicy* policy) {
+ if (dialog_factory_) {
+ SelectFileDialog* dialog = dialog_factory_->Create(listener, policy);
+ if (dialog)
+ return dialog;
+ }
+
+#if defined(USE_AURA) && !defined(USE_ASH) && defined(OS_LINUX)
+ const ui::LinuxUI* linux_ui = ui::LinuxUI::instance();
+ if (linux_ui)
+ return linux_ui->CreateSelectFileDialog(listener, policy);
+#endif
+
+#if defined(OS_WIN) && !defined(USE_AURA)
+ // TODO(port): The windows people need this to work in aura, too.
+ return CreateWinSelectFileDialog(listener, policy);
+#elif defined(OS_MACOSX) && !defined(USE_AURA)
+ return CreateMacSelectFileDialog(listener, policy);
+#elif defined(TOOLKIT_GTK)
+ return CreateLinuxSelectFileDialog(listener, policy);
+#elif defined(OS_ANDROID)
+ return CreateAndroidSelectFileDialog(listener, policy);
+#endif
+
+ return NULL;
+}
+
+void SelectFileDialog::SelectFile(Type type,
+ const string16& title,
+ const FilePath& default_path,
+ const FileTypeInfo* file_types,
+ int file_type_index,
+ const FilePath::StringType& default_extension,
+ gfx::NativeWindow owning_window,
+ void* params) {
+ DCHECK(listener_);
+
+ if (select_file_policy_.get() &&
+ !select_file_policy_->CanOpenSelectFileDialog()) {
+ select_file_policy_->SelectFileDenied();
+
+ // Inform the listener that no file was selected.
+ // Post a task rather than calling FileSelectionCanceled directly to ensure
+ // that the listener is called asynchronously.
+ MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&SelectFileDialog::CancelFileSelection, this,
+ params));
+ return;
+ }
+
+ // Call the platform specific implementation of the file selection dialog.
+ SelectFileImpl(type, title, default_path, file_types, file_type_index,
+ default_extension, owning_window, params);
+}
+
+bool SelectFileDialog::HasMultipleFileTypeChoices() {
+ return HasMultipleFileTypeChoicesImpl();
+}
+
+SelectFileDialog::SelectFileDialog(Listener* listener,
+ ui::SelectFilePolicy* policy)
+ : listener_(listener),
+ select_file_policy_(policy) {
+ DCHECK(listener_);
+}
+
+SelectFileDialog::~SelectFileDialog() {}
+
+void SelectFileDialog::CancelFileSelection(void* params) {
+ if (listener_)
+ listener_->FileSelectionCanceled(params);
+}
+
+} // namespace ui
diff --git a/ui/shell_dialogs/select_file_dialog.h b/ui/shell_dialogs/select_file_dialog.h
new file mode 100644
index 0000000..b5922d9
--- /dev/null
+++ b/ui/shell_dialogs/select_file_dialog.h
@@ -0,0 +1,201 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_DIALOGS_SELECT_FILE_DIALOG_H_
+#define UI_BASE_DIALOGS_SELECT_FILE_DIALOG_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string16.h"
+#include "ui/base/dialogs/base_shell_dialog.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace ui {
+class SelectFileDialogFactory;
+class SelectFilePolicy;
+struct SelectedFileInfo;
+
+// Shows a dialog box for selecting a file or a folder.
+class UI_EXPORT SelectFileDialog
+ : public base::RefCountedThreadSafe<SelectFileDialog>,
+ public ui::BaseShellDialog {
+ public:
+ enum Type {
+ SELECT_NONE,
+ SELECT_FOLDER,
+ SELECT_SAVEAS_FILE,
+ SELECT_OPEN_FILE,
+ SELECT_OPEN_MULTI_FILE
+ };
+
+ // An interface implemented by a Listener object wishing to know about the
+ // the result of the Select File/Folder action. These callbacks must be
+ // re-entrant.
+ class UI_EXPORT Listener {
+ public:
+ // Notifies the Listener that a file/folder selection has been made. The
+ // file/folder path is in |path|. |params| is contextual passed to
+ // SelectFile. |index| specifies the index of the filter passed to the
+ // the initial call to SelectFile.
+ virtual void FileSelected(const FilePath& path,
+ int index, void* params) = 0;
+
+ // Similar to FileSelected() but takes SelectedFileInfo instead of
+ // FilePath. Used for passing extra information (ex. display name).
+ //
+ // If not overridden, calls FileSelected() with path from |file|.
+ virtual void FileSelectedWithExtraInfo(
+ const ui::SelectedFileInfo& file,
+ int index,
+ void* params);
+
+ // Notifies the Listener that many files have been selected. The
+ // files are in |files|. |params| is contextual passed to SelectFile.
+ virtual void MultiFilesSelected(
+ const std::vector<FilePath>& files, void* params) {}
+
+ // Similar to MultiFilesSelected() but takes SelectedFileInfo instead of
+ // FilePath. Used for passing extra information (ex. display name).
+ //
+ // If not overridden, calls MultiFilesSelected() with paths from |files|.
+ virtual void MultiFilesSelectedWithExtraInfo(
+ const std::vector<ui::SelectedFileInfo>& files,
+ void* params);
+
+ // Notifies the Listener that the file/folder selection was aborted (via
+ // the user canceling or closing the selection dialog box, for example).
+ // |params| is contextual passed to SelectFile.
+ virtual void FileSelectionCanceled(void* params) {}
+
+ protected:
+ virtual ~Listener() {}
+ };
+
+ // Sets the factory that creates SelectFileDialog objects, overriding default
+ // behaviour.
+ //
+ // This is optional and should only be used by components that have to live
+ // elsewhere in the tree due to layering violations. (For example, because of
+ // a dependency on chrome's extension system.)
+ static void SetFactory(ui::SelectFileDialogFactory* factory);
+
+ // Creates a dialog box helper. This object is ref-counted, but the returned
+ // object will have no reference (refcount is 0). |policy| is an optional
+ // class that can prevent showing a dialog.
+ static SelectFileDialog* Create(Listener* listener,
+ ui::SelectFilePolicy* policy);
+
+ // Holds information about allowed extensions on a file save dialog.
+ struct UI_EXPORT FileTypeInfo {
+ FileTypeInfo();
+ ~FileTypeInfo();
+
+ // A list of allowed extensions. For example, it might be
+ //
+ // { { "htm", "html" }, { "txt" } }
+ //
+ // Only pass more than one extension in the inner vector if the extensions
+ // are equivalent. Do NOT include leading periods.
+ std::vector<std::vector<FilePath::StringType> > extensions;
+
+ // Overrides the system descriptions of the specified extensions. Entries
+ // correspond to |extensions|; if left blank the system descriptions will
+ // be used.
+ std::vector<string16> extension_description_overrides;
+
+ // Specifies whether there will be a filter added for all files (i.e. *.*).
+ bool include_all_files;
+
+ // Specifies whether the caller can support files/folders that are on
+ // GDrive.
+ bool support_gdata;
+ };
+
+ // Selects a File.
+ // Before doing anything this function checks if FileBrowsing is forbidden
+ // by Policy. If so, it tries to show an InfoBar and behaves as though no File
+ // was selected (the user clicked `Cancel` immediately).
+ // Otherwise it will start displaying the dialog box. This will also
+ // block the calling window until the dialog box is complete. The listener
+ // associated with this object will be notified when the selection is
+ // complete.
+ // |type| is the type of file dialog to be shown, see Type enumeration above.
+ // |title| is the title to be displayed in the dialog. If this string is
+ // empty, the default title is used.
+ // |default_path| is the default path and suggested file name to be shown in
+ // the dialog. This only works for SELECT_SAVEAS_FILE and SELECT_OPEN_FILE.
+ // Can be an empty string to indicate the platform default.
+ // |file_types| holds the information about the file types allowed. Pass NULL
+ // to get no special behavior
+ // |file_type_index| is the 1-based index into the file type list in
+ // |file_types|. Specify 0 if you don't need to specify extension behavior.
+ // |default_extension| is the default extension to add to the file if the
+ // user doesn't type one. This should NOT include the '.'. On Windows, if
+ // you specify this you must also specify |file_types|.
+ // |owning_window| is the window the dialog is modal to, or NULL for a
+ // modeless dialog.
+ // |params| is data from the calling context which will be passed through to
+ // the listener. Can be NULL.
+ // NOTE: only one instance of any shell dialog can be shown per owning_window
+ // at a time (for obvious reasons).
+ void SelectFile(Type type,
+ const string16& title,
+ const FilePath& default_path,
+ const FileTypeInfo* file_types,
+ int file_type_index,
+ const FilePath::StringType& default_extension,
+ gfx::NativeWindow owning_window,
+ void* params);
+ bool HasMultipleFileTypeChoices();
+
+ protected:
+ friend class base::RefCountedThreadSafe<SelectFileDialog>;
+ explicit SelectFileDialog(Listener* listener,
+ ui::SelectFilePolicy* policy);
+ virtual ~SelectFileDialog();
+
+ // Displays the actual file-selection dialog.
+ // This is overridden in the platform-specific descendants of FileSelectDialog
+ // and gets called from SelectFile after testing the
+ // AllowFileSelectionDialogs-Policy.
+ virtual void SelectFileImpl(Type type,
+ const string16& title,
+ const FilePath& default_path,
+ const FileTypeInfo* file_types,
+ int file_type_index,
+ const FilePath::StringType& default_extension,
+ gfx::NativeWindow owning_window,
+ void* params) = 0;
+
+ // The listener to be notified of selection completion.
+ Listener* listener_;
+
+ private:
+ // Tests if the file selection dialog can be displayed by
+ // testing if the AllowFileSelectionDialogs-Policy is
+ // either unset or set to true.
+ bool CanOpenSelectFileDialog();
+
+ // Informs the |listener_| that the file selection dialog was canceled. Moved
+ // to a function for being able to post it to the message loop.
+ void CancelFileSelection(void* params);
+
+ // Returns true if the dialog has multiple file type choices.
+ virtual bool HasMultipleFileTypeChoicesImpl() = 0;
+
+ scoped_ptr<ui::SelectFilePolicy> select_file_policy_;
+
+ DISALLOW_COPY_AND_ASSIGN(SelectFileDialog);
+};
+
+} // namespace ui
+
+#endif // UI_BASE_DIALOGS_SELECT_FILE_DIALOG_H_
diff --git a/ui/shell_dialogs/select_file_dialog_android.cc b/ui/shell_dialogs/select_file_dialog_android.cc
new file mode 100644
index 0000000..993eb58
--- /dev/null
+++ b/ui/shell_dialogs/select_file_dialog_android.cc
@@ -0,0 +1,113 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "select_file_dialog_android.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/jni_array.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/logging.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "jni/SelectFileDialog_jni.h"
+#include "ui/gfx/android/window_android.h"
+
+namespace ui {
+
+// static
+SelectFileDialogImpl* SelectFileDialogImpl::Create(Listener* listener,
+ ui::SelectFilePolicy* policy) {
+ return new SelectFileDialogImpl(listener, policy);
+}
+
+void SelectFileDialogImpl::OnFileSelected(JNIEnv* env,
+ jobject java_object,
+ jstring filepath) {
+ if (listener_) {
+ std::string path = base::android::ConvertJavaStringToUTF8(env, filepath);
+ listener_->FileSelected(FilePath(path), 0, NULL);
+ }
+
+ is_running_ = false;
+}
+
+void SelectFileDialogImpl::OnFileNotSelected(
+ JNIEnv* env,
+ jobject java_object) {
+ if (listener_)
+ listener_->FileSelectionCanceled(NULL);
+
+ is_running_ = false;
+}
+
+bool SelectFileDialogImpl::IsRunning(gfx::NativeWindow) const {
+ return is_running_;
+}
+
+void SelectFileDialogImpl::ListenerDestroyed() {
+ listener_ = NULL;
+}
+
+void SelectFileDialogImpl::SelectFileImpl(
+ ui::SelectFileDialog::Type type,
+ const string16& title,
+ const FilePath& default_path,
+ const SelectFileDialog::FileTypeInfo* file_types,
+ int file_type_index,
+ const std::string& default_extension,
+ gfx::NativeWindow owning_window,
+ void* params) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+
+ std::vector<string16> accept_types =
+ *(reinterpret_cast<std::vector<string16>*>(params));
+
+ // The last string in params is expected to be the string with capture value.
+ ScopedJavaLocalRef<jstring> capture_value =
+ base::android::ConvertUTF16ToJavaString(env,
+ StringToLowerASCII(accept_types.back()));
+ base::android::CheckException(env);
+ accept_types.pop_back();
+
+ // The rest params elements are expected to be accept_types.
+ ScopedJavaLocalRef<jobjectArray> accept_types_java =
+ base::android::ToJavaArrayOfStrings(env, accept_types);
+
+ Java_SelectFileDialog_selectFile(env, java_object_.obj(),
+ accept_types_java.obj(),
+ capture_value.obj(),
+ owning_window->GetJavaObject().obj());
+ is_running_ = true;
+}
+
+bool SelectFileDialogImpl::RegisterSelectFileDialog(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+SelectFileDialogImpl::~SelectFileDialogImpl() {
+}
+
+SelectFileDialogImpl::SelectFileDialogImpl(Listener* listener,
+ ui::SelectFilePolicy* policy)
+ : ui::SelectFileDialog(listener, policy),
+ is_running_(false) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ java_object_.Reset(
+ Java_SelectFileDialog_create(env, reinterpret_cast<jint>(this)));
+}
+
+bool SelectFileDialogImpl::HasMultipleFileTypeChoicesImpl() {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+SelectFileDialog* CreateAndroidSelectFileDialog(
+ SelectFileDialog::Listener* listener,
+ SelectFilePolicy* policy) {
+ return SelectFileDialogImpl::Create(listener, policy);
+}
+
+} // namespace ui
diff --git a/ui/shell_dialogs/select_file_dialog_android.h b/ui/shell_dialogs/select_file_dialog_android.h
new file mode 100644
index 0000000..55efaa6
--- /dev/null
+++ b/ui/shell_dialogs/select_file_dialog_android.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_DIALOGS_ANDROID_SELECT_FILE_DIALOG_ANDROID_H_
+#define UI_BASE_DIALOGS_ANDROID_SELECT_FILE_DIALOG_ANDROID_H_
+
+#include <jni.h>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/file_path.h"
+#include "ui/base/dialogs/select_file_dialog.h"
+
+namespace ui {
+
+class SelectFileDialogImpl : public ui::SelectFileDialog {
+ public:
+ static SelectFileDialogImpl* Create(
+ Listener* listener,
+ ui::SelectFilePolicy* policy);
+
+ void OnFileSelected(JNIEnv* env, jobject java_object, jstring filepath);
+ void OnFileNotSelected(JNIEnv* env, jobject java_object);
+
+ // From SelectFileDialog
+ virtual bool IsRunning(gfx::NativeWindow) const OVERRIDE;
+ virtual void ListenerDestroyed() OVERRIDE;
+
+ // Called when it is time to display the file picker.
+ // params is expected to be a Vector<string16> with accept_types first and
+ // the capture value as the last element of the vector.
+ virtual void SelectFileImpl(
+ ui::SelectFileDialog::Type type,
+ const string16& title,
+ const FilePath& default_path,
+ const ui::SelectFileDialog::FileTypeInfo* file_types,
+ int file_type_index,
+ const std::string& default_extension,
+ gfx::NativeWindow owning_window,
+ void* params) OVERRIDE;
+
+ static bool RegisterSelectFileDialog(JNIEnv* env);
+
+ protected:
+ virtual ~SelectFileDialogImpl();
+
+ private:
+ SelectFileDialogImpl(Listener* listener, ui::SelectFilePolicy* policy);
+
+ virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE;
+
+ base::android::ScopedJavaGlobalRef<jobject> java_object_;
+
+ // Stores the state whether select_file_dialog is running.
+ bool is_running_;
+
+ DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImpl);
+};
+
+SelectFileDialog* CreateAndroidSelectFileDialog(
+ SelectFileDialog::Listener* listener,
+ SelectFilePolicy* policy);
+
+} // namespace ui
+
+#endif // UI_BASE_DIALOGS_ANDROID_SELECT_FILE_DIALOG_ANDROID_H_
diff --git a/ui/shell_dialogs/select_file_dialog_factory.cc b/ui/shell_dialogs/select_file_dialog_factory.cc
new file mode 100644
index 0000000..7967b34
--- /dev/null
+++ b/ui/shell_dialogs/select_file_dialog_factory.cc
@@ -0,0 +1,11 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/dialogs/select_file_dialog_factory.h"
+
+namespace ui {
+
+SelectFileDialogFactory::~SelectFileDialogFactory() {}
+
+} // namespace ui
diff --git a/ui/shell_dialogs/select_file_dialog_factory.h b/ui/shell_dialogs/select_file_dialog_factory.h
new file mode 100644
index 0000000..174ed98e
--- /dev/null
+++ b/ui/shell_dialogs/select_file_dialog_factory.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_DIALOGS_SELECT_FILE_DIALOG_FACTORY_H_
+#define UI_BASE_DIALOGS_SELECT_FILE_DIALOG_FACTORY_H_
+
+#include "ui/base/dialogs/select_file_dialog.h"
+#include "ui/base/ui_export.h"
+
+namespace ui {
+class SelectFilePolicy;
+
+// Some chrome components want to create their own SelectFileDialog objects
+// (for example, using an extension to provide the select file dialog needs to
+// live in chrome/ due to the extension dependency.)
+//
+// They can implement a factory which creates their SelectFileDialog.
+class UI_EXPORT SelectFileDialogFactory {
+ public:
+ virtual ~SelectFileDialogFactory();
+
+ virtual SelectFileDialog* Create(ui::SelectFileDialog::Listener* listener,
+ ui::SelectFilePolicy* policy) = 0;
+};
+
+} // namespace ui
+
+#endif // UI_BASE_DIALOGS_SELECT_FILE_DIALOG_FACTORY_H_
diff --git a/ui/shell_dialogs/select_file_dialog_mac.h b/ui/shell_dialogs/select_file_dialog_mac.h
new file mode 100644
index 0000000..e346f41
--- /dev/null
+++ b/ui/shell_dialogs/select_file_dialog_mac.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_DIALOGS_SELECT_FILE_DIALOG_MAC_H_
+#define UI_BASE_DIALOGS_SELECT_FILE_DIALOG_MAC_H_
+
+#include "ui/base/dialogs/select_file_dialog.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace ui {
+
+SelectFileDialog* CreateMacSelectFileDialog(
+ SelectFileDialog::Listener* listener,
+ SelectFilePolicy* policy);
+
+} // namespace ui
+
+#endif // UI_BASE_DIALOGS_SELECT_FILE_DIALOG_MAC_H_
diff --git a/ui/shell_dialogs/select_file_dialog_mac.mm b/ui/shell_dialogs/select_file_dialog_mac.mm
new file mode 100644
index 0000000..1f2efc8
--- /dev/null
+++ b/ui/shell_dialogs/select_file_dialog_mac.mm
@@ -0,0 +1,424 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/dialogs/select_file_dialog.h"
+
+#import <Cocoa/Cocoa.h>
+#include <CoreServices/CoreServices.h>
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/mac/bundle_locations.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#import "base/memory/scoped_nsobject.h"
+#include "base/sys_string_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "grit/ui_strings.h"
+#import "ui/base/cocoa/nib_loading.h"
+#include "ui/base/l10n/l10n_util_mac.h"
+
+namespace {
+
+const int kFileTypePopupTag = 1234;
+
+CFStringRef CreateUTIFromExtension(const FilePath::StringType& ext) {
+ base::mac::ScopedCFTypeRef<CFStringRef> ext_cf(
+ base::SysUTF8ToCFStringRef(ext));
+ return UTTypeCreatePreferredIdentifierForTag(
+ kUTTagClassFilenameExtension, ext_cf.get(), NULL);
+}
+
+} // namespace
+
+class SelectFileDialogImpl;
+
+// A bridge class to act as the modal delegate to the save/open sheet and send
+// the results to the C++ class.
+@interface SelectFileDialogBridge : NSObject<NSOpenSavePanelDelegate> {
+ @private
+ SelectFileDialogImpl* selectFileDialogImpl_; // WEAK; owns us
+}
+
+- (id)initWithSelectFileDialogImpl:(SelectFileDialogImpl*)s;
+- (void)endedPanel:(NSSavePanel*)panel
+ didCancel:(bool)did_cancel
+ type:(ui::SelectFileDialog::Type)type
+ parentWindow:(NSWindow*)parentWindow;
+
+// NSSavePanel delegate method
+- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename;
+
+@end
+
+// Implementation of SelectFileDialog that shows Cocoa dialogs for choosing a
+// file or folder.
+class SelectFileDialogImpl : public ui::SelectFileDialog {
+ public:
+ explicit SelectFileDialogImpl(Listener* listener,
+ ui::SelectFilePolicy* policy);
+
+ // BaseShellDialog implementation.
+ virtual bool IsRunning(gfx::NativeWindow parent_window) const;
+ virtual void ListenerDestroyed();
+
+ // Callback from ObjC bridge.
+ void FileWasSelected(NSSavePanel* dialog,
+ NSWindow* parent_window,
+ bool was_cancelled,
+ bool is_multi,
+ const std::vector<FilePath>& files,
+ int index);
+
+ bool ShouldEnableFilename(NSSavePanel* dialog, NSString* filename);
+
+ protected:
+ // SelectFileDialog implementation.
+ // |params| is user data we pass back via the Listener interface.
+ virtual void SelectFileImpl(Type type,
+ const string16& title,
+ const FilePath& default_path,
+ const FileTypeInfo* file_types,
+ int file_type_index,
+ const FilePath::StringType& default_extension,
+ gfx::NativeWindow owning_window,
+ void* params) OVERRIDE;
+
+ private:
+ virtual ~SelectFileDialogImpl();
+
+ // Gets the accessory view for the save dialog.
+ NSView* GetAccessoryView(const FileTypeInfo* file_types,
+ int file_type_index);
+
+ virtual bool HasMultipleFileTypeChoicesImpl();
+
+ // The bridge for results from Cocoa to return to us.
+ scoped_nsobject<SelectFileDialogBridge> bridge_;
+
+ // A map from file dialogs to the |params| user data associated with them.
+ std::map<NSSavePanel*, void*> params_map_;
+
+ // The set of all parent windows for which we are currently running dialogs.
+ std::set<NSWindow*> parents_;
+
+ // A map from file dialogs to their types.
+ std::map<NSSavePanel*, Type> type_map_;
+
+ bool hasMultipleFileTypeChoices_;
+
+ DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImpl);
+};
+
+SelectFileDialogImpl::SelectFileDialogImpl(Listener* listener,
+ ui::SelectFilePolicy* policy)
+ : SelectFileDialog(listener, policy),
+ bridge_([[SelectFileDialogBridge alloc]
+ initWithSelectFileDialogImpl:this]) {
+}
+
+bool SelectFileDialogImpl::IsRunning(gfx::NativeWindow parent_window) const {
+ return parents_.find(parent_window) != parents_.end();
+}
+
+void SelectFileDialogImpl::ListenerDestroyed() {
+ listener_ = NULL;
+}
+
+void SelectFileDialogImpl::FileWasSelected(NSSavePanel* dialog,
+ NSWindow* parent_window,
+ bool was_cancelled,
+ bool is_multi,
+ const std::vector<FilePath>& files,
+ int index) {
+ void* params = params_map_[dialog];
+ params_map_.erase(dialog);
+ parents_.erase(parent_window);
+ type_map_.erase(dialog);
+
+ [dialog setDelegate:nil];
+
+ if (!listener_)
+ return;
+
+ if (was_cancelled || files.empty()) {
+ listener_->FileSelectionCanceled(params);
+ } else {
+ if (is_multi) {
+ listener_->MultiFilesSelected(files, params);
+ } else {
+ listener_->FileSelected(files[0], index, params);
+ }
+ }
+}
+
+bool SelectFileDialogImpl::ShouldEnableFilename(NSSavePanel* dialog,
+ NSString* filename) {
+ // If this is a single open file dialog, disable selecting packages.
+ if (type_map_[dialog] != SELECT_OPEN_FILE)
+ return true;
+
+ return ![[NSWorkspace sharedWorkspace] isFilePackageAtPath:filename];
+}
+
+void SelectFileDialogImpl::SelectFileImpl(
+ Type type,
+ const string16& title,
+ const FilePath& default_path,
+ const FileTypeInfo* file_types,
+ int file_type_index,
+ const FilePath::StringType& default_extension,
+ gfx::NativeWindow owning_window,
+ void* params) {
+ DCHECK(type == SELECT_FOLDER ||
+ type == SELECT_OPEN_FILE ||
+ type == SELECT_OPEN_MULTI_FILE ||
+ type == SELECT_SAVEAS_FILE);
+ parents_.insert(owning_window);
+
+ // Note: we need to retain the dialog as owning_window can be null.
+ // (See http://crbug.com/29213 .)
+ NSSavePanel* dialog;
+ if (type == SELECT_SAVEAS_FILE)
+ dialog = [[NSSavePanel savePanel] retain];
+ else
+ dialog = [[NSOpenPanel openPanel] retain];
+
+ if (!title.empty())
+ [dialog setTitle:base::SysUTF16ToNSString(title)];
+
+ NSString* default_dir = nil;
+ NSString* default_filename = nil;
+ if (!default_path.empty()) {
+ // The file dialog is going to do a ton of stats anyway. Not much
+ // point in eliminating this one.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ if (file_util::DirectoryExists(default_path)) {
+ default_dir = base::SysUTF8ToNSString(default_path.value());
+ } else {
+ default_dir = base::SysUTF8ToNSString(default_path.DirName().value());
+ default_filename =
+ base::SysUTF8ToNSString(default_path.BaseName().value());
+ }
+ }
+
+ NSArray* allowed_file_types = nil;
+ if (file_types) {
+ if (!file_types->extensions.empty()) {
+ // While the example given in the header for FileTypeInfo lists an example
+ // |file_types->extensions| value as
+ // { { "htm", "html" }, { "txt" } }
+ // it is not always the case that the given extensions in one of the sub-
+ // lists are all synonyms. In fact, in the case of a <select> element with
+ // multiple "accept" types, all the extensions allowed for all the types
+ // will be part of one list. To be safe, allow the types of all the
+ // specified extensions.
+ NSMutableSet* file_type_set = [NSMutableSet set];
+ for (size_t i = 0; i < file_types->extensions.size(); ++i) {
+ const std::vector<FilePath::StringType>& ext_list =
+ file_types->extensions[i];
+ for (size_t j = 0; j < ext_list.size(); ++j) {
+ base::mac::ScopedCFTypeRef<CFStringRef> uti(
+ CreateUTIFromExtension(ext_list[j]));
+ [file_type_set addObject:base::mac::CFToNSCast(uti.get())];
+
+ // Always allow the extension itself, in case the UTI doesn't map
+ // back to the original extension correctly. This occurs with dynamic
+ // UTIs on 10.7 and 10.8.
+ // See http://crbug.com/148840, http://openradar.me/12316273
+ base::mac::ScopedCFTypeRef<CFStringRef> ext_cf(
+ base::SysUTF8ToCFStringRef(ext_list[j]));
+ [file_type_set addObject:base::mac::CFToNSCast(ext_cf.get())];
+ }
+ }
+ allowed_file_types = [file_type_set allObjects];
+ }
+ if (type == SELECT_SAVEAS_FILE)
+ [dialog setAllowedFileTypes:allowed_file_types];
+ // else we'll pass it in when we run the open panel
+
+ if (file_types->include_all_files || file_types->extensions.empty())
+ [dialog setAllowsOtherFileTypes:YES];
+
+ if (file_types->extension_description_overrides.size() > 1) {
+ NSView* accessory_view = GetAccessoryView(file_types, file_type_index);
+ [dialog setAccessoryView:accessory_view];
+ }
+ } else {
+ // If no type info is specified, anything goes.
+ [dialog setAllowsOtherFileTypes:YES];
+ }
+ hasMultipleFileTypeChoices_ =
+ file_types ? file_types->extensions.size() > 1 : true;
+
+ if (!default_extension.empty())
+ [dialog setAllowedFileTypes:@[base::SysUTF8ToNSString(default_extension)]];
+
+ params_map_[dialog] = params;
+ type_map_[dialog] = type;
+
+ if (type == SELECT_SAVEAS_FILE) {
+ [dialog setCanSelectHiddenExtension:YES];
+ } else {
+ NSOpenPanel* open_dialog = (NSOpenPanel*)dialog;
+
+ if (type == SELECT_OPEN_MULTI_FILE)
+ [open_dialog setAllowsMultipleSelection:YES];
+ else
+ [open_dialog setAllowsMultipleSelection:NO];
+
+ if (type == SELECT_FOLDER) {
+ [open_dialog setCanChooseFiles:NO];
+ [open_dialog setCanChooseDirectories:YES];
+ [open_dialog setCanCreateDirectories:YES];
+ NSString *prompt = l10n_util::GetNSString(IDS_SELECT_FOLDER_BUTTON_TITLE);
+ [open_dialog setPrompt:prompt];
+ } else {
+ [open_dialog setCanChooseFiles:YES];
+ [open_dialog setCanChooseDirectories:NO];
+ }
+
+ [open_dialog setDelegate:bridge_.get()];
+ [open_dialog setAllowedFileTypes:allowed_file_types];
+ }
+ if (default_dir)
+ [dialog setDirectoryURL:[NSURL fileURLWithPath:default_dir]];
+ if (default_filename)
+ [dialog setNameFieldStringValue:default_filename];
+ [dialog beginSheetModalForWindow:owning_window
+ completionHandler:^(NSInteger result) {
+ [bridge_.get() endedPanel:dialog
+ didCancel:result != NSFileHandlingPanelOKButton
+ type:type
+ parentWindow:owning_window];
+ }];
+}
+
+SelectFileDialogImpl::~SelectFileDialogImpl() {
+ // Walk through the open dialogs and close them all. Use a temporary vector
+ // to hold the pointers, since we can't delete from the map as we're iterating
+ // through it.
+ std::vector<NSSavePanel*> panels;
+ for (std::map<NSSavePanel*, void*>::iterator it = params_map_.begin();
+ it != params_map_.end(); ++it) {
+ panels.push_back(it->first);
+ }
+
+ for (std::vector<NSSavePanel*>::iterator it = panels.begin();
+ it != panels.end(); ++it) {
+ [*it cancel:*it];
+ }
+}
+
+NSView* SelectFileDialogImpl::GetAccessoryView(const FileTypeInfo* file_types,
+ int file_type_index) {
+ DCHECK(file_types);
+ NSView* accessory_view = ui::GetViewFromNib(@"SaveAccessoryView");
+ if (!accessory_view)
+ return nil;
+
+ NSPopUpButton* popup = [accessory_view viewWithTag:kFileTypePopupTag];
+ DCHECK(popup);
+
+ size_t type_count = file_types->extensions.size();
+ for (size_t type = 0; type < type_count; ++type) {
+ NSString* type_description;
+ if (type < file_types->extension_description_overrides.size()) {
+ type_description = base::SysUTF16ToNSString(
+ file_types->extension_description_overrides[type]);
+ } else {
+ // No description given for a list of extensions; pick the first one from
+ // the list (arbitrarily) and use its description.
+ const std::vector<FilePath::StringType>& ext_list =
+ file_types->extensions[type];
+ DCHECK(!ext_list.empty());
+ base::mac::ScopedCFTypeRef<CFStringRef> uti(
+ CreateUTIFromExtension(ext_list[0]));
+ base::mac::ScopedCFTypeRef<CFStringRef> description(
+ UTTypeCopyDescription(uti.get()));
+
+ type_description =
+ [[base::mac::CFToNSCast(description.get()) retain] autorelease];
+ }
+ [popup addItemWithTitle:type_description];
+ }
+
+ [popup selectItemAtIndex:file_type_index - 1]; // 1-based
+ return accessory_view;
+}
+
+bool SelectFileDialogImpl::HasMultipleFileTypeChoicesImpl() {
+ return hasMultipleFileTypeChoices_;
+}
+
+@implementation SelectFileDialogBridge
+
+- (id)initWithSelectFileDialogImpl:(SelectFileDialogImpl*)s {
+ self = [super init];
+ if (self != nil) {
+ selectFileDialogImpl_ = s;
+ }
+ return self;
+}
+
+- (void)endedPanel:(NSSavePanel*)panel
+ didCancel:(bool)did_cancel
+ type:(ui::SelectFileDialog::Type)type
+ parentWindow:(NSWindow*)parentWindow {
+ int index = 0;
+ std::vector<FilePath> paths;
+ if (!did_cancel) {
+ if (type == ui::SelectFileDialog::SELECT_SAVEAS_FILE) {
+ if ([[panel URL] isFileURL])
+ paths.push_back(FilePath(base::SysNSStringToUTF8([[panel URL] path])));
+
+ NSView* accessoryView = [panel accessoryView];
+ if (accessoryView) {
+ NSPopUpButton* popup = [accessoryView viewWithTag:kFileTypePopupTag];
+ if (popup) {
+ // File type indexes are 1-based.
+ index = [popup indexOfSelectedItem] + 1;
+ }
+ } else {
+ index = 1;
+ }
+ } else {
+ CHECK([panel isKindOfClass:[NSOpenPanel class]]);
+ NSArray* urls = [static_cast<NSOpenPanel*>(panel) URLs];
+ for (NSURL* url in urls)
+ if ([url isFileURL])
+ paths.push_back(FilePath(base::SysNSStringToUTF8([url path])));
+ }
+ }
+
+ bool isMulti = type == ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
+ selectFileDialogImpl_->FileWasSelected(panel,
+ parentWindow,
+ did_cancel,
+ isMulti,
+ paths,
+ index);
+ [panel release];
+}
+
+- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename {
+ return selectFileDialogImpl_->ShouldEnableFilename(sender, filename);
+}
+
+@end
+
+namespace ui {
+
+SelectFileDialog* CreateMacSelectFileDialog(
+ SelectFileDialog::Listener* listener,
+ SelectFilePolicy* policy) {
+ return new SelectFileDialogImpl(listener, policy);
+}
+
+} // namespace ui
diff --git a/ui/shell_dialogs/select_file_dialog_win.cc b/ui/shell_dialogs/select_file_dialog_win.cc
new file mode 100644
index 0000000..2a41a23
--- /dev/null
+++ b/ui/shell_dialogs/select_file_dialog_win.cc
@@ -0,0 +1,864 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/dialogs/select_file_dialog_win.h"
+
+#include <windows.h>
+#include <commdlg.h>
+#include <shlobj.h>
+
+#include <algorithm>
+#include <set>
+
+#include "base/bind.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/i18n/case_conversion.h"
+#include "base/message_loop.h"
+#include "base/message_loop_proxy.h"
+#include "base/string_split.h"
+#include "base/threading/thread.h"
+#include "base/utf_string_conversions.h"
+#include "base/win/metro.h"
+#include "base/win/registry.h"
+#include "base/win/scoped_comptr.h"
+#include "base/win/shortcut.h"
+#include "base/win/windows_version.h"
+#include "grit/ui_strings.h"
+#include "ui/base/dialogs/base_shell_dialog_win.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace {
+
+// Given |extension|, if it's not empty, then remove the leading dot.
+std::wstring GetExtensionWithoutLeadingDot(const std::wstring& extension) {
+ DCHECK(extension.empty() || extension[0] == L'.');
+ return extension.empty() ? extension : extension.substr(1);
+}
+
+// Diverts to a metro-specific implementation as appropriate.
+bool CallGetOpenFileName(OPENFILENAME* ofn) {
+ HMODULE metro_module = base::win::GetMetroModule();
+ if (metro_module != NULL) {
+ typedef BOOL (*MetroGetOpenFileName)(OPENFILENAME*);
+ MetroGetOpenFileName metro_get_open_file_name =
+ reinterpret_cast<MetroGetOpenFileName>(
+ ::GetProcAddress(metro_module, "MetroGetOpenFileName"));
+ if (metro_get_open_file_name == NULL) {
+ NOTREACHED();
+ return false;
+ }
+
+ return metro_get_open_file_name(ofn) == TRUE;
+ } else {
+ return GetOpenFileName(ofn) == TRUE;
+ }
+}
+
+// Diverts to a metro-specific implementation as appropriate.
+bool CallGetSaveFileName(OPENFILENAME* ofn) {
+ HMODULE metro_module = base::win::GetMetroModule();
+ if (metro_module != NULL) {
+ typedef BOOL (*MetroGetSaveFileName)(OPENFILENAME*);
+ MetroGetSaveFileName metro_get_save_file_name =
+ reinterpret_cast<MetroGetSaveFileName>(
+ ::GetProcAddress(metro_module, "MetroGetSaveFileName"));
+ if (metro_get_save_file_name == NULL) {
+ NOTREACHED();
+ return false;
+ }
+
+ return metro_get_save_file_name(ofn) == TRUE;
+ } else {
+ return GetSaveFileName(ofn) == TRUE;
+ }
+}
+
+// Distinguish directories from regular files.
+bool IsDirectory(const FilePath& path) {
+ base::PlatformFileInfo file_info;
+ return file_util::GetFileInfo(path, &file_info) ?
+ file_info.is_directory : file_util::EndsWithSeparator(path);
+}
+
+// Get the file type description from the registry. This will be "Text Document"
+// for .txt files, "JPEG Image" for .jpg files, etc. If the registry doesn't
+// have an entry for the file type, we return false, true if the description was
+// found. 'file_ext' must be in form ".txt".
+static bool GetRegistryDescriptionFromExtension(const std::wstring& file_ext,
+ std::wstring* reg_description) {
+ DCHECK(reg_description);
+ base::win::RegKey reg_ext(HKEY_CLASSES_ROOT, file_ext.c_str(), KEY_READ);
+ std::wstring reg_app;
+ if (reg_ext.ReadValue(NULL, &reg_app) == ERROR_SUCCESS && !reg_app.empty()) {
+ base::win::RegKey reg_link(HKEY_CLASSES_ROOT, reg_app.c_str(), KEY_READ);
+ if (reg_link.ReadValue(NULL, reg_description) == ERROR_SUCCESS)
+ return true;
+ }
+ return false;
+}
+
+// Set up a filter for a Save/Open dialog, which will consist of |file_ext| file
+// extensions (internally separated by semicolons), |ext_desc| as the text
+// descriptions of the |file_ext| types (optional), and (optionally) the default
+// 'All Files' view. The purpose of the filter is to show only files of a
+// particular type in a Windows Save/Open dialog box. The resulting filter is
+// returned. The filters created here are:
+// 1. only files that have 'file_ext' as their extension
+// 2. all files (only added if 'include_all_files' is true)
+// Example:
+// file_ext: { "*.txt", "*.htm;*.html" }
+// ext_desc: { "Text Document" }
+// returned: "Text Document\0*.txt\0HTML Document\0*.htm;*.html\0"
+// "All Files\0*.*\0\0" (in one big string)
+// If a description is not provided for a file extension, it will be retrieved
+// from the registry. If the file extension does not exist in the registry, it
+// will be omitted from the filter, as it is likely a bogus extension.
+std::wstring FormatFilterForExtensions(
+ const std::vector<std::wstring>& file_ext,
+ const std::vector<std::wstring>& ext_desc,
+ bool include_all_files) {
+ const std::wstring all_ext = L"*.*";
+ const std::wstring all_desc =
+ l10n_util::GetStringUTF16(IDS_APP_SAVEAS_ALL_FILES);
+
+ DCHECK(file_ext.size() >= ext_desc.size());
+
+ if (file_ext.empty())
+ include_all_files = true;
+
+ std::wstring result;
+
+ for (size_t i = 0; i < file_ext.size(); ++i) {
+ std::wstring ext = file_ext[i];
+ std::wstring desc;
+ if (i < ext_desc.size())
+ desc = ext_desc[i];
+
+ if (ext.empty()) {
+ // Force something reasonable to appear in the dialog box if there is no
+ // extension provided.
+ include_all_files = true;
+ continue;
+ }
+
+ if (desc.empty()) {
+ DCHECK(ext.find(L'.') != std::wstring::npos);
+ std::wstring first_extension = ext.substr(ext.find(L'.'));
+ size_t first_separator_index = first_extension.find(L';');
+ if (first_separator_index != std::wstring::npos)
+ first_extension = first_extension.substr(0, first_separator_index);
+
+ // Find the extension name without the preceeding '.' character.
+ std::wstring ext_name = first_extension;
+ size_t ext_index = ext_name.find_first_not_of(L'.');
+ if (ext_index != std::wstring::npos)
+ ext_name = ext_name.substr(ext_index);
+
+ if (!GetRegistryDescriptionFromExtension(first_extension, &desc)) {
+ // The extension doesn't exist in the registry. Create a description
+ // based on the unknown extension type (i.e. if the extension is .qqq,
+ // the we create a description "QQQ File (.qqq)").
+ include_all_files = true;
+ desc = l10n_util::GetStringFUTF16(
+ IDS_APP_SAVEAS_EXTENSION_FORMAT,
+ base::i18n::ToUpper(WideToUTF16(ext_name)),
+ ext_name);
+ }
+ if (desc.empty())
+ desc = L"*." + ext_name;
+ }
+
+ result.append(desc.c_str(), desc.size() + 1); // Append NULL too.
+ result.append(ext.c_str(), ext.size() + 1);
+ }
+
+ if (include_all_files) {
+ result.append(all_desc.c_str(), all_desc.size() + 1);
+ result.append(all_ext.c_str(), all_ext.size() + 1);
+ }
+
+ result.append(1, '\0'); // Double NULL required.
+ return result;
+}
+
+// Enforce visible dialog box.
+UINT_PTR CALLBACK SaveAsDialogHook(HWND dialog, UINT message,
+ WPARAM wparam, LPARAM lparam) {
+ static const UINT kPrivateMessage = 0x2F3F;
+ switch (message) {
+ case WM_INITDIALOG: {
+ // Do nothing here. Just post a message to defer actual processing.
+ PostMessage(dialog, kPrivateMessage, 0, 0);
+ return TRUE;
+ }
+ case kPrivateMessage: {
+ // The dialog box is the parent of the current handle.
+ HWND real_dialog = GetParent(dialog);
+
+ // Retrieve the final size.
+ RECT dialog_rect;
+ GetWindowRect(real_dialog, &dialog_rect);
+
+ // Verify that the upper left corner is visible.
+ POINT point = { dialog_rect.left, dialog_rect.top };
+ HMONITOR monitor1 = MonitorFromPoint(point, MONITOR_DEFAULTTONULL);
+ point.x = dialog_rect.right;
+ point.y = dialog_rect.bottom;
+
+ // Verify that the lower right corner is visible.
+ HMONITOR monitor2 = MonitorFromPoint(point, MONITOR_DEFAULTTONULL);
+ if (monitor1 && monitor2)
+ return 0;
+
+ // Some part of the dialog box is not visible, fix it by moving is to the
+ // client rect position of the browser window.
+ HWND parent_window = GetParent(real_dialog);
+ if (!parent_window)
+ return 0;
+ WINDOWINFO parent_info;
+ parent_info.cbSize = sizeof(WINDOWINFO);
+ GetWindowInfo(parent_window, &parent_info);
+ SetWindowPos(real_dialog, NULL,
+ parent_info.rcClient.left,
+ parent_info.rcClient.top,
+ 0, 0, // Size.
+ SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE |
+ SWP_NOZORDER);
+
+ return 0;
+ }
+ }
+ return 0;
+}
+
+// Prompt the user for location to save a file.
+// Callers should provide the filter string, and also a filter index.
+// The parameter |index| indicates the initial index of filter description
+// and filter pattern for the dialog box. If |index| is zero or greater than
+// the number of total filter types, the system uses the first filter in the
+// |filter| buffer. |index| is used to specify the initial selected extension,
+// and when done contains the extension the user chose. The parameter
+// |final_name| returns the file name which contains the drive designator,
+// path, file name, and extension of the user selected file name. |def_ext| is
+// the default extension to give to the file if the user did not enter an
+// extension. If |ignore_suggested_ext| is true, any file extension contained in
+// |suggested_name| will not be used to generate the file name. This is useful
+// in the case of saving web pages, where we know the extension type already and
+// where |suggested_name| may contain a '.' character as a valid part of the
+// name, thus confusing our extension detection code.
+bool SaveFileAsWithFilter(HWND owner,
+ const std::wstring& suggested_name,
+ const std::wstring& filter,
+ const std::wstring& def_ext,
+ bool ignore_suggested_ext,
+ unsigned* index,
+ std::wstring* final_name) {
+ DCHECK(final_name);
+ // Having an empty filter makes for a bad user experience. We should always
+ // specify a filter when saving.
+ DCHECK(!filter.empty());
+ const FilePath suggested_path(suggested_name);
+ std::wstring file_part = suggested_path.BaseName().value();
+ // If the suggested_name is a root directory, file_part will be '\', and the
+ // call to GetSaveFileName below will fail.
+ if (file_part.size() == 1 && file_part[0] == L'\\')
+ file_part.clear();
+
+ // The size of the in/out buffer in number of characters we pass to win32
+ // GetSaveFileName. From MSDN "The buffer must be large enough to store the
+ // path and file name string or strings, including the terminating NULL
+ // character. ... The buffer should be at least 256 characters long.".
+ // _IsValidPathComDlg does a copy expecting at most MAX_PATH, otherwise will
+ // result in an error of FNERR_INVALIDFILENAME. So we should only pass the
+ // API a buffer of at most MAX_PATH.
+ wchar_t file_name[MAX_PATH];
+ base::wcslcpy(file_name, file_part.c_str(), arraysize(file_name));
+
+ OPENFILENAME save_as;
+ // We must do this otherwise the ofn's FlagsEx may be initialized to random
+ // junk in release builds which can cause the Places Bar not to show up!
+ ZeroMemory(&save_as, sizeof(save_as));
+ save_as.lStructSize = sizeof(OPENFILENAME);
+ save_as.hwndOwner = owner;
+ save_as.hInstance = NULL;
+
+ save_as.lpstrFilter = filter.empty() ? NULL : filter.c_str();
+
+ save_as.lpstrCustomFilter = NULL;
+ save_as.nMaxCustFilter = 0;
+ save_as.nFilterIndex = *index;
+ save_as.lpstrFile = file_name;
+ save_as.nMaxFile = arraysize(file_name);
+ save_as.lpstrFileTitle = NULL;
+ save_as.nMaxFileTitle = 0;
+
+ // Set up the initial directory for the dialog.
+ std::wstring directory;
+ if (!suggested_name.empty()) {
+ if (IsDirectory(suggested_path)) {
+ directory = suggested_path.value();
+ file_part.clear();
+ } else {
+ directory = suggested_path.DirName().value();
+ }
+ }
+
+ save_as.lpstrInitialDir = directory.c_str();
+ save_as.lpstrTitle = NULL;
+ save_as.Flags = OFN_OVERWRITEPROMPT | OFN_EXPLORER | OFN_ENABLESIZING |
+ OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST;
+ save_as.lpstrDefExt = &def_ext[0];
+ save_as.lCustData = NULL;
+
+ if (base::win::GetVersion() < base::win::VERSION_VISTA) {
+ // The save as on Windows XP remembers its last position,
+ // and if the screen resolution changed, it will be off screen.
+ save_as.Flags |= OFN_ENABLEHOOK;
+ save_as.lpfnHook = &SaveAsDialogHook;
+ }
+
+ // Must be NULL or 0.
+ save_as.pvReserved = NULL;
+ save_as.dwReserved = 0;
+
+ if (!CallGetSaveFileName(&save_as)) {
+ // Zero means the dialog was closed, otherwise we had an error.
+ DWORD error_code = CommDlgExtendedError();
+ if (error_code != 0) {
+ NOTREACHED() << "GetSaveFileName failed with code: " << error_code;
+ }
+ return false;
+ }
+
+ // Return the user's choice.
+ final_name->assign(save_as.lpstrFile);
+ *index = save_as.nFilterIndex;
+
+ // Figure out what filter got selected from the vector with embedded nulls.
+ // NOTE: The filter contains a string with embedded nulls, such as:
+ // JPG Image\0*.jpg\0All files\0*.*\0\0
+ // The filter index is 1-based index for which pair got selected. So, using
+ // the example above, if the first index was selected we need to skip 1
+ // instance of null to get to "*.jpg".
+ std::vector<std::wstring> filters;
+ if (!filter.empty() && save_as.nFilterIndex > 0)
+ base::SplitString(filter, '\0', &filters);
+ std::wstring filter_selected;
+ if (!filters.empty())
+ filter_selected = filters[(2 * (save_as.nFilterIndex - 1)) + 1];
+
+ // Get the extension that was suggested to the user (when the Save As dialog
+ // was opened). For saving web pages, we skip this step since there may be
+ // 'extension characters' in the title of the web page.
+ std::wstring suggested_ext;
+ if (!ignore_suggested_ext)
+ suggested_ext = GetExtensionWithoutLeadingDot(suggested_path.Extension());
+
+ // If we can't get the extension from the suggested_name, we use the default
+ // extension passed in. This is to cover cases like when saving a web page,
+ // where we get passed in a name without an extension and a default extension
+ // along with it.
+ if (suggested_ext.empty())
+ suggested_ext = def_ext;
+
+ *final_name =
+ ui::AppendExtensionIfNeeded(*final_name, filter_selected, suggested_ext);
+ return true;
+}
+
+// Prompt the user for location to save a file. 'suggested_name' is a full path
+// that gives the dialog box a hint as to how to initialize itself.
+// For example, a 'suggested_name' of:
+// "C:\Documents and Settings\jojo\My Documents\picture.png"
+// will start the dialog in the "C:\Documents and Settings\jojo\My Documents\"
+// directory, and filter for .png file types.
+// 'owner' is the window to which the dialog box is modal, NULL for a modeless
+// dialog box.
+// On success, returns true and 'final_name' contains the full path of the file
+// that the user chose. On error, returns false, and 'final_name' is not
+// modified.
+bool SaveFileAs(HWND owner,
+ const std::wstring& suggested_name,
+ std::wstring* final_name) {
+ std::wstring file_ext = FilePath(suggested_name).Extension().insert(0, L"*");
+ std::wstring filter = FormatFilterForExtensions(
+ std::vector<std::wstring>(1, file_ext),
+ std::vector<std::wstring>(),
+ true);
+ unsigned index = 1;
+ return SaveFileAsWithFilter(owner,
+ suggested_name,
+ filter,
+ L"",
+ false,
+ &index,
+ final_name);
+}
+
+// Implementation of SelectFileDialog that shows a Windows common dialog for
+// choosing a file or folder.
+class SelectFileDialogImpl : public ui::SelectFileDialog,
+ public ui::BaseShellDialogImpl {
+ public:
+ explicit SelectFileDialogImpl(Listener* listener,
+ ui::SelectFilePolicy* policy);
+
+ // BaseShellDialog implementation:
+ virtual bool IsRunning(HWND owning_hwnd) const OVERRIDE;
+ virtual void ListenerDestroyed() OVERRIDE;
+
+ protected:
+ // SelectFileDialog implementation:
+ virtual void SelectFileImpl(Type type,
+ const string16& title,
+ const FilePath& default_path,
+ const FileTypeInfo* file_types,
+ int file_type_index,
+ const FilePath::StringType& default_extension,
+ gfx::NativeWindow owning_window,
+ void* params) OVERRIDE;
+
+ private:
+ virtual ~SelectFileDialogImpl();
+
+ // A struct for holding all the state necessary for displaying a Save dialog.
+ struct ExecuteSelectParams {
+ ExecuteSelectParams(Type type,
+ const std::wstring& title,
+ const FilePath& default_path,
+ const FileTypeInfo* file_types,
+ int file_type_index,
+ const std::wstring& default_extension,
+ RunState run_state,
+ HWND owner,
+ void* params)
+ : type(type),
+ title(title),
+ default_path(default_path),
+ file_type_index(file_type_index),
+ default_extension(default_extension),
+ run_state(run_state),
+ ui_proxy(MessageLoopForUI::current()->message_loop_proxy()),
+ owner(owner),
+ params(params) {
+ if (file_types)
+ this->file_types = *file_types;
+ }
+ SelectFileDialog::Type type;
+ std::wstring title;
+ FilePath default_path;
+ FileTypeInfo file_types;
+ int file_type_index;
+ std::wstring default_extension;
+ RunState run_state;
+ scoped_refptr<base::MessageLoopProxy> ui_proxy;
+ HWND owner;
+ void* params;
+ };
+
+ // Shows the file selection dialog modal to |owner| and calls the result
+ // back on the ui thread. Run on the dialog thread.
+ void ExecuteSelectFile(const ExecuteSelectParams& params);
+
+ // Notifies the listener that a folder was chosen. Run on the ui thread.
+ void FileSelected(const FilePath& path, int index,
+ void* params, RunState run_state);
+
+ // Notifies listener that multiple files were chosen. Run on the ui thread.
+ void MultiFilesSelected(const std::vector<FilePath>& paths, void* params,
+ RunState run_state);
+
+ // Notifies the listener that no file was chosen (the action was canceled).
+ // Run on the ui thread.
+ void FileNotSelected(void* params, RunState run_state);
+
+ // Runs a Folder selection dialog box, passes back the selected folder in
+ // |path| and returns true if the user clicks OK. If the user cancels the
+ // dialog box the value in |path| is not modified and returns false. |title|
+ // is the user-supplied title text to show for the dialog box. Run on the
+ // dialog thread.
+ bool RunSelectFolderDialog(const std::wstring& title,
+ HWND owner,
+ FilePath* path);
+
+ // Runs an Open file dialog box, with similar semantics for input paramaters
+ // as RunSelectFolderDialog.
+ bool RunOpenFileDialog(const std::wstring& title,
+ const std::wstring& filters,
+ HWND owner,
+ FilePath* path);
+
+ // Runs an Open file dialog box that supports multi-select, with similar
+ // semantics for input paramaters as RunOpenFileDialog.
+ bool RunOpenMultiFileDialog(const std::wstring& title,
+ const std::wstring& filter,
+ HWND owner,
+ std::vector<FilePath>* paths);
+
+ // The callback function for when the select folder dialog is opened.
+ static int CALLBACK BrowseCallbackProc(HWND window, UINT message,
+ LPARAM parameter,
+ LPARAM data);
+
+ virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE;
+
+ bool has_multiple_file_type_choices_;
+
+
+ DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImpl);
+};
+
+SelectFileDialogImpl::SelectFileDialogImpl(Listener* listener,
+ ui::SelectFilePolicy* policy)
+ : SelectFileDialog(listener, policy),
+ BaseShellDialogImpl(),
+ has_multiple_file_type_choices_(false) {
+}
+
+SelectFileDialogImpl::~SelectFileDialogImpl() {
+}
+
+void SelectFileDialogImpl::SelectFileImpl(
+ Type type,
+ const string16& title,
+ const FilePath& default_path,
+ const FileTypeInfo* file_types,
+ int file_type_index,
+ const FilePath::StringType& default_extension,
+ gfx::NativeWindow owning_window,
+ void* params) {
+ has_multiple_file_type_choices_ =
+ file_types ? file_types->extensions.size() > 1 : true;
+ ExecuteSelectParams execute_params(type, UTF16ToWide(title), default_path,
+ file_types, file_type_index,
+ default_extension, BeginRun(owning_window),
+ owning_window, params);
+ execute_params.run_state.dialog_thread->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&SelectFileDialogImpl::ExecuteSelectFile, this,
+ execute_params));
+}
+
+bool SelectFileDialogImpl::HasMultipleFileTypeChoicesImpl() {
+ return has_multiple_file_type_choices_;
+}
+
+bool SelectFileDialogImpl::IsRunning(HWND owning_hwnd) const {
+ return listener_ && IsRunningDialogForOwner(owning_hwnd);
+}
+
+void SelectFileDialogImpl::ListenerDestroyed() {
+ // Our associated listener has gone away, so we shouldn't call back to it if
+ // our worker thread returns after the listener is dead.
+ listener_ = NULL;
+}
+
+void SelectFileDialogImpl::ExecuteSelectFile(
+ const ExecuteSelectParams& params) {
+ std::vector<std::wstring> exts;
+ for (size_t i = 0; i < params.file_types.extensions.size(); ++i) {
+ const std::vector<std::wstring>& inner_exts =
+ params.file_types.extensions[i];
+ std::wstring ext_string;
+ for (size_t j = 0; j < inner_exts.size(); ++j) {
+ if (!ext_string.empty())
+ ext_string.push_back(L';');
+ ext_string.append(L"*.");
+ ext_string.append(inner_exts[j]);
+ }
+ exts.push_back(ext_string);
+ }
+ std::wstring filter = FormatFilterForExtensions(
+ exts,
+ params.file_types.extension_description_overrides,
+ params.file_types.include_all_files);
+
+ FilePath path = params.default_path;
+ bool success = false;
+ unsigned filter_index = params.file_type_index;
+ if (params.type == SELECT_FOLDER) {
+ success = RunSelectFolderDialog(params.title,
+ params.run_state.owner,
+ &path);
+ } else if (params.type == SELECT_SAVEAS_FILE) {
+ std::wstring path_as_wstring = path.value();
+ success = SaveFileAsWithFilter(params.run_state.owner,
+ params.default_path.value(), filter,
+ params.default_extension, false, &filter_index, &path_as_wstring);
+ if (success)
+ path = FilePath(path_as_wstring);
+ DisableOwner(params.run_state.owner);
+ } else if (params.type == SELECT_OPEN_FILE) {
+ success = RunOpenFileDialog(params.title, filter,
+ params.run_state.owner, &path);
+ } else if (params.type == SELECT_OPEN_MULTI_FILE) {
+ std::vector<FilePath> paths;
+ if (RunOpenMultiFileDialog(params.title, filter,
+ params.run_state.owner, &paths)) {
+ params.ui_proxy->PostTask(
+ FROM_HERE,
+ base::Bind(&SelectFileDialogImpl::MultiFilesSelected, this, paths,
+ params.params, params.run_state));
+ return;
+ }
+ }
+
+ if (success) {
+ params.ui_proxy->PostTask(
+ FROM_HERE,
+ base::Bind(&SelectFileDialogImpl::FileSelected, this, path,
+ filter_index, params.params, params.run_state));
+ } else {
+ params.ui_proxy->PostTask(
+ FROM_HERE,
+ base::Bind(&SelectFileDialogImpl::FileNotSelected, this, params.params,
+ params.run_state));
+ }
+}
+
+void SelectFileDialogImpl::FileSelected(const FilePath& selected_folder,
+ int index,
+ void* params,
+ RunState run_state) {
+ if (listener_)
+ listener_->FileSelected(selected_folder, index, params);
+ EndRun(run_state);
+}
+
+void SelectFileDialogImpl::MultiFilesSelected(
+ const std::vector<FilePath>& selected_files,
+ void* params,
+ RunState run_state) {
+ if (listener_)
+ listener_->MultiFilesSelected(selected_files, params);
+ EndRun(run_state);
+}
+
+void SelectFileDialogImpl::FileNotSelected(void* params, RunState run_state) {
+ if (listener_)
+ listener_->FileSelectionCanceled(params);
+ EndRun(run_state);
+}
+
+int CALLBACK SelectFileDialogImpl::BrowseCallbackProc(HWND window,
+ UINT message,
+ LPARAM parameter,
+ LPARAM data) {
+ if (message == BFFM_INITIALIZED) {
+ // WParam is TRUE since passing a path.
+ // data lParam member of the BROWSEINFO structure.
+ SendMessage(window, BFFM_SETSELECTION, TRUE, (LPARAM)data);
+ }
+ return 0;
+}
+
+bool SelectFileDialogImpl::RunSelectFolderDialog(const std::wstring& title,
+ HWND owner,
+ FilePath* path) {
+ DCHECK(path);
+
+ wchar_t dir_buffer[MAX_PATH + 1];
+
+ bool result = false;
+ BROWSEINFO browse_info = {0};
+ browse_info.hwndOwner = owner;
+ browse_info.lpszTitle = title.c_str();
+ browse_info.pszDisplayName = dir_buffer;
+ browse_info.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS;
+
+ if (path->value().length()) {
+ // Highlight the current value.
+ browse_info.lParam = (LPARAM)path->value().c_str();
+ browse_info.lpfn = &BrowseCallbackProc;
+ }
+
+ LPITEMIDLIST list = SHBrowseForFolder(&browse_info);
+ DisableOwner(owner);
+ if (list) {
+ STRRET out_dir_buffer;
+ ZeroMemory(&out_dir_buffer, sizeof(out_dir_buffer));
+ out_dir_buffer.uType = STRRET_WSTR;
+ base::win::ScopedComPtr<IShellFolder> shell_folder;
+ if (SHGetDesktopFolder(shell_folder.Receive()) == NOERROR) {
+ HRESULT hr = shell_folder->GetDisplayNameOf(list, SHGDN_FORPARSING,
+ &out_dir_buffer);
+ if (SUCCEEDED(hr) && out_dir_buffer.uType == STRRET_WSTR) {
+ *path = FilePath(out_dir_buffer.pOleStr);
+ CoTaskMemFree(out_dir_buffer.pOleStr);
+ result = true;
+ } else {
+ // Use old way if we don't get what we want.
+ wchar_t old_out_dir_buffer[MAX_PATH + 1];
+ if (SHGetPathFromIDList(list, old_out_dir_buffer)) {
+ *path = FilePath(old_out_dir_buffer);
+ result = true;
+ }
+ }
+
+ // According to MSDN, win2000 will not resolve shortcuts, so we do it
+ // ourself.
+ base::win::ResolveShortcut(*path, path, NULL);
+ }
+ CoTaskMemFree(list);
+ }
+ return result;
+}
+
+bool SelectFileDialogImpl::RunOpenFileDialog(
+ const std::wstring& title,
+ const std::wstring& filter,
+ HWND owner,
+ FilePath* path) {
+ OPENFILENAME ofn;
+ // We must do this otherwise the ofn's FlagsEx may be initialized to random
+ // junk in release builds which can cause the Places Bar not to show up!
+ ZeroMemory(&ofn, sizeof(ofn));
+ ofn.lStructSize = sizeof(ofn);
+ ofn.hwndOwner = owner;
+
+ wchar_t filename[MAX_PATH];
+ // According to http://support.microsoft.com/?scid=kb;en-us;222003&x=8&y=12,
+ // The lpstrFile Buffer MUST be NULL Terminated.
+ filename[0] = 0;
+ // Define the dir in here to keep the string buffer pointer pointed to
+ // ofn.lpstrInitialDir available during the period of running the
+ // GetOpenFileName.
+ FilePath dir;
+ // Use lpstrInitialDir to specify the initial directory
+ if (!path->empty()) {
+ if (IsDirectory(*path)) {
+ ofn.lpstrInitialDir = path->value().c_str();
+ } else {
+ dir = path->DirName();
+ ofn.lpstrInitialDir = dir.value().c_str();
+ // Only pure filename can be put in lpstrFile field.
+ base::wcslcpy(filename, path->BaseName().value().c_str(),
+ arraysize(filename));
+ }
+ }
+
+ ofn.lpstrFile = filename;
+ ofn.nMaxFile = MAX_PATH;
+
+ // We use OFN_NOCHANGEDIR so that the user can rename or delete the directory
+ // without having to close Chrome first.
+ ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
+
+ if (!filter.empty())
+ ofn.lpstrFilter = filter.c_str();
+ bool success = CallGetOpenFileName(&ofn);
+ DisableOwner(owner);
+ if (success)
+ *path = FilePath(filename);
+ return success;
+}
+
+bool SelectFileDialogImpl::RunOpenMultiFileDialog(
+ const std::wstring& title,
+ const std::wstring& filter,
+ HWND owner,
+ std::vector<FilePath>* paths) {
+ OPENFILENAME ofn;
+ // We must do this otherwise the ofn's FlagsEx may be initialized to random
+ // junk in release builds which can cause the Places Bar not to show up!
+ ZeroMemory(&ofn, sizeof(ofn));
+ ofn.lStructSize = sizeof(ofn);
+ ofn.hwndOwner = owner;
+
+ scoped_array<wchar_t> filename(new wchar_t[UNICODE_STRING_MAX_CHARS]);
+ filename[0] = 0;
+
+ ofn.lpstrFile = filename.get();
+ ofn.nMaxFile = UNICODE_STRING_MAX_CHARS;
+ // We use OFN_NOCHANGEDIR so that the user can rename or delete the directory
+ // without having to close Chrome first.
+ ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER
+ | OFN_HIDEREADONLY | OFN_ALLOWMULTISELECT;
+
+ if (!filter.empty()) {
+ ofn.lpstrFilter = filter.c_str();
+ }
+
+ bool success = CallGetOpenFileName(&ofn);
+ DisableOwner(owner);
+ if (success) {
+ std::vector<FilePath> files;
+ const wchar_t* selection = ofn.lpstrFile;
+ while (*selection) { // Empty string indicates end of list.
+ files.push_back(FilePath(selection));
+ // Skip over filename and null-terminator.
+ selection += files.back().value().length() + 1;
+ }
+ if (files.empty()) {
+ success = false;
+ } else if (files.size() == 1) {
+ // When there is one file, it contains the path and filename.
+ paths->swap(files);
+ } else {
+ // Otherwise, the first string is the path, and the remainder are
+ // filenames.
+ std::vector<FilePath>::iterator path = files.begin();
+ for (std::vector<FilePath>::iterator file = path + 1;
+ file != files.end(); ++file) {
+ paths->push_back(path->Append(*file));
+ }
+ }
+ }
+ return success;
+}
+
+} // namespace
+
+namespace ui {
+
+// This function takes the output of a SaveAs dialog: a filename, a filter and
+// the extension originally suggested to the user (shown in the dialog box) and
+// returns back the filename with the appropriate extension tacked on. If the
+// user requests an unknown extension and is not using the 'All files' filter,
+// the suggested extension will be appended, otherwise we will leave the
+// filename unmodified. |filename| should contain the filename selected in the
+// SaveAs dialog box and may include the path, |filter_selected| should be
+// '*.something', for example '*.*' or it can be blank (which is treated as
+// *.*). |suggested_ext| should contain the extension without the dot (.) in
+// front, for example 'jpg'.
+std::wstring AppendExtensionIfNeeded(
+ const std::wstring& filename,
+ const std::wstring& filter_selected,
+ const std::wstring& suggested_ext) {
+ DCHECK(!filename.empty());
+ std::wstring return_value = filename;
+
+ // If we wanted a specific extension, but the user's filename deleted it or
+ // changed it to something that the system doesn't understand, re-append.
+ // Careful: Checking net::GetMimeTypeFromExtension() will only find
+ // extensions with a known MIME type, which many "known" extensions on Windows
+ // don't have. So we check directly for the "known extension" registry key.
+ std::wstring file_extension(
+ GetExtensionWithoutLeadingDot(FilePath(filename).Extension()));
+ std::wstring key(L"." + file_extension);
+ if (!(filter_selected.empty() || filter_selected == L"*.*") &&
+ !base::win::RegKey(HKEY_CLASSES_ROOT, key.c_str(), KEY_READ).Valid() &&
+ file_extension != suggested_ext) {
+ if (return_value[return_value.length() - 1] != L'.')
+ return_value.append(L".");
+ return_value.append(suggested_ext);
+ }
+
+ // Strip any trailing dots, which Windows doesn't allow.
+ size_t index = return_value.find_last_not_of(L'.');
+ if (index < return_value.size() - 1)
+ return_value.resize(index + 1);
+
+ return return_value;
+}
+
+SelectFileDialog* CreateWinSelectFileDialog(
+ SelectFileDialog::Listener* listener,
+ SelectFilePolicy* policy) {
+ return new SelectFileDialogImpl(listener, policy);
+}
+
+} // namespace ui
diff --git a/ui/shell_dialogs/select_file_dialog_win.h b/ui/shell_dialogs/select_file_dialog_win.h
new file mode 100644
index 0000000..8175042
--- /dev/null
+++ b/ui/shell_dialogs/select_file_dialog_win.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_DIALOGS_SELECT_FILE_DIALOG_WIN_H_
+#define UI_BASE_DIALOGS_SELECT_FILE_DIALOG_WIN_H_
+
+#include "ui/base/ui_export.h"
+#include "ui/base/dialogs/select_file_dialog.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace ui {
+class SelectFilePolicy;
+
+// Implementation detail exported for unit tests.
+UI_EXPORT std::wstring AppendExtensionIfNeeded(
+ const std::wstring& filename,
+ const std::wstring& filter_selected,
+ const std::wstring& suggested_ext);
+
+// Intentionally not exported. Implementation detail of SelectFileDialog. Use
+// SelectFileDialog::Create() instead.
+SelectFileDialog* CreateWinSelectFileDialog(
+ SelectFileDialog::Listener* listener,
+ SelectFilePolicy* policy);
+
+} // namespace ui
+
+#endif // UI_BASE_DIALOGS_SELECT_FILE_DIALOG_WIN_H_
diff --git a/ui/shell_dialogs/select_file_dialog_win_unittest.cc b/ui/shell_dialogs/select_file_dialog_win_unittest.cc
new file mode 100644
index 0000000..f16c4d4
--- /dev/null
+++ b/ui/shell_dialogs/select_file_dialog_win_unittest.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/dialogs/select_file_dialog.h"
+#include "ui/base/dialogs/select_file_dialog_win.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(ShellDialogsWin, AppendExtensionIfNeeded) {
+ struct AppendExtensionTestCase {
+ wchar_t* filename;
+ wchar_t* filter_selected;
+ wchar_t* suggested_ext;
+ wchar_t* expected_filename;
+ } test_cases[] = {
+ // Known extensions, with or without associated MIME types, should not get
+ // an extension appended.
+ { L"sample.html", L"*.txt", L"txt", L"sample.html" },
+ { L"sample.reg", L"*.txt", L"txt", L"sample.reg" },
+
+ // An unknown extension, or no extension, should get the default extension
+ // appended.
+ { L"sample.unknown", L"*.txt", L"txt", L"sample.unknown.txt" },
+ { L"sample", L"*.txt", L"txt", L"sample.txt" },
+ // ...unless the unknown and default extensions match.
+ { L"sample.unknown", L"*.unknown", L"unknown", L"sample.unknown" },
+
+ // The extension alone should be treated like a filename with no extension.
+ { L"txt", L"*.txt", L"txt", L"txt.txt" },
+
+ // Trailing dots should cause us to append an extension.
+ { L"sample.txt.", L"*.txt", L"txt", L"sample.txt.txt" },
+ { L"...", L"*.txt", L"txt", L"...txt" },
+
+ // If the filter is changed to "All files", we allow any filename.
+ { L"sample.unknown", L"*.*", L"", L"sample.unknown" },
+ };
+
+ for (size_t i = 0; i < arraysize(test_cases); ++i) {
+ EXPECT_EQ(std::wstring(test_cases[i].expected_filename),
+ ui::AppendExtensionIfNeeded(test_cases[i].filename,
+ test_cases[i].filter_selected,
+ test_cases[i].suggested_ext));
+ }
+}
diff --git a/ui/shell_dialogs/select_file_policy.cc b/ui/shell_dialogs/select_file_policy.cc
new file mode 100644
index 0000000..8e1b309
--- /dev/null
+++ b/ui/shell_dialogs/select_file_policy.cc
@@ -0,0 +1,11 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/dialogs/select_file_policy.h"
+
+namespace ui {
+
+SelectFilePolicy::~SelectFilePolicy() {}
+
+} // namespace ui
diff --git a/ui/shell_dialogs/select_file_policy.h b/ui/shell_dialogs/select_file_policy.h
new file mode 100644
index 0000000..63e8781
--- /dev/null
+++ b/ui/shell_dialogs/select_file_policy.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_DIALOGS_SELECT_FILE_POLICY_H_
+#define UI_BASE_DIALOGS_SELECT_FILE_POLICY_H_
+
+#include "ui/base/ui_export.h"
+
+namespace ui {
+
+// An optional policy class that provides decisions on whether to allow showing
+// a native file dialog. Some ports need this.
+class UI_EXPORT SelectFilePolicy {
+ public:
+ virtual ~SelectFilePolicy();
+
+ // Returns true if the current policy allows for file selection dialogs.
+ virtual bool CanOpenSelectFileDialog() = 0;
+
+ // Called from the SelectFileDialog when we've denied a request.
+ virtual void SelectFileDenied() = 0;
+};
+
+} // namespace ui
+
+#endif // UI_BASE_DIALOGS_SELECT_FILE_POLICY_H_
diff --git a/ui/shell_dialogs/selected_file_info.cc b/ui/shell_dialogs/selected_file_info.cc
new file mode 100644
index 0000000..9111f05
--- /dev/null
+++ b/ui/shell_dialogs/selected_file_info.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/dialogs/selected_file_info.h"
+
+namespace ui {
+
+SelectedFileInfo::SelectedFileInfo() {}
+
+SelectedFileInfo::SelectedFileInfo(const FilePath& in_file_path,
+ const FilePath& in_local_path)
+ : file_path(in_file_path),
+ local_path(in_local_path) {
+ if (local_path.empty())
+ local_path = file_path;
+ display_name = in_file_path.BaseName().value();
+}
+
+SelectedFileInfo::~SelectedFileInfo() {}
+
+} // namespace ui
diff --git a/ui/shell_dialogs/selected_file_info.h b/ui/shell_dialogs/selected_file_info.h
new file mode 100644
index 0000000..db7c570
--- /dev/null
+++ b/ui/shell_dialogs/selected_file_info.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_DIALOGS_SELECTED_FILE_INFO_H_
+#define UI_BASE_DIALOGS_SELECTED_FILE_INFO_H_
+
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/string16.h"
+#include "ui/base/ui_export.h"
+
+namespace ui {
+
+// Struct used for passing selected file info to WebKit.
+struct UI_EXPORT SelectedFileInfo {
+ // Selected file's user friendly path as seen in the UI.
+ FilePath file_path;
+
+ // The actual local path to the selected file. This can be a snapshot file
+ // with a human unreadable name like /blah/.d41d8cd98f00b204e9800998ecf8427e.
+ // |real_path| can differ from |file_path| for drive files (e.g.
+ // /drive_cache/temporary/d41d8cd98f00b204e9800998ecf8427e vs.
+ // /special/drive/foo.txt).
+ // If not set, defaults to |file_path|.
+ FilePath local_path;
+
+ // This field is optional. The display name contains only the base name
+ // portion of a file name (ex. no path separators), and used for displaying
+ // selected file names. If this field is empty, the base name portion of
+ // |path| is used for displaying.
+ FilePath::StringType display_name;
+
+ SelectedFileInfo();
+ SelectedFileInfo(const FilePath& in_file_path,
+ const FilePath& in_local_path);
+ ~SelectedFileInfo();
+};
+
+} // namespace ui
+
+#endif // UI_BASE_DIALOGS_SELECTED_FILE_INFO_H_