diff --git a/src/FileNames.cpp b/src/FileNames.cpp index 82adcfa4a..f5f258aee 100644 --- a/src/FileNames.cpp +++ b/src/FileNames.cpp @@ -49,6 +49,102 @@ used throughout Audacity into this one place. static wxString gDataDir; +const FileNames::FileType + FileNames::AllFiles{ XO("All files"), { wxT("") } } + /* i18n-hint an Audacity project is the state of the program, stored as + files that can be reopened to resume the session later */ + , FileNames::AudacityProjects{ XO("Audacity projects"), { wxT("aup") }, true } + , FileNames::DynamicLibraries{ +#if defined(__WXMSW__) + XO("Dynamically Linked Libraries"), { wxT("dll") }, true +#elif defined(__WXMAC__) + XO("Dynamic Libraries"), { wxT("dylib") }, true +#else + XO("Dynamically Linked Libraries"), { wxT("so*") }, true +#endif + } + , FileNames::TextFiles{ XO("Text files"), { wxT("txt") }, true } + , FileNames::XMLFiles{ XO("XML files"), { wxT("xml"), wxT("XML") }, true } +; + +wxString FileNames::FormatWildcard( const FileTypes &fileTypes ) +{ + // |-separated list of: + // [ Description, + // ( if appendExtensions, then ' (', globs, ')' ), + // '|', + // globs ] + // where globs is a ;-separated list of filename patterns, which are + // '*' for an empty extension, else '*.' then the extension + // Only the part before | is displayed in the choice drop-down of file + // dialogs + // + // Exceptional case: if there is only one type and its description is empty, + // then just give the globs with no | + // Another exception: an empty description, when there is more than one + // type, is replaced with a default + // Another exception: if an extension contains a dot, it is interpreted as + // not really an extension, but a literal filename + + const wxString dot{ '.' }; + const auto makeGlobs = [&dot]( const FileExtensions &extensions ){ + wxString globs; + for ( const auto &extension: extensions ) { + if ( !globs.empty() ) + globs += ';'; + if ( extension.Contains( dot ) ) + globs += extension; + else { + globs += '*'; + if ( !extension.empty() ) { + globs += '.'; + globs += extension; + } + } + } + return globs; + }; + + const auto defaultDescription = []( const FileExtensions &extensions ){ + // Assume extensions is not empty + wxString exts = extensions[0]; + for (size_t ii = 1, size = extensions.size(); ii < size; ++ii ) { + exts += XO(", ").Translation(); + exts += extensions[ii]; + } + /* i18n-hint a type or types such as "txt" or "txt, xml" will be + substituted for %s */ + return XO("%s files").Format( exts ); + }; + + if ( fileTypes.size() == 1 && fileTypes[0].description.empty() ) { + return makeGlobs( fileTypes[0].extensions ); + } + else { + wxString result; + for ( const auto &fileType : fileTypes ) { + const auto &extensions = fileType.extensions; + if (extensions.empty()) + continue; + + if (!result.empty()) + result += '|'; + + const auto globs = makeGlobs( extensions ); + + auto mask = fileType.description; + if ( mask.empty() ) + mask = defaultDescription( extensions ); + if ( fileType.appendExtensions ) + mask.Join( XO("(%s)").Format( globs ), " " ); + result += mask.Translation(); + result += '|'; + result += globs; + } + return result; + } +} + bool FileNames::CopyFile( const FilePath& file1, const FilePath& file2, bool overwrite) { diff --git a/src/FileNames.h b/src/FileNames.h index bdb3b597b..f2431dbd6 100644 --- a/src/FileNames.h +++ b/src/FileNames.h @@ -16,6 +16,7 @@ #include // for wxDIR_FILES #include // function return value #include "audacity/Types.h" +#include "MemoryX.h" class wxFileName; class wxFileNameWrapper; @@ -25,6 +26,37 @@ class wxFileNameWrapper; class AUDACITY_DLL_API FileNames { public: + // A description of a type of file + struct FileType { + FileType() = default; + + FileType( TranslatableString d, FileExtensions e, bool a = false ) + : description{ std::move( d ) } + , extensions( std::move( e ) ) + , appendExtensions{ a } + {} + + TranslatableString description; + FileExtensions extensions; + // Whether to extend the displayed description with mention of the + // extensions: + bool appendExtensions = false; + }; + + // Frequently used types + static const FileType + AllFiles // * + , AudacityProjects // *.aup + , DynamicLibraries // depends on the operating system + , TextFiles // *.txt + , XMLFiles; // *.xml, *.XML + + using FileTypes = std::vector< FileType >; + + // Convert fileTypes into a single string as expected by wxWidgets file + // selection dialog + static wxString FormatWildcard( const FileTypes &fileTypes ); + // This exists to compensate for bugs in wxCopyFile: static bool CopyFile( const FilePath& file1, const FilePath& file2, bool overwrite = true); diff --git a/src/import/Import.cpp b/src/import/Import.cpp index fda4d7d9d..a64452397 100644 --- a/src/import/Import.cpp +++ b/src/import/Import.cpp @@ -41,6 +41,7 @@ and ImportLOF.cpp. #include "ImportPlugin.h" #include +#include #include #include @@ -145,6 +146,78 @@ void Importer::GetSupportedImportFormats(FormatList *formatList) } } +FileNames::FileTypes +Importer::GetFileTypes( const FileNames::FileType &extraType ) +{ + // Construct the filter + FileNames::FileTypes fileTypes{ + FileNames::AllFiles, + // Will fill in the list of extensions later: + { XO("All supported files"), {} } + }; + + if ( !extraType.extensions.empty() ) + fileTypes.push_back( extraType ); + + FormatList l; + GetSupportedImportFormats(&l); + + using ExtensionSet = std::unordered_set< FileExtension >; + FileExtensions allList = extraType.extensions, newList; + ExtensionSet allSet{ allList.begin(), allList.end() }, newSet; + for ( const auto &format : l ) { + newList.clear(); + newSet.clear(); + for ( const auto &extension : format.formatExtensions ) { + if ( newSet.insert( extension ).second ) + newList.push_back( extension ); + if ( allSet.insert( extension ).second ) + allList.push_back( extension ); + } + fileTypes.push_back( { format.formatName, newList } ); + } + + fileTypes[1].extensions = allList; + return fileTypes; +} + +void Importer::SetLastOpenType( const FileNames::FileType &type ) +{ + // PRL: Preference key /LastOpenType, unusually, stores a localized + // string! + // The bad consequences of a change of locale are not severe -- only that + // a default choice of file type for an open dialog is not remembered + gPrefs->Write(wxT("/LastOpenType"), type.description.Translation()); + gPrefs->Flush(); +} + +void Importer::SetDefaultOpenType( const FileNames::FileType &type ) +{ + // PRL: Preference key /DefaultOpenType, unusually, stores a localized + // string! + // The bad consequences of a change of locale are not severe -- only that + // a default choice of file type for an open dialog is not remembered + gPrefs->Write(wxT("/DefaultOpenType"), type.description.Translation()); + gPrefs->Flush(); +} + +size_t Importer::SelectDefaultOpenType( const FileNames::FileTypes &fileTypes ) +{ + wxString defaultValue; + if ( !fileTypes.empty() ) + defaultValue = fileTypes[0].description.Translation(); + + wxString type = gPrefs->Read(wxT("/DefaultOpenType"), defaultValue); + // Convert the type to the filter index + auto begin = fileTypes.begin(); + auto index = std::distance( + begin, + std::find_if( begin, fileTypes.end(), + [&type](const FileNames::FileType &fileType){ + return fileType.description.Translation() == type; } ) ); + return (index == fileTypes.size()) ? 0 : index; +} + void Importer::StringToList(wxString &str, wxString &delims, wxArrayString &list, wxStringTokenizerMode mod) { wxStringTokenizer toker; @@ -389,10 +462,6 @@ bool Importer::Import(const FilePath &fName, for (const auto &plugin : sImportPluginList()) { - // PRL: Preference keys /DefaultOpenType and /LastOpenType, unusually, - // store localized strings! - // The bad consequences of a change of locale are not severe -- only that - // a default choice of file type for an open dialog is not remembered if (plugin->GetPluginFormatDescription().Translation() == type ) { // This plugin corresponds to user-selected filter, try it first. diff --git a/src/import/Import.h b/src/import/Import.h index f56a4f3b3..8cff8cc85 100644 --- a/src/import/Import.h +++ b/src/import/Import.h @@ -18,6 +18,7 @@ #include // for enum wxStringTokenizerMode #include "../widgets/wxPanelWrapper.h" // to inherit +#include "../FileNames.h" // for FileType class wxArrayString; class wxListBox; @@ -120,6 +121,31 @@ public: */ void GetSupportedImportFormats(FormatList *formatList); + /** + * Constructs a list of types, for use by file opening dialogs, that includes + * all supported file types + */ + FileNames::FileTypes + GetFileTypes( const FileNames::FileType &extraType = {} ); + + /** + * Remember a file type in preferences + */ + static void + SetLastOpenType( const FileNames::FileType &type ); + + /** + * Remember a file type in preferences + */ + static void + SetDefaultOpenType( const FileNames::FileType &type ); + + /** + * Choose index of preferred type + */ + static size_t + SelectDefaultOpenType( const FileNames::FileTypes &fileTypes ); + /** * Reads extended import filters from gPrefs into internal * list mExtImportItems