diff options
author | ananta@chromium.org <ananta@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-16 23:08:27 +0000 |
---|---|---|
committer | ananta@chromium.org <ananta@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-16 23:08:27 +0000 |
commit | e8c62b968fba790e682b0d77416131a2e916f475 (patch) | |
tree | e4755e37cf6208c182aa8c5290923e16b5f53cf3 /ui/shell_dialogs | |
parent | fdc9eec417bb1eaf98485ab7e1285fcb432bcbcd (diff) | |
download | chromium_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')
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, ®_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_ |