diff --git a/build/libaegisub/libaegisub.vcxproj b/build/libaegisub/libaegisub.vcxproj index a778d8d15f4993f5fa0315a37542b8ef068fe0b4..7e3cd44c0c50ded143adf5c19ac635b85ab55b30 100644 --- a/build/libaegisub/libaegisub.vcxproj +++ b/build/libaegisub/libaegisub.vcxproj @@ -55,6 +55,8 @@ <ClInclude Include="$(SrcDir)include\libaegisub\exception.h" /> <ClInclude Include="$(SrcDir)include\libaegisub\file_mapping.h" /> <ClInclude Include="$(SrcDir)include\libaegisub\format.h" /> + <ClInclude Include="$(SrcDir)include\libaegisub\format_flyweight.h" /> + <ClInclude Include="$(SrcDir)include\libaegisub\format_path.h" /> <ClInclude Include="$(SrcDir)include\libaegisub\fs.h" /> <ClInclude Include="$(SrcDir)include\libaegisub\fs_fwd.h" /> <ClInclude Include="$(SrcDir)include\libaegisub\hotkey.h" /> diff --git a/build/libaegisub/libaegisub.vcxproj.filters b/build/libaegisub/libaegisub.vcxproj.filters index 71d5059d4b86de53606c90d572370d2259592a12..474ac2553a6ce8ee6bd8e38885c175bee2b41c7a 100644 --- a/build/libaegisub/libaegisub.vcxproj.filters +++ b/build/libaegisub/libaegisub.vcxproj.filters @@ -173,6 +173,12 @@ <ClInclude Include="$(SrcDir)include\libaegisub\format.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="$(SrcDir)include\libaegisub\format_flyweight.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="$(SrcDir)include\libaegisub\format_path.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="$(SrcDir)include\libaegisub\character_count.h"> <Filter>Header Files</Filter> </ClInclude> diff --git a/build/tests/tests.vcxproj b/build/tests/tests.vcxproj index 7ab92bbb71293c90ad806625640a190c2da5951f..0cc0e1ab78130b1a596460ae3c07a51f59a23895 100644 --- a/build/tests/tests.vcxproj +++ b/build/tests/tests.vcxproj @@ -42,6 +42,7 @@ <ClCompile Include="$(SrcDir)tests\cajun.cpp" /> <ClCompile Include="$(SrcDir)tests\color.cpp" /> <ClCompile Include="$(SrcDir)tests\dialogue_lexer.cpp" /> + <ClCompile Include="$(SrcDir)tests\format.cpp" /> <ClCompile Include="$(SrcDir)tests\fs.cpp" /> <ClCompile Include="$(SrcDir)tests\hotkey.cpp" /> <ClCompile Include="$(SrcDir)tests\iconv.cpp" /> diff --git a/libaegisub/include/libaegisub/format.h b/libaegisub/include/libaegisub/format.h index bda534f2aac570cf9c6eaabe47352ca760440179..17781ae4cf58f6ce005b2631f60844948d4035ea 100644 --- a/libaegisub/include/libaegisub/format.h +++ b/libaegisub/include/libaegisub/format.h @@ -14,11 +14,15 @@ // // Aegisub Project http://www.aegisub.org/ +#include <libaegisub/fs_fwd.h> + #include <boost/interprocess/streams/vectorstream.hpp> #include <boost/io/ios_state.hpp> -#include <cassert> +#include <codecvt> #include <type_traits> +class wxString; + namespace agi { namespace format_detail { // A static cast which throws at runtime if the cast is invalid rather than // failing to compile, as with format strings we don't know what type to cast @@ -40,36 +44,75 @@ Out runtime_cast(In const& value) { return runtime_cast_helper<In, Out>::cast(value); } -template<typename T> -void write_string(std::ostream& out, int, T const& value) { - out << value; -} - // Check length for string types -inline void write_string(std::ostream& out, int max_len, const char *value) { - if (max_len <= 0) - out << value; - else { - std::streamsize len = 0; - for (; len < max_len && value[len]; ++len) ; - out.write(value, len); - } +template<typename Char> +int actual_len(int max_len, const Char *value) { + int len = 0; + while (value[len] && (max_len <= 0 || len < max_len)) ++len; + return len; } -inline void write_string(std::ostream& out, int max_len, std::string const& value) { +template<typename Char> +int actual_len(int max_len, std::basic_string<Char> value) { if (max_len > 0 && static_cast<size_t>(max_len) < value.size()) - out.write(value.data(), max_len); - else - out << value; + return max_len; + return value.size(); +} + +template<typename Char> +inline void do_write_str(std::basic_ostream<Char>& out, const Char *str, int len) { + out.write(str, len); +} + +inline void do_write_str(std::ostream& out, const wchar_t *str, int len) { + std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv; + auto u8 = utf8_conv.to_bytes(str, str + len); + out.write(u8.data(), u8.size()); +} + +inline void do_write_str(std::wostream& out, const char *str, int len) { + std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv; + auto wc = utf8_conv.from_bytes(str, str + len); + out.write(wc.data(), wc.size()); } +} + +template<typename Char, typename T> +struct writer { + static void write(std::basic_ostream<Char>& out, int, T const& value) { + out << value; + } +}; + +// Ensure things with specializations don't get implicitly initialized +template<> struct writer<char, agi::fs::path>; +template<> struct writer<wchar_t, agi::fs::path>; +template<> struct writer<char, wxString>; +template<> struct writer<wchar_t, wxString>; + +template<typename StreamChar, typename Char> +struct writer<StreamChar, const Char *> { + static void write(std::basic_ostream<StreamChar>& out, int max_len, const Char *value) { + format_detail::do_write_str(out, value, format_detail::actual_len(max_len, value)); + } +}; + +template<typename StreamChar, typename Char> +struct writer<StreamChar, std::basic_string<Char>> { + static void write(std::basic_ostream<StreamChar>& out, int max_len, std::basic_string<Char> const& value) { + format_detail::do_write_str(out, value.data(), format_detail::actual_len(max_len, value)); + } +}; +namespace format_detail { +template<typename Char> class formatter { formatter(const formatter&) = delete; formatter& operator=(const formatter&) = delete; - std::ostream& out; - const char *fmt; - const char *fmt_cur = nullptr; + std::basic_ostream<Char>& out; + const Char *fmt; + const Char *fmt_cur = nullptr; bool read_width = false; bool read_precision = false; @@ -78,7 +121,7 @@ class formatter { int width = 0; int precision = 0; - boost::io::ios_all_saver saver; + boost::io::basic_ios_all_saver<Char> saver; void read_and_append_up_to_next_specifier() { for (std::streamsize len = 0; ; ++len) { @@ -168,7 +211,7 @@ class formatter { void parse_length_modifiers() { // Where "parse" means "skip" since we don't need them - for (char c = *fmt_cur; + for (Char c = *fmt_cur; c == 'l' || c == 'h' || c == 'L' || c == 'j' || c == 'z' || c == 't'; c = *++fmt_cur); } @@ -198,7 +241,7 @@ class formatter { } public: - formatter(std::ostream& out, const char *fmt) : out(out), fmt(fmt), saver(out) { } + formatter(std::basic_ostream<Char>& out, const Char *fmt) : out(out), fmt(fmt), saver(out) { } ~formatter() { // Write remaining formatting string for (std::streamsize len = 0; ; ++len) { @@ -245,7 +288,7 @@ public: out.width(width); out.precision(precision < 0 ? 6 : precision); - char c = *fmt_cur ? fmt_cur[0] : 's'; + Char c = *fmt_cur ? fmt_cur[0] : 's'; if (c >= 'A' && c <= 'Z') { out.setf(std::ios::uppercase); c += 'a' - 'A'; @@ -254,7 +297,7 @@ public: switch (c) { case 'c': out.setf(std::ios::dec, std::ios::basefield); - out << runtime_cast<char>(value); + out << runtime_cast<Char>(value); break; case 'd': case 'i': out.setf(std::ios::dec, std::ios::basefield); @@ -292,7 +335,7 @@ public: break; default: // s and other out.setf(std::ios::boolalpha); - write_string(out, precision, value); + writer<Char, typename std::decay<T>::type>::write(out, precision, value); break; } @@ -301,23 +344,24 @@ public: }; // Base case for variadic template recursion -inline void format(formatter&&) { } +template<typename Char> +inline void format(formatter<Char>&&) { } -template<typename T, typename... Args> -void format(formatter&& fmt, T&& first, Args&&... rest) { +template<typename Char, typename T, typename... Args> +void format(formatter<Char>&& fmt, T&& first, Args&&... rest) { fmt(first); format(std::move(fmt), std::forward<Args>(rest)...); } } // namespace format_detail -template<typename... Args> -void format(std::ostream& out, const char *fmt, Args&&... args) { - format(format_detail::formatter(out, fmt), std::forward<Args>(args)...); +template<typename Char, typename... Args> +void format(std::basic_ostream<Char>& out, const Char *fmt, Args&&... args) { + format(format_detail::formatter<Char>(out, fmt), std::forward<Args>(args)...); } -template<typename... Args> -std::string format(const char *fmt, Args&&... args) { - boost::interprocess::basic_vectorstream<std::string> out; +template<typename Char, typename... Args> +std::basic_string<Char> format(const Char *fmt, Args&&... args) { + boost::interprocess::basic_vectorstream<std::basic_string<Char>> out; format(out, fmt, std::forward<Args>(args)...); return out.vector(); } diff --git a/libaegisub/include/libaegisub/format_flyweight.h b/libaegisub/include/libaegisub/format_flyweight.h new file mode 100644 index 0000000000000000000000000000000000000000..aa07061272fee2ab4c53ebf3a68a676b7630144f --- /dev/null +++ b/libaegisub/include/libaegisub/format_flyweight.h @@ -0,0 +1,36 @@ +// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org> +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +#include <libaegisub/fs_fwd.h> + +#include <boost/flyweight.hpp> + +namespace agi { +template<> +struct writer<char, boost::flyweight<std::string>> { + static void write(std::basic_ostream<char>& out, int max_len, boost::flyweight<std::string> const& value) { + writer<char, std::string>::write(out, max_len, value.get()); + } +}; + +template<> +struct writer<wchar_t, boost::flyweight<std::string>> { + static void write(std::basic_ostream<wchar_t>& out, int max_len, boost::flyweight<std::string> const& value) { + writer<wchar_t, std::string>::write(out, max_len, value.get()); + } +}; +} + diff --git a/libaegisub/include/libaegisub/format_path.h b/libaegisub/include/libaegisub/format_path.h new file mode 100644 index 0000000000000000000000000000000000000000..ff195ece6de4784d351f5c4d65138191fb282cbe --- /dev/null +++ b/libaegisub/include/libaegisub/format_path.h @@ -0,0 +1,36 @@ +// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org> +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +#include <libaegisub/fs_fwd.h> + +#include <boost/filesystem/path.hpp> + +namespace agi { +// Default version quotes the path +template<> +struct writer<char, agi::fs::path> { + static void write(std::basic_ostream<char>& out, int max_len, agi::fs::path const& value) { + out << value.string(); + } +}; + +template<> +struct writer<wchar_t, agi::fs::path> { + static void write(std::basic_ostream<wchar_t>& out, int max_len, agi::fs::path const& value) { + out << value.wstring(); + } +}; +} diff --git a/po/make_pot.sh b/po/make_pot.sh index 58e77b035dad80ee6e0f6eab8d9e306410202726..d19079ad79b2c6579bd047e53419514f7edf2fdb 100755 --- a/po/make_pot.sh +++ b/po/make_pot.sh @@ -13,7 +13,7 @@ maybe_append() { } find ../src ../src/command -name \*.cpp -o -name \*.h \ - | xgettext --files-from=- -o - --c++ -k_ -kSTR_MENU -kSTR_DISP -kSTR_HELP -kwxT -kwxPLURAL:1,2 \ + | xgettext --files-from=- -o - --c++ -k_ -kSTR_MENU -kSTR_DISP -kSTR_HELP -kfmt_tl -kfmt_plural:2,3 -kwxT -kwxPLURAL:1,2 \ | sed 's/SOME DESCRIPTIVE TITLE./Aegisub 3.2/' \ | sed 's/YEAR/2005-2014/' \ | sed "s/THE PACKAGE'S COPYRIGHT HOLDER/Rodrigo Braz Monteiro, Niels Martin Hansen, Thomas Goyne et. al./" \ diff --git a/src/agi_pre.h b/src/agi_pre.h index 89e2827ba87b80c1a3bc9aca62fc47ee8472f642..b31970552d08ca95e9216ce2c44abbed4e2433bf 100644 --- a/src/agi_pre.h +++ b/src/agi_pre.h @@ -72,6 +72,7 @@ // Common C++ #include <algorithm> #include <array> +#include <codecvt> #include <functional> #include <iterator> #include <limits> @@ -81,9 +82,10 @@ #include <set> #include <string> #include <type_traits> +#include <type_traits> #include <typeinfo> -#include <utility> #include <unordered_map> +#include <utility> #include <vector> #ifdef _MSC_VER @@ -100,6 +102,7 @@ // Boost #include <boost/container/list.hpp> #include <boost/flyweight.hpp> +#include <boost/io/ios_state.hpp> #include <boost/range/adaptor/filtered.hpp> #include <boost/range/adaptor/transformed.hpp> #include <boost/range/algorithm.hpp> @@ -109,6 +112,7 @@ #include <boost/filesystem/path.hpp> #undef BOOST_NO_SCOPED_ENUMS #include <boost/interprocess/streams/bufferstream.hpp> +#include <boost/interprocess/streams/vectorstream.hpp> // wxWidgets headers #include <wx/defs.h> // Leave this first. diff --git a/src/audio_display.cpp b/src/audio_display.cpp index 8f086dc75b7f731b3c1f703e4cdad5cb5786e828..5c9135143837640e4764d3aeea67f73d7bc164bb 100644 --- a/src/audio_display.cpp +++ b/src/audio_display.cpp @@ -37,6 +37,7 @@ #include "audio_renderer_waveform.h" #include "audio_timing.h" #include "compat.h" +#include "format.h" #include "include/aegisub/audio_provider.h" #include "include/aegisub/context.h" #include "include/aegisub/hotkey.h" @@ -417,21 +418,21 @@ public: if (changed_hour) { - time_string = wxString::Format("%d:%02d:", mark_hour, mark_minute); + time_string = fmt_wx("%d:%02d:", mark_hour, mark_minute); last_hour = mark_hour; last_minute = mark_minute; } else if (changed_minute) { - time_string = wxString::Format("%d:", mark_minute); + time_string = fmt_wx("%d:", mark_minute); last_minute = mark_minute; } if (scale_minor >= Sc_Decisecond) - time_string += wxString::Format("%02d", (int)mark_second); + time_string += fmt_wx("%02d", mark_second); else if (scale_minor == Sc_Centisecond) - time_string += wxString::Format("%02.1f", mark_second); + time_string += fmt_wx("%02.1f", mark_second); else - time_string += wxString::Format("%02.2f", mark_second); + time_string += fmt_wx("%02.2f", mark_second); int tw, th; dc.GetTextExtent(time_string, &tw, &th); @@ -673,7 +674,7 @@ wxString AudioDisplay::GetZoomLevelDescription(int level) const const int base_pixels_per_second = 50; /// @todo Make this customisable along with the above const int second_pixels = 100 * base_pixels_per_second / factor; - return wxString::Format(_("%d%%, %d pixel/second"), factor, second_pixels); + return fmt_tl("%d%%, %d pixel/second", factor, second_pixels); } int AudioDisplay::GetZoomLevelFactor(int level) diff --git a/src/command/command.cpp b/src/command/command.cpp index 2feaaf60e1f8874ea90d411ad8669a0413f46ed6..edc041b6a25ea62341de8b7e9a7de1f6fd1a8b9e 100644 --- a/src/command/command.cpp +++ b/src/command/command.cpp @@ -15,6 +15,7 @@ #include "command.h" #include "../compat.h" +#include "../format.h" #include <libaegisub/log.h> @@ -27,7 +28,7 @@ namespace cmd { static iterator find_command(std::string const& name) { auto it = cmd_map.find(name); if (it == cmd_map.end()) - throw CommandNotFound(from_wx(wxString::Format(_("'%s' is not a valid command name"), to_wx(name)))); + throw CommandNotFound(agi::format(_("'%s' is not a valid command name"), name)); return it; } diff --git a/src/command/edit.cpp b/src/command/edit.cpp index 196cc79d35892aab003606d5732cc0febec2841f..f6a0a1e9e6d304a1c732fb6978694d66b831419d 100644 --- a/src/command/edit.cpp +++ b/src/command/edit.cpp @@ -38,6 +38,7 @@ #include "../compat.h" #include "../dialog_search_replace.h" #include "../dialogs.h" +#include "../format.h" #include "../include/aegisub/context.h" #include "../initial_line_state.h" #include "../libresrc/libresrc.h" @@ -50,7 +51,6 @@ #include "../video_controller.h" #include <libaegisub/address_of_adaptor.h> -#include <libaegisub/format.h> #include <libaegisub/of_type_adaptor.h> #include <libaegisub/make_unique.h> @@ -1146,12 +1146,12 @@ struct edit_redo final : public Command { wxString StrMenu(const agi::Context *c) const override { return c->subsController->IsRedoStackEmpty() ? _("Nothing to &redo") : - wxString::Format(_("&Redo %s"), c->subsController->GetRedoDescription()); + fmt_tl("&Redo %s", c->subsController->GetRedoDescription()); } wxString StrDisplay(const agi::Context *c) const override { return c->subsController->IsRedoStackEmpty() ? _("Nothing to redo") : - wxString::Format(_("Redo %s"), c->subsController->GetRedoDescription()); + fmt_tl("Redo %s", c->subsController->GetRedoDescription()); } bool Validate(const agi::Context *c) override { @@ -1172,12 +1172,12 @@ struct edit_undo final : public Command { wxString StrMenu(const agi::Context *c) const override { return c->subsController->IsUndoStackEmpty() ? _("Nothing to &undo") : - wxString::Format(_("&Undo %s"), c->subsController->GetUndoDescription()); + fmt_tl("&Undo %s", c->subsController->GetUndoDescription()); } wxString StrDisplay(const agi::Context *c) const override { return c->subsController->IsUndoStackEmpty() ? _("Nothing to undo") : - wxString::Format(_("Undo %s"), c->subsController->GetUndoDescription()); + fmt_tl("Undo %s", c->subsController->GetUndoDescription()); } bool Validate(const agi::Context *c) override { diff --git a/src/command/video.cpp b/src/command/video.cpp index 115aa6c86bbbecbc07d2db1c2751252b906f83c3..84dbb24e3111621e93b382d92c3409ffd4460226 100644 --- a/src/command/video.cpp +++ b/src/command/video.cpp @@ -38,6 +38,7 @@ #include "../dialog_detached_video.h" #include "../dialog_manager.h" #include "../dialogs.h" +#include "../format.h" #include "../frame_main.h" #include "../include/aegisub/context.h" #include "../include/aegisub/subtitles_provider.h" @@ -50,7 +51,6 @@ #include "../video_display.h" #include "../video_frame.h" -#include <libaegisub/format.h> #include <libaegisub/fs.h> #include <libaegisub/path.h> #include <libaegisub/make_unique.h> @@ -232,7 +232,7 @@ struct video_cycle_subtitles_provider final : public cmd::Command { if (it == end(providers)) it = begin(providers); OPT_SET("Subtitle/Provider")->SetString(*it); - c->frame->StatusTimeout(wxString::Format(_("Subtitles provider set to %s"), to_wx(*it)), 5000); + c->frame->StatusTimeout(fmt_tl("Subtitles provider set to %s", *it), 5000); } }; diff --git a/src/dialog_about.cpp b/src/dialog_about.cpp index 75d51f0cf1175d14c7f99d22d2a788a9106863b4..d56de11485b04abcdf54e022e7391c984b09a6ae 100644 --- a/src/dialog_about.cpp +++ b/src/dialog_about.cpp @@ -28,6 +28,7 @@ // Aegisub Project http://www.aegisub.org/ #include "libresrc/libresrc.h" +#include "format.h" #include "version.h" #include <wx/button.h> @@ -126,7 +127,7 @@ struct AboutScreen : wxDialog { aboutString += translatorCredit; aboutString += "\n" + libString; aboutString += _("\nSee the help file for full credits.\n"); - aboutString += wxString::Format(_("Built by %s on %s."), GetAegisubBuildCredit(), GetAegisubBuildTime()); + aboutString += fmt_tl("Built by %s on %s.", GetAegisubBuildCredit(), GetAegisubBuildTime()); // Replace copyright symbol wxChar copySymbol = 0xA9; diff --git a/src/dialog_automation.cpp b/src/dialog_automation.cpp index 69ef63033dd925184656cca813e251f4c0d3315d..dd53db7b4255f06a0764b39122c6ac918f211f78 100644 --- a/src/dialog_automation.cpp +++ b/src/dialog_automation.cpp @@ -31,6 +31,7 @@ #include "compat.h" #include "command/command.h" #include "dialog_manager.h" +#include "format.h" #include "help_button.h" #include "include/aegisub/context.h" #include "libresrc/libresrc.h" @@ -273,20 +274,20 @@ void DialogAutomation::OnInfo(wxCommandEvent &) wxArrayString info; std::back_insert_iterator<wxArrayString> append_info(info); - info.push_back(wxString::Format( - _("Total scripts loaded: %d\nGlobal scripts loaded: %d\nLocal scripts loaded: %d\n"), - (int)local_manager->GetScripts().size() + (int)global_manager->GetScripts().size(), - (int)global_manager->GetScripts().size(), - (int)local_manager->GetScripts().size())); + info.push_back(fmt_tl( + "Total scripts loaded: %d\nGlobal scripts loaded: %d\nLocal scripts loaded: %d\n", + local_manager->GetScripts().size() + global_manager->GetScripts().size(), + global_manager->GetScripts().size(), + local_manager->GetScripts().size())); info.push_back(_("Scripting engines installed:")); boost::transform(Automation4::ScriptFactory::GetFactories(), append_info, [](std::unique_ptr<Automation4::ScriptFactory> const& f) { - return wxString::Format("- %s (%s)", to_wx(f->GetEngineName()), to_wx(f->GetFilenamePattern())); + return fmt_wx("- %s (%s)", f->GetEngineName(), f->GetFilenamePattern()); }); if (ei) { - info.push_back(wxString::Format(_("\nScript info:\nName: %s\nDescription: %s\nAuthor: %s\nVersion: %s\nFull path: %s\nState: %s\n\nFeatures provided by script:"), + info.push_back(fmt_tl("\nScript info:\nName: %s\nDescription: %s\nAuthor: %s\nVersion: %s\nFull path: %s\nState: %s\n\nFeatures provided by script:", ei->script->GetName(), ei->script->GetDescription(), ei->script->GetAuthor(), @@ -295,10 +296,10 @@ void DialogAutomation::OnInfo(wxCommandEvent &) ei->script->GetLoadedState() ? _("Correctly loaded") : _("Failed to load"))); boost::transform(ei->script->GetMacros(), append_info, [=](const cmd::Command *f) { - return wxString::Format(_(" Macro: %s (%s)"), f->StrDisplay(context), to_wx(f->name())); + return fmt_tl(" Macro: %s (%s)", f->StrDisplay(context), f->name()); }); boost::transform(ei->script->GetFilters(), append_info, [](const Automation4::ExportFilter* f) { - return wxString::Format(_(" Export filter: %s"), to_wx(f->GetName())); + return fmt_tl(" Export filter: %s", f->GetName()); }); } diff --git a/src/dialog_autosave.cpp b/src/dialog_autosave.cpp index b06c94c0bf9d367ccfc0490ebd18a0ae2f664a96..73981005f6a048b6aa318a331c3550428a1c53f9 100644 --- a/src/dialog_autosave.cpp +++ b/src/dialog_autosave.cpp @@ -15,6 +15,7 @@ // Aegisub Project http://www.aegisub.org/ #include "compat.h" +#include "format.h" #include "libresrc/libresrc.h" #include "options.h" @@ -140,7 +141,7 @@ void DialogAutosave::Populate(std::map<wxString, AutosaveFile> &files_map, std:: auto it = files_map.find(name); if (it == files_map.end()) it = files_map.insert({name, AutosaveFile{name}}).first; - it->second.versions.push_back(Version{wxFileName(directory, fn).GetFullPath(), date, wxString::Format(name_fmt, date.Format())}); + it->second.versions.push_back(Version{wxFileName(directory, fn).GetFullPath(), date, agi::wxformat(name_fmt, date.Format())}); } while (dir.GetNext(&fn)); } diff --git a/src/dialog_detached_video.cpp b/src/dialog_detached_video.cpp index 727eca4fd59a9765b2b45a5ad76b67f981963a2a..80995a8caff2285c9cadda8371e9fa7d2155c614 100644 --- a/src/dialog_detached_video.cpp +++ b/src/dialog_detached_video.cpp @@ -34,6 +34,7 @@ #include "dialog_detached_video.h" +#include "format.h" #include "include/aegisub/context.h" #include "include/aegisub/hotkey.h" #include "options.h" @@ -44,6 +45,7 @@ #include "video_controller.h" #include "video_display.h" +#include <libaegisub/format_path.h> #include <libaegisub/make_unique.h> #include <boost/filesystem/path.hpp> @@ -61,7 +63,7 @@ DialogDetachedVideo::DialogDetachedVideo(agi::Context *context) // Set obscure stuff SetExtraStyle((GetExtraStyle() & ~wxWS_EX_BLOCK_EVENTS) | wxWS_EX_PROCESS_UI_UPDATES); - SetTitle(wxString::Format(_("Video: %s"), context->project->VideoName().filename().wstring())); + SetTitle(fmt_tl("Video: %s", context->project->VideoName().filename())); old_display->Unload(); @@ -129,7 +131,7 @@ void DialogDetachedVideo::OnKeyDown(wxKeyEvent &evt) { void DialogDetachedVideo::OnVideoOpen() { if (context->project->VideoProvider()) - SetTitle(wxString::Format(_("Video: %s"), context->project->VideoName().filename().wstring())); + SetTitle(fmt_tl("Video: %s", context->project->VideoName().filename())); else { Close(); OPT_SET("Video/Detached/Enabled")->SetBool(true); diff --git a/src/dialog_dummy_video.cpp b/src/dialog_dummy_video.cpp index 85e5aed898b61de53a319130844fd5f0a1c111d3..d46e6f53d996f0e35fba8f9af89f01989b65e39a 100644 --- a/src/dialog_dummy_video.cpp +++ b/src/dialog_dummy_video.cpp @@ -16,6 +16,7 @@ #include "ass_time.h" #include "colour_button.h" +#include "format.h" #include "help_button.h" #include "libresrc/libresrc.h" #include "options.h" @@ -158,7 +159,7 @@ void DialogDummyVideo::OnResolutionShortcut(wxCommandEvent &e) { } void DialogDummyVideo::UpdateLengthDisplay() { - length_display->SetLabel(wxString::Format(_("Resulting duration: %s"), AssTime(length / fps * 1000).GetAssFormated(true))); + length_display->SetLabel(fmt_tl("Resulting duration: %s", AssTime(length / fps * 1000).GetAssFormated(true))); } } diff --git a/src/dialog_export_ebu3264.cpp b/src/dialog_export_ebu3264.cpp index f2eb54c784c5a0459c7c8ea9cbdbeb7671ef6719..9216416bd42da5bfc542484a1dfb339cbc290500 100644 --- a/src/dialog_export_ebu3264.cpp +++ b/src/dialog_export_ebu3264.cpp @@ -22,6 +22,7 @@ #include "dialog_export_ebu3264.h" #include "compat.h" +#include "format.h" #include "options.h" #include <libaegisub/charset_conv.h> @@ -53,7 +54,7 @@ namespace { bool TransferToWindow() override { wxTextCtrl *ctrl = GetCtrl(); if (!ctrl) return false; - ctrl->SetValue(wxString::Format("%02d:%02d:%02d:%02d", (int)value->h, (int)value->m, (int)value->s, (int)value->f)); + ctrl->SetValue(fmt_wx("%02d:%02d:%02d:%02d", value->h, value->m, value->s, value->f)); return true; } diff --git a/src/dialog_fonts_collector.cpp b/src/dialog_fonts_collector.cpp index d37d8945a7903ec15f040c16fb05834129eec8b9..86938a7ee2984eabc9d29da3bb7866c0c3e6a34e 100644 --- a/src/dialog_fonts_collector.cpp +++ b/src/dialog_fonts_collector.cpp @@ -19,6 +19,7 @@ #include "compat.h" #include "dialog_manager.h" +#include "format.h" #include "help_button.h" #include "include/aegisub/context.h" #include "libresrc/libresrc.h" @@ -27,6 +28,7 @@ #include "utils.h" #include <libaegisub/dispatch.h> +#include <libaegisub/format_path.h> #include <libaegisub/fs.h> #include <libaegisub/path.h> #include <libaegisub/make_unique.h> @@ -122,7 +124,7 @@ void FontsCollectorThread(AssFile *subs, agi::fs::path const& destination, FcMod agi::fs::CreateDirectory(destination.parent_path()); } catch (agi::fs::FileSystemError const& e) { - AppendText(wxString::Format(_("* Failed to create directory '%s': %s.\n"), + AppendText(fmt_tl("* Failed to create directory '%s': %s.\n", destination.parent_path().wstring(), to_wx(e.GetMessage())), 2); collector->AddPendingEvent(wxThreadEvent(EVT_COLLECTION_DONE)); return; @@ -133,7 +135,7 @@ void FontsCollectorThread(AssFile *subs, agi::fs::path const& destination, FcMod zip = agi::make_unique<wxZipOutputStream>(*out); if (!out->IsOk() || !zip || !zip->IsOk()) { - AppendText(wxString::Format(_("* Failed to open %s.\n"), destination.wstring()), 2); + AppendText(fmt_tl("* Failed to open %s.\n", destination), 2); collector->AddPendingEvent(wxThreadEvent(EVT_COLLECTION_DONE)); return; } @@ -188,13 +190,13 @@ void FontsCollectorThread(AssFile *subs, agi::fs::path const& destination, FcMod } if (ret == 1) - AppendText(wxString::Format(_("* Copied %s.\n"), path.wstring()), 1); + AppendText(fmt_tl("* Copied %s.\n", path), 1); else if (ret == 2) - AppendText(wxString::Format(_("* %s already exists on destination.\n"), path.filename().wstring()), 3); + AppendText(fmt_tl("* %s already exists on destination.\n", path.filename()), 3); else if (ret == 3) - AppendText(wxString::Format(_("* Symlinked %s.\n"), path.wstring()), 1); + AppendText(fmt_tl("* Symlinked %s.\n", path), 1); else { - AppendText(wxString::Format(_("* Failed to copy %s.\n"), path.wstring()), 2); + AppendText(fmt_tl("* Failed to copy %s.\n", path), 2); allOk = false; } } diff --git a/src/dialog_jumpto.cpp b/src/dialog_jumpto.cpp index 1c5fce2b8bab2e445eccfd30a2688518d5aed6ac..ec7919aaf42b6a67d4a59cff22ee136456600f3d 100644 --- a/src/dialog_jumpto.cpp +++ b/src/dialog_jumpto.cpp @@ -29,6 +29,7 @@ #include "ass_time.h" #include "async_video_provider.h" +#include "format.h" #include "include/aegisub/context.h" #include "libresrc/libresrc.h" #include "project.h" @@ -117,7 +118,7 @@ void DialogJumpTo::OnEditTime (wxCommandEvent &) { long newframe = c->videoController->FrameAtTime(JumpTime->GetTime()); if (jumpframe != newframe) { jumpframe = newframe; - JumpFrame->ChangeValue(wxString::Format("%li", jumpframe)); + JumpFrame->ChangeValue(fmt_wx("%d", jumpframe)); } } diff --git a/src/dialog_log.cpp b/src/dialog_log.cpp index 89489653d52fadcbc611262374476d06778537be..b7de8892d9c51d2545f72213cbb1286006338aea 100644 --- a/src/dialog_log.cpp +++ b/src/dialog_log.cpp @@ -29,6 +29,7 @@ #include "compat.h" #include "dialog_manager.h" +#include "format.h" #include "include/aegisub/context.h" #include <libaegisub/dispatch.h> @@ -57,26 +58,26 @@ public: #ifndef _WIN32 tm tmtime; localtime_r(&time, &tmtime); - auto log = wxString::Format("%c %02d:%02d:%02d %-6ld <%-25s> [%s:%s:%d] %s\n", + auto log = fmt_wx("%c %02d:%02d:%02d %-6d <%-25s> [%s:%s:%d] %s\n", agi::log::Severity_ID[sm.severity], - (int)tmtime.tm_hour, - (int)tmtime.tm_min, - (int)tmtime.tm_sec, - (long)(sm.time % 1000000000), + tmtime.tm_hour, + tmtime.tm_min, + tmtime.tm_sec, + (sm.time % 1000000000), sm.section, sm.file, sm.func, sm.line, - to_wx(sm.message)); + sm.message); #else - auto log = wxString::Format("%c %-6ld <%-25s> [%s:%s:%d] %s\n", + auto log = fmt_wx("%c %-6ld <%-25s> [%s:%s:%d] %s\n", agi::log::Severity_ID[sm.severity], - (long)(sm.time % 1000000000), + (sm.time % 1000000000), sm.section, sm.file, sm.func, sm.line, - to_wx(sm.message)); + sm.message); #endif if (wxIsMainThread()) diff --git a/src/dialog_selection.cpp b/src/dialog_selection.cpp index 260ddde842a8289854cd701e1c7321aef8d161e2..2adfa764cd7bba99dea901ac43433c81a849fa4d 100644 --- a/src/dialog_selection.cpp +++ b/src/dialog_selection.cpp @@ -18,6 +18,7 @@ #include "ass_file.h" #include "compat.h" #include "dialog_manager.h" +#include "format.h" #include "frame_main.h" #include "help_button.h" #include "include/aegisub/context.h" @@ -207,14 +208,14 @@ void DialogSelection::Process(wxCommandEvent&) { case Action::SET: new_sel = std::move(matches); message = (count = new_sel.size()) - ? wxString::Format(wxPLURAL("Selection was set to one line", "Selection was set to %u lines", count), (unsigned)count) + ? fmt_plural(count, "Selection was set to one line", "Selection was set to %u lines", count) : _("Selection was set to no lines"); break; case Action::ADD: boost::set_union(old_sel, matches, inserter(new_sel, new_sel.begin())); message = (count = new_sel.size() - old_sel.size()) - ? wxString::Format(wxPLURAL("One line was added to selection", "%u lines were added to selection", count), (unsigned)count) + ? fmt_plural(count, "One line was added to selection", "%u lines were added to selection", count) : _("No lines were added to selection"); break; @@ -226,7 +227,7 @@ void DialogSelection::Process(wxCommandEvent&) { boost::set_intersection(old_sel, matches, inserter(new_sel, new_sel.begin())); sub_message: message = (count = old_sel.size() - new_sel.size()) - ? wxString::Format(wxPLURAL("One line was removed from selection", "%u lines were removed from selection", count), (unsigned)count) + ? fmt_plural(count, "One line was removed from selection", "%u lines were removed from selection", count) : _("No lines were removed from selection"); break; } diff --git a/src/dialog_shift_times.cpp b/src/dialog_shift_times.cpp index 717428b7e761c67f50f0c160a0ddd2d8bb2a0819..c6ade8b1fa7c76b2541301beca84f0c82dc141d0 100644 --- a/src/dialog_shift_times.cpp +++ b/src/dialog_shift_times.cpp @@ -19,6 +19,7 @@ #include "ass_time.h" #include "compat.h" #include "dialog_manager.h" +#include "format.h" #include "include/aegisub/context.h" #include "help_button.h" #include "libresrc/libresrc.h" @@ -92,7 +93,7 @@ static wxString get_history_string(json::Object &obj) { wxString shift_amount(to_wx(obj["amount"])); if (!obj["is by time"]) - shift_amount = wxString::Format(_("%s frames"), shift_amount); + shift_amount = fmt_tl("%s frames", shift_amount); wxString shift_direction = obj["is backward"] ? _("backward") : _("forward"); @@ -109,7 +110,7 @@ static wxString get_history_string(json::Object &obj) { if (sel_mode == 0) lines = _("all"); else if (sel_mode == 2) - lines = wxString::Format(_("from %d onward"), (int)(int64_t)sel.front()["start"]); + lines = fmt_tl("from %d onward", (int64_t)sel.front()["start"]); else { lines += _("sel "); for (auto it = sel.begin(); it != sel.end(); ++it) { @@ -118,13 +119,13 @@ static wxString get_history_string(json::Object &obj) { if (beg == end) lines += std::to_wstring(beg); else - lines += wxString::Format("%d-%d", beg, end); + lines += fmt_wx("%d-%d", beg, end); if (it + 1 != sel.end()) lines += ";"; } } - return wxString::Format("%s, %s %s, %s, %s", filename, shift_amount, shift_direction, fields, lines); + return fmt_wx("%s, %s %s, %s, %s", filename, shift_amount, shift_direction, fields, lines); } DialogShiftTimes::DialogShiftTimes(agi::Context *context) diff --git a/src/dialog_style_editor.cpp b/src/dialog_style_editor.cpp index a1b947b1121bfdb3550f2ad0eddee4b8b42255fa..307565dd41c1af691b98a69fcff3e0a65e2362be 100644 --- a/src/dialog_style_editor.cpp +++ b/src/dialog_style_editor.cpp @@ -149,7 +149,7 @@ DialogStyleEditor::DialogStyleEditor(wxWindow *parent, AssStyle *style, agi::Con }; auto spin_ctrl = [&](float value, int max_value) { - return new wxSpinCtrl(this, -1, wxString::Format("%g", value), wxDefaultPosition, wxSize(60, -1), wxSP_ARROW_KEYS, 0, max_value, value); + return new wxSpinCtrl(this, -1, std::to_wstring(value), wxDefaultPosition, wxSize(60, -1), wxSP_ARROW_KEYS, 0, max_value, value); }; auto num_text_ctrl = [&](double *value, double min, double max, double step) -> wxSpinCtrlDouble * { diff --git a/src/dialog_style_manager.cpp b/src/dialog_style_manager.cpp index 07bf4f268558a463a87b0b5c5798191f88382089..6231b4c4f4338c6de2c8f68e6b5e5cd7b6f02f5b 100644 --- a/src/dialog_style_manager.cpp +++ b/src/dialog_style_manager.cpp @@ -36,8 +36,9 @@ #include "dialog_manager.h" #include "dialog_style_editor.h" #include "dialogs.h" -#include "include/aegisub/context.h" +#include "format.h" #include "help_button.h" +#include "include/aegisub/context.h" #include "libresrc/libresrc.h" #include "options.h" #include "persist_location.h" @@ -214,9 +215,9 @@ wxSizer *make_edit_buttons(wxWindow *parent, wxString move_label, wxButton **mov template<class Func> std::string unique_name(Func name_checker, std::string const& source_name) { if (name_checker(source_name)) { - std::string name = from_wx(wxString::Format(_("%s - Copy"), to_wx(source_name))); + std::string name = agi::format(_("%s - Copy"), source_name); for (int i = 2; name_checker(name); ++i) - name = from_wx(wxString::Format(_("%s - Copy (%d)"), to_wx(source_name), i)); + name = agi::format(_("%s - Copy (%d)"), source_name, i); return name; } return source_name; @@ -244,7 +245,7 @@ void add_styles(Func1 name_checker, Func2 style_adder) { int confirm_delete(int n, wxWindow *parent, wxString const& title) { return wxMessageBox( - wxString::Format(wxPLURAL("Are you sure you want to delete this style?", "Are you sure you want to delete these %d styles?", n), n), + fmt_plural(n, "Are you sure you want to delete this style?", "Are you sure you want to delete these %d styles?", n), title, wxYES_NO | wxICON_EXCLAMATION, parent); } @@ -472,7 +473,7 @@ void DialogStyleManager::OnCatalogNew() { // Warn about bad characters if (badchars_removed) { wxMessageBox( - wxString::Format(_("The specified catalog name contains one or more illegal characters. They have been replaced with underscores instead.\nThe catalog has been renamed to \"%s\"."), name), + fmt_tl("The specified catalog name contains one or more illegal characters. They have been replaced with underscores instead.\nThe catalog has been renamed to \"%s\".", name), _("Invalid characters")); } @@ -486,7 +487,7 @@ void DialogStyleManager::OnCatalogDelete() { if (CatalogList->GetCount() == 1) return; wxString name = CatalogList->GetStringSelection(); - wxString message = wxString::Format(_("Are you sure you want to delete the storage \"%s\" from the catalog?"), name); + wxString message = fmt_tl("Are you sure you want to delete the storage \"%s\" from the catalog?", name); int option = wxMessageBox(message, _("Confirm delete"), wxYES_NO | wxICON_EXCLAMATION , this); if (option == wxYES) { agi::fs::Remove(config::path->Decode("?user/catalog/" + from_wx(name) + ".sty")); @@ -505,7 +506,7 @@ void DialogStyleManager::OnCopyToStorage() { wxString styleName = CurrentList->GetString(selections[i]); if (AssStyle *style = Store.GetStyle(from_wx(styleName))) { - if (wxYES == wxMessageBox(wxString::Format(_("There is already a style with the name \"%s\" in the current storage. Overwrite?"),styleName), _("Style name collision"), wxYES_NO)) { + if (wxYES == wxMessageBox(fmt_tl("There is already a style with the name \"%s\" in the current storage. Overwrite?", styleName), _("Style name collision"), wxYES_NO)) { *style = *styleMap.at(selections[i]); copied.push_back(styleName); } @@ -532,7 +533,7 @@ void DialogStyleManager::OnCopyToCurrent() { wxString styleName = StorageList->GetString(selections[i]); if (AssStyle *style = c->ass->GetStyle(from_wx(styleName))) { - if (wxYES == wxMessageBox(wxString::Format(_("There is already a style with the name \"%s\" in the current script. Overwrite?"), styleName), _("Style name collision"), wxYES_NO)) { + if (wxYES == wxMessageBox(fmt_tl("There is already a style with the name \"%s\" in the current script. Overwrite?", styleName), _("Style name collision"), wxYES_NO)) { *style = *Store[selections[i]]; copied.push_back(styleName); } @@ -709,7 +710,7 @@ void DialogStyleManager::OnCurrentImport() { // Check if there is already a style with that name if (AssStyle *existing = c->ass->GetStyle(styles[sel])) { int answer = wxMessageBox( - wxString::Format(_("There is already a style with the name \"%s\" in the current script. Overwrite?"), styles[sel]), + fmt_tl("There is already a style with the name \"%s\" in the current script. Overwrite?", styles[sel]), _("Style name collision"), wxYES_NO); if (answer == wxYES) { diff --git a/src/dialog_timing_processor.cpp b/src/dialog_timing_processor.cpp index 39860d5c0c738ab6906dfc03d104ed7ea7536eeb..cce9ab98e2deb5605472281e822953523d3fd6d0 100644 --- a/src/dialog_timing_processor.cpp +++ b/src/dialog_timing_processor.cpp @@ -32,6 +32,7 @@ #include "ass_time.h" #include "async_video_provider.h" #include "compat.h" +#include "format.h" #include "help_button.h" #include "include/aegisub/context.h" #include "libresrc/libresrc.h" @@ -345,7 +346,7 @@ std::vector<AssDialogue*> DialogTimingProcessor::SortDialogues() { for (auto diag : sorted) { if (diag->Start > diag->End) { wxMessageBox( - wxString::Format(_("One of the lines in the file (%i) has negative duration. Aborting."), diag->Row), + fmt_tl("One of the lines in the file (%i) has negative duration. Aborting.", diag->Row), _("Invalid script"), wxOK | wxICON_ERROR | wxCENTER); sorted.clear(); diff --git a/src/dialog_translation.cpp b/src/dialog_translation.cpp index a78a6ebc40e87366ece3e91c63d8f3b422c1d57a..e8bdf77cf7b07a131cb6ce19efa8f0ab0babdec3 100644 --- a/src/dialog_translation.cpp +++ b/src/dialog_translation.cpp @@ -28,6 +28,7 @@ #include "ass_file.h" #include "command/command.h" #include "compat.h" +#include "format.h" #include "help_button.h" #include "libresrc/libresrc.h" #include "persist_location.h" @@ -181,7 +182,7 @@ void DialogTranslation::OnActiveLineChanged(AssDialogue *new_line) { void DialogTranslation::OnExternalCommit(int commit_type) { if (commit_type == AssFile::COMMIT_NEW || commit_type & AssFile::COMMIT_DIAG_ADDREM) { line_count = c->ass->Events.size(); - line_number_display->SetLabel(wxString::Format(_("Current line: %d/%d"), active_line->Row + 1, (int)line_count)); + line_number_display->SetLabel(fmt_tl("Current line: %d/%d", active_line->Row + 1, line_count)); } if (commit_type & AssFile::COMMIT_DIAG_TEXT) @@ -231,7 +232,7 @@ bool DialogTranslation::PrevBlock() { } void DialogTranslation::UpdateDisplay() { - line_number_display->SetLabel(wxString::Format(_("Current line: %d/%d"), active_line->Row, (int)line_count)); + line_number_display->SetLabel(fmt_tl("Current line: %d/%d", active_line->Row, line_count)); original_text->SetReadOnly(false); original_text->ClearAll(); diff --git a/src/dialog_version_check.cpp b/src/dialog_version_check.cpp index 865d32b2d131905834e23027218d4e7d7c86fae0..b5d95910e30506357d13eb7107746cc08973120f 100644 --- a/src/dialog_version_check.cpp +++ b/src/dialog_version_check.cpp @@ -34,13 +34,13 @@ #endif #include "compat.h" +#include "format.h" #include "options.h" #include "string_codec.h" #include "version.h" #include <libaegisub/dispatch.h> #include <libaegisub/exception.h> -#include <libaegisub/format.h> #include <libaegisub/line_iterator.h> #include <libaegisub/scoped_ptr.h> @@ -255,7 +255,7 @@ static wxString GetSystemLanguage() { wxString res = GetUILanguage(); if (!res) // On an old version of Windows, let's just return the LANGID as a string - res = wxString::Format("x-win%04x", GetUserDefaultUILanguage()); + res = fmt_wx("x-win%04x", GetUserDefaultUILanguage()); return res; } @@ -287,7 +287,7 @@ void DoCheck(bool interactive) { if (!stream) throw VersionCheckError(from_wx(_("Could not connect to updates server."))); - stream << agi::format( + agi::format(stream, "GET %s?rev=%d&rel=%d&os=%s&lang=%s&aegilang=%s HTTP/1.0\r\n" "User-Agent: Aegisub %s\r\n" "Host: %s\r\n" @@ -309,7 +309,7 @@ void DoCheck(bool interactive) { if (!stream || http_version.substr(0, 5) != "HTTP/") throw VersionCheckError(from_wx(_("Could not download from updates server."))); if (status_code != 200) - throw VersionCheckError(from_wx(wxString::Format(_("HTTP request failed, got HTTP response %d."), status_code))); + throw VersionCheckError(agi::format(_("HTTP request failed, got HTTP response %d."), status_code)); stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); @@ -371,8 +371,8 @@ void PerformVersionCheck(bool interactive) { DoCheck(interactive); } catch (const agi::Exception &e) { - PostErrorEvent(interactive, wxString::Format( - _("There was an error checking for updates to Aegisub:\n%s\n\nIf other applications can access the Internet fine, this is probably a temporary server problem on our end."), + PostErrorEvent(interactive, fmt_tl( + "There was an error checking for updates to Aegisub:\n%s\n\nIf other applications can access the Internet fine, this is probably a temporary server problem on our end.", e.GetMessage())); } catch (...) { diff --git a/src/dialog_video_details.cpp b/src/dialog_video_details.cpp index 436ad2d567e0bf20831b93cee38e0e6218faafaf..7ed9c51417412a0d77b9ce7be044158812df5eb0 100644 --- a/src/dialog_video_details.cpp +++ b/src/dialog_video_details.cpp @@ -30,6 +30,7 @@ #include "ass_time.h" #include "async_video_provider.h" #include "compat.h" +#include "format.h" #include "include/aegisub/context.h" #include "project.h" @@ -62,10 +63,10 @@ DialogVideoDetails::DialogVideoDetails(agi::Context *c) fg->Add(new wxTextCtrl(this, -1, value, wxDefaultPosition, wxSize(300,-1), wxTE_READONLY), 0, wxALIGN_CENTRE_VERTICAL | wxEXPAND); }; make_field(_("File name:"), c->project->VideoName().wstring()); - make_field(_("FPS:"), wxString::Format("%.3f", fps.FPS())); - make_field(_("Resolution:"), wxString::Format("%dx%d (%d:%d)", width, height, ar.numerator(), ar.denominator())); - make_field(_("Length:"), wxString::Format(wxPLURAL("1 frame", "%d frames (%s)", framecount), - framecount, to_wx(AssTime(fps.TimeAtFrame(framecount - 1)).GetAssFormated(true)))); + make_field(_("FPS:"), fmt_wx("%.3f", fps.FPS())); + make_field(_("Resolution:"), fmt_wx("%dx%d (%d:%d)", width, height, ar.numerator(), ar.denominator())); + make_field(_("Length:"), fmt_plural(framecount, "1 frame", "%d frames (%s)", + framecount, AssTime(fps.TimeAtFrame(framecount - 1)).GetAssFormated(true))); make_field(_("Decoder:"), to_wx(provider->GetDecoderName())); wxStaticBoxSizer *video_sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Video")); diff --git a/src/dialog_video_properties.cpp b/src/dialog_video_properties.cpp index 00ece34071864d357b394d27ecb68106cc9c6478..3d2dad45bcd5849c6a9191e3ea96d5a69cd2521b 100644 --- a/src/dialog_video_properties.cpp +++ b/src/dialog_video_properties.cpp @@ -16,6 +16,7 @@ #include "ass_file.h" #include "async_video_provider.h" +#include "format.h" #include "options.h" #include "resolution_resampler.h" @@ -43,7 +44,7 @@ public: Prompt(wxWindow *parent, bool ar_changed, int sx, int sy, int vx, int vy) : wxDialog(parent, -1, _("Resolution mismatch")) { - auto label_text = wxString::Format(_("The resolution of the loaded video and the resolution specified for the subtitles don't match.\n\nVideo resolution:\t%d x %d\nScript resolution:\t%d x %d\n\nChange subtitles resolution to match video?"), vx, vy, sx, sy); + auto label_text = fmt_tl("The resolution of the loaded video and the resolution specified for the subtitles don't match.\n\nVideo resolution:\t%d x %d\nScript resolution:\t%d x %d\n\nChange subtitles resolution to match video?", vx, vy, sx, sy); auto sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(new wxStaticText(this, -1, label_text), wxSizerFlags().Border()); diff --git a/src/export_framerate.cpp b/src/export_framerate.cpp index d27b9e4e79c64e533195091f89868ff5f5ac657e..024489f64ccafd19985383b4997d605fdbcab735 100644 --- a/src/export_framerate.cpp +++ b/src/export_framerate.cpp @@ -33,6 +33,7 @@ #include "ass_file.h" #include "async_video_provider.h" #include "compat.h" +#include "format.h" #include "include/aegisub/context.h" #include "project.h" @@ -68,9 +69,9 @@ wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, a wxString initialInput; auto FromVideo = new wxButton(base,-1,_("From &video")); if (Input.IsLoaded()) { - initialInput = wxString::Format("%2.3f", Input.FPS()); + initialInput = fmt_wx("%2.3f", Input.FPS()); FromVideo->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { - InputFramerate->SetValue(wxString::Format("%g", c->project->Timecodes().FPS())); + InputFramerate->SetValue(fmt_wx("%g", c->project->Timecodes().FPS())); }); } else { diff --git a/src/ffmpegsource_common.cpp b/src/ffmpegsource_common.cpp index 9c806dd94a24629dd1f69304bcb2472412ab23f5..1ff0776e99180976718495c8e843f3cee2dd1d5c 100644 --- a/src/ffmpegsource_common.cpp +++ b/src/ffmpegsource_common.cpp @@ -36,6 +36,7 @@ #include "ffmpegsource_common.h" #include "compat.h" +#include "format.h" #include "options.h" #include "utils.h" @@ -141,7 +142,7 @@ int FFmpegSourceProvider::AskForTrackSelection(const std::map<int, std::string> wxArrayString Choices; for (auto const& track : TrackList) { - Choices.Add(wxString::Format(_("Track %02d: %s"), track.first, to_wx(track.second))); + Choices.Add(agi::format(_("Track %02d: %s"), track.first, to_wx(track.second))); TrackNumbers.push_back(track.first); } diff --git a/src/font_file_lister.cpp b/src/font_file_lister.cpp index 0a40a68d01c66046546070e04c273caa6a8eecdc..8c0b6becbf95cf8811dd66b0ce87f27dac18b1e8 100644 --- a/src/font_file_lister.cpp +++ b/src/font_file_lister.cpp @@ -25,6 +25,10 @@ #include "ass_file.h" #include "ass_style.h" #include "compat.h" +#include "format.h" + +#include <libaegisub/format_flyweight.h> +#include <libaegisub/format_path.h> #include <algorithm> #include <tuple> @@ -39,7 +43,7 @@ namespace { if (!u_isUWhiteSpace(c.GetValue())) printable += c; else { - unprintable += wxString::Format("\n - U+%04X ", c.GetValue()); + unprintable += fmt_wx("\n - U+%04X ", c.GetValue()); UErrorCode ec; char buf[1024]; auto len = u_charName(c.GetValue(), U_EXTENDED_CHAR_NAME, buf, sizeof buf, &ec); @@ -65,7 +69,7 @@ void FontCollector::ProcessDialogueLine(const AssDialogue *line, int index) { auto style_it = styles.find(line->Style); if (style_it == end(styles)) { - status_callback(wxString::Format(_("Style '%s' does not exist\n"), to_wx(line->Style)), 2); + status_callback(fmt_tl("Style '%s' does not exist\n", line->Style), 2); ++missing; return; } @@ -131,26 +135,26 @@ void FontCollector::ProcessChunk(std::pair<StyleInfo, UsageData> const& style) { FontFileLister::CollectionResult res = lister.GetFontPaths(style.first.facename, style.first.bold, style.first.italic, style.second.chars); if (res.paths.empty()) { - status_callback(wxString::Format(_("Could not find font '%s'\n"), to_wx(style.first.facename)), 2); + status_callback(fmt_tl("Could not find font '%s'\n", style.first.facename), 2); PrintUsage(style.second); ++missing; } else { for (auto& elem : res.paths) { if (results.insert(elem).second) - status_callback(wxString::Format(_("Found '%s' at '%s'\n"), to_wx(style.first.facename), elem.make_preferred().wstring()), 0); + status_callback(fmt_tl("Found '%s' at '%s'\n", style.first.facename, elem.make_preferred()), 0); } if (res.fake_bold) - status_callback(wxString::Format(_("'%s' does not have a bold variant.\n"), to_wx(style.first.facename)), 3); + status_callback(fmt_tl("'%s' does not have a bold variant.\n", style.first.facename), 3); if (res.fake_italic) - status_callback(wxString::Format(_("'%s' does not have an italic variant.\n"), to_wx(style.first.facename)), 3); + status_callback(fmt_tl("'%s' does not have an italic variant.\n", style.first.facename), 3); if (res.missing.size()) { if (res.missing.size() > 50) - status_callback(wxString::Format(_("'%s' is missing %d glyphs used.\n"), to_wx(style.first.facename), (int)res.missing.size()), 2); + status_callback(fmt_tl("'%s' is missing %d glyphs used.\n", style.first.facename, res.missing.size()), 2); else if (res.missing.size() > 0) - status_callback(wxString::Format(_("'%s' is missing the following glyphs used: %s\n"), to_wx(style.first.facename), format_missing(res.missing)), 2); + status_callback(fmt_tl("'%s' is missing the following glyphs used: %s\n", style.first.facename, format_missing(res.missing)), 2); PrintUsage(style.second); ++missing_glyphs; } @@ -161,15 +165,15 @@ void FontCollector::ProcessChunk(std::pair<StyleInfo, UsageData> const& style) { void FontCollector::PrintUsage(UsageData const& data) { if (data.styles.size()) { - status_callback(wxString::Format(_("Used in styles:\n")), 2); + status_callback(_("Used in styles:\n"), 2); for (auto const& style : data.styles) - status_callback(wxString::Format(" - %s\n", style), 2); + status_callback(fmt_wx(" - %s\n", style), 2); } if (data.lines.size()) { - status_callback(wxString::Format(_("Used on lines:")), 2); + status_callback(_("Used on lines:"), 2); for (int line : data.lines) - status_callback(wxString::Format(" %d", line), 2); + status_callback(fmt_wx(" %d", line), 2); status_callback("\n", 2); } status_callback("\n", 2); @@ -204,12 +208,11 @@ std::vector<agi::fs::path> FontCollector::GetFontPaths(const AssFile *file) { if (missing == 0) status_callback(_("All fonts found.\n"), 1); else - status_callback(wxString::Format(wxPLURAL("One font could not be found\n", "%d fonts could not be found.\n", missing), missing), 2); + status_callback(fmt_plural(missing, "One font could not be found\n", "%d fonts could not be found.\n", missing), 2); if (missing_glyphs != 0) - status_callback(wxString::Format(wxPLURAL( - "One font was found, but was missing glyphs used in the script.\n", - "%d fonts were found, but were missing glyphs used in the script.\n", - missing_glyphs), + status_callback(fmt_plural(missing_glyphs, + "One font was found, but was missing glyphs used in the script.\n", + "%d fonts were found, but were missing glyphs used in the script.\n", missing_glyphs), 2); return paths; diff --git a/src/format.h b/src/format.h new file mode 100644 index 0000000000000000000000000000000000000000..381fb900c2e753f598655db5c546804f7129235a --- /dev/null +++ b/src/format.h @@ -0,0 +1,64 @@ +// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org> +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +#include <libaegisub/format.h> + +#include <wx/string.h> +#include <wx/translation.h> + +namespace agi { +template<> +struct writer<char, wxString> { + static void write(std::basic_ostream<char>& out, int max_len, wxString const& value) { + format_detail::do_write_str(out, value.wx_str(), + format_detail::actual_len(max_len, value.wx_str())); + } +}; + +template<> +struct writer<wchar_t, wxString> { + static void write(std::basic_ostream<wchar_t>& out, int max_len, wxString const& value) { + format_detail::do_write_str(out, value.wx_str(), + format_detail::actual_len(max_len, value.wx_str())); + } +}; + +template<typename... Args> +std::string format(wxString const& fmt, Args&&... args) { + boost::interprocess::basic_vectorstream<std::basic_string<char>> out; + format(out, (const char *)fmt.utf8_str(), std::forward<Args>(args)...); + return out.vector(); +} + +template<typename... Args> +wxString wxformat(wxString const& fmt, Args&&... args) { + boost::interprocess::basic_vectorstream<std::basic_string<wxChar>> out; + format(out, fmt.wx_str(), std::forward<Args>(args)...); + return out.vector(); +} + +template<typename... Args> +wxString wxformat(const wxChar *fmt, Args&&... args) { + boost::interprocess::basic_vectorstream<std::basic_string<wxChar>> out; + format(out, fmt, std::forward<Args>(args)...); + return out.vector(); +} +} + +#define fmt_wx(str, ...) agi::wxformat(wxS(str), __VA_ARGS__) +#define fmt_tl(str, ...) agi::wxformat(wxGetTranslation(wxS(str)), __VA_ARGS__) +#define fmt_plural(n, sing, plural, ...) \ + agi::wxformat(wxGetTranslation(wxS(sing), wxS(plural), (n)), __VA_ARGS__) diff --git a/src/help_button.cpp b/src/help_button.cpp index 674b4215e44f54e6bfb73a0806ffbb5759c0189f..23db3d3b6949bf3b06e0a063f9816865cf0d270e 100644 --- a/src/help_button.cpp +++ b/src/help_button.cpp @@ -29,6 +29,8 @@ #include "help_button.h" +#include "format.h" + #include <libaegisub/exception.h> #include <boost/container/flat_map.hpp> @@ -79,5 +81,5 @@ void HelpButton::OpenPage(wxString const& pageID) { wxString section; page = page.BeforeFirst('#', §ion); - wxLaunchDefaultBrowser(wxString::Format("http://docs.aegisub.org/3.1/%s/#%s", page, section)); + wxLaunchDefaultBrowser(fmt_wx("http://docs.aegisub.org/3.1/%s/#%s", page, section)); } diff --git a/src/main.cpp b/src/main.cpp index 5714f9f40a9fbc3da7a6d75d8f723fb4c18c332f..de711211391d7f6b561f3047fe3ef3b9b1292ea0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -44,6 +44,7 @@ #include "dialogs.h" #include "export_fixstyle.h" #include "export_framerate.h" +#include "format.h" #include "frame_main.h" #include "include/aegisub/context.h" #include "libresrc/libresrc.h" @@ -55,7 +56,7 @@ #include "version.h" #include <libaegisub/dispatch.h> -#include <libaegisub/format.h> +#include <libaegisub/format_path.h> #include <libaegisub/fs.h> #include <libaegisub/io.h> #include <libaegisub/log.h> @@ -386,12 +387,12 @@ static void UnhandledExeception(bool stackWalk, agi::Context *c) { crash_writer::Write(); // Inform user of crash. - wxMessageBox(wxString::Format(exception_message, path.wstring()), _("Program error"), wxOK | wxICON_ERROR | wxCENTER, nullptr); + wxMessageBox(agi::wxformat(exception_message, path), _("Program error"), wxOK | wxICON_ERROR | wxCENTER, nullptr); } else if (LastStartupState) { if (stackWalk) crash_writer::Write(); - wxMessageBox(wxString::Format("Aegisub has crashed while starting up!\n\nThe last startup step attempted was: %s.", LastStartupState), _("Program error"), wxOK | wxICON_ERROR | wxCENTER); + wxMessageBox(fmt_wx("Aegisub has crashed while starting up!\n\nThe last startup step attempted was: %s.", LastStartupState), _("Program error"), wxOK | wxICON_ERROR | wxCENTER); } #endif } @@ -405,7 +406,7 @@ void AegisubApp::OnFatalException() { } #define SHOW_EXCEPTION(str) \ - wxMessageBox(wxString::Format(_("An unexpected error has occurred. Please save your work and restart Aegisub.\n\nError Message: %s"), str), \ + wxMessageBox(fmt_tl("An unexpected error has occurred. Please save your work and restart Aegisub.\n\nError Message: %s", str), \ "Exception in event handler", wxOK | wxICON_ERROR | wxCENTER | wxSTAY_ON_TOP) void AegisubApp::HandleEvent(wxEvtHandler *handler, wxEventFunction func, wxEvent& event) const { try { diff --git a/src/menu.cpp b/src/menu.cpp index 8a51c4d7b9fb7f58cb6cafb4d73950f54e11b7fb..4a34312dab8d9346da3fbb1264b396e242fb09fe 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -24,6 +24,7 @@ #include "auto4_base.h" #include "command/command.h" #include "compat.h" +#include "format.h" #include "libresrc/libresrc.h" #include "main.h" #include "options.h" @@ -37,6 +38,7 @@ #include <libaegisub/split.h> #include <algorithm> +#include <boost/algorithm/string/case_conv.hpp> #include <boost/range/algorithm_ext/push_back.hpp> #include <vector> #include <wx/app.h> @@ -61,7 +63,7 @@ class MruMenu final : public wxMenu { for (size_t i = GetMenuItemCount(); i < new_size; ++i) { if (i >= items.size()) { items.push_back(new wxMenuItem(this, MENU_ID_BASE + cmds->size(), "_")); - cmds->push_back(from_wx(wxString::Format("recent/%s/%d", to_wx(type).Lower(), (int)i))); + cmds->push_back(agi::format("recent/%s/%d", boost::to_lower_copy(type), i)); } Append(items[i]); } @@ -96,7 +98,7 @@ public: wxString name = it->wstring(); if (!name.StartsWith("?")) name = it->filename().wstring(); - items[i]->SetItemLabel(wxString::Format("%s%d %s", + items[i]->SetItemLabel(fmt_wx("%s%d %s", i <= 9 ? "&" : "", i + 1, name)); items[i]->Enable(true); diff --git a/src/preferences_base.cpp b/src/preferences_base.cpp index 25e66fab475032db505d0d4033e9cc95dc9aafe9..7a664f0fe79485358e014115846b9a46b0390267 100644 --- a/src/preferences_base.cpp +++ b/src/preferences_base.cpp @@ -130,7 +130,7 @@ wxControl *OptionPage::OptionAdd(wxFlexGridSizer *flex, const wxString &name, co } case agi::OptionType::Double: { - wxSpinCtrlDouble *scd = new wxSpinCtrlDouble(this, -1, wxString::Format("%g", opt->GetDouble()), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max, opt->GetDouble(), inc); + wxSpinCtrlDouble *scd = new wxSpinCtrlDouble(this, -1, std::to_wstring(opt->GetDouble()), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max, opt->GetDouble(), inc); scd->Bind(wxEVT_SPINCTRL, DoubleUpdater(opt_name, parent)); Add(flex, name, scd); return scd; diff --git a/src/project.cpp b/src/project.cpp index 395fd52f7a116db032826b0ae9d2c9383d049f74..1471d436d06bfaac7d346d68b5fdb028ba9b15b3 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -23,6 +23,7 @@ #include "compat.h" #include "dialog_progress.h" #include "dialogs.h" +#include "format.h" #include "include/aegisub/audio_provider.h" #include "include/aegisub/context.h" #include "include/aegisub/video_provider.h" @@ -32,6 +33,7 @@ #include "video_controller.h" #include "video_display.h" +#include <libaegisub/format_path.h> #include <libaegisub/fs.h> #include <libaegisub/keyframe.h> #include <libaegisub/log.h> @@ -165,7 +167,7 @@ void Project::LoadUnloadFiles() { if (p.empty()) str += "\n" + unload; else - str += "\n" + wxString::Format(load, p.wstring()); + str += "\n" + agi::wxformat(load, p); }; if (audio != audio_file) diff --git a/src/search_replace_engine.cpp b/src/search_replace_engine.cpp index 9faf90db92aea00a1ee303a25aeba138102095c1..e0e2066b4127edcf3056d628b0cbb71cc6cfdc9d 100644 --- a/src/search_replace_engine.cpp +++ b/src/search_replace_engine.cpp @@ -18,6 +18,7 @@ #include "ass_dialogue.h" #include "ass_file.h" +#include "format.h" #include "include/aegisub/context.h" #include "selection_controller.h" #include "text_selection_controller.h" @@ -328,7 +329,7 @@ bool SearchReplaceEngine::ReplaceAll() { if (count > 0) { context->ass->Commit(_("replace"), AssFile::COMMIT_DIAG_TEXT); - wxMessageBox(wxString::Format(wxPLURAL("One match was replaced.", "%d matches were replaced.", count), (int)count)); + wxMessageBox(fmt_plural(count, "One match was replaced.", "%d matches were replaced.", (int)count)); } else { wxMessageBox(_("No matches found.")); diff --git a/src/subs_controller.cpp b/src/subs_controller.cpp index 358811ba20622a8165a91505050648c60e99055e..a2d9f8b1b2ea808ed1a9faee30010303f96e9a87 100644 --- a/src/subs_controller.cpp +++ b/src/subs_controller.cpp @@ -23,6 +23,7 @@ #include "ass_style.h" #include "compat.h" #include "command/command.h" +#include "format.h" #include "frame_main.h" #include "include/aegisub/context.h" #include "options.h" @@ -31,7 +32,7 @@ #include "subtitle_format.h" #include "text_selection_controller.h" -#include <libaegisub/format.h> +#include <libaegisub/format_path.h> #include <libaegisub/fs.h> #include <libaegisub/path.h> #include <libaegisub/util.h> @@ -154,7 +155,7 @@ SubsController::SubsController(agi::Context *context) try { auto fn = AutoSave(); if (!fn.empty()) - context->frame->StatusTimeout(wxString::Format(_("File backup saved as \"%s\"."), fn.wstring())); + context->frame->StatusTimeout(fmt_tl("File backup saved as \"%s\".", fn)); } catch (const agi::Exception& err) { context->frame->StatusTimeout(to_wx("Exception when attempting to autosave file: " + err.GetMessage())); @@ -254,7 +255,7 @@ int SubsController::TryToClose(bool allow_cancel) const { int flags = wxYES_NO; if (allow_cancel) flags |= wxCANCEL; - int result = wxMessageBox(wxString::Format(_("Do you want to save changes to %s?"), Filename().wstring()), _("Unsaved changes"), flags, context->parent); + int result = wxMessageBox(fmt_tl("Do you want to save changes to %s?", Filename()), _("Unsaved changes"), flags, context->parent); if (result == wxYES) { cmd::call("subtitle/save", context); // If it fails saving, return cancel anyway diff --git a/src/subs_edit_box.cpp b/src/subs_edit_box.cpp index 8c4f7868002cbea35cfcd4210ddb8084817914d7..2680d9fbaf21f473767b8adee61b3d307984d50b 100644 --- a/src/subs_edit_box.cpp +++ b/src/subs_edit_box.cpp @@ -613,7 +613,7 @@ void SubsEditBox::UpdateCharacterCount(std::string const& text) { if (OPT_GET("Subtitle/Character Counter/Ignore Punctuation")->GetBool()) ignore |= agi::IGNORE_PUNCTUATION; size_t length = agi::MaxLineLength(text, ignore); - char_count->SetValue(wxString::Format("%lu", (unsigned long)length)); + char_count->SetValue(std::to_wstring(length)); size_t limit = (size_t)OPT_GET("Subtitle/Character Limit")->GetInt(); if (limit && length > limit) char_count->SetBackgroundColour(to_wx(OPT_GET("Colour/Subtitle/Syntax/Background/Error")->GetColor())); diff --git a/src/subs_edit_ctrl.cpp b/src/subs_edit_ctrl.cpp index 59dc2de1dfed961bbea8815b3c7c840f057901e5..9177659dc26864815d90d8753d8cb082f82f14f9 100644 --- a/src/subs_edit_ctrl.cpp +++ b/src/subs_edit_ctrl.cpp @@ -37,6 +37,7 @@ #include "ass_dialogue.h" #include "command/command.h" #include "compat.h" +#include "format.h" #include "options.h" #include "include/aegisub/context.h" #include "include/aegisub/spellchecker.h" @@ -380,7 +381,7 @@ void SubsTextEditCtrl::AddSpellCheckerEntries(wxMenu &menu) { if (currentWord.empty()) return; if (spellchecker->CanRemoveWord(currentWord)) - menu.Append(EDIT_MENU_REMOVE_FROM_DICT, wxString::Format(_("Remove \"%s\" from dictionary"), to_wx(currentWord))); + menu.Append(EDIT_MENU_REMOVE_FROM_DICT, fmt_tl("Remove \"%s\" from dictionary", currentWord)); sugs = spellchecker->GetSuggestions(currentWord); if (spellchecker->CheckWord(currentWord)) { @@ -391,7 +392,7 @@ void SubsTextEditCtrl::AddSpellCheckerEntries(wxMenu &menu) { for (size_t i = 0; i < sugs.size(); ++i) subMenu->Append(EDIT_MENU_SUGGESTIONS+i, to_wx(sugs[i])); - menu.Append(-1, wxString::Format(_("Spell checker suggestions for \"%s\""), to_wx(currentWord)), subMenu); + menu.Append(-1, fmt_tl("Spell checker suggestions for \"%s\"", currentWord), subMenu); } } else { @@ -402,7 +403,7 @@ void SubsTextEditCtrl::AddSpellCheckerEntries(wxMenu &menu) { menu.Append(EDIT_MENU_SUGGESTIONS+i, to_wx(sugs[i])); // Append "add word" - menu.Append(EDIT_MENU_ADD_TO_DICT, wxString::Format(_("Add \"%s\" to dictionary"), to_wx(currentWord)))->Enable(spellchecker->CanAddWord(currentWord)); + menu.Append(EDIT_MENU_ADD_TO_DICT, fmt_tl("Add \"%s\" to dictionary", currentWord))->Enable(spellchecker->CanAddWord(currentWord)); } } @@ -437,7 +438,7 @@ void SubsTextEditCtrl::AddThesaurusEntries(wxMenu &menu) { } } - menu.Append(-1, wxString::Format(_("Thesaurus suggestions for \"%s\""), to_wx(currentWord)), thesMenu); + menu.Append(-1, fmt_tl("Thesaurus suggestions for \"%s\"", currentWord), thesMenu); } else menu.Append(EDIT_MENU_THESAURUS,_("No thesaurus suggestions"))->Enable(false); diff --git a/src/subtitle_format.cpp b/src/subtitle_format.cpp index f8531c93f85ec110618e38535388b997783d952e..11fe3b19a29fc65e1cfed6c380825c5773432e47 100644 --- a/src/subtitle_format.cpp +++ b/src/subtitle_format.cpp @@ -34,12 +34,10 @@ #include "subtitle_format.h" -#include <wx/intl.h> -#include <wx/choicdlg.h> // Keep this last so wxUSE_CHOICEDLG is set. - #include "ass_dialogue.h" #include "ass_file.h" #include "compat.h" +#include "format.h" #include "subtitle_format_ass.h" #include "subtitle_format_ebu3264.h" #include "subtitle_format_encore.h" @@ -56,6 +54,7 @@ #include <algorithm> #include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/replace.hpp> +#include <wx/choicdlg.h> namespace { std::vector<std::unique_ptr<SubtitleFormat>> formats; @@ -98,7 +97,7 @@ agi::vfr::Framerate SubtitleFormat::AskForFPS(bool allow_vfr, bool show_smpte, a if (fps.IsLoaded()) { vidLoaded = true; if (!fps.IsVFR()) - choices.Add(wxString::Format(_("From video (%g)"), fps.FPS())); + choices.Add(fmt_tl("From video (%g)", fps.FPS())); else if (allow_vfr) choices.Add(_("From video (VFR)")); else diff --git a/src/subtitle_format_ebu3264.cpp b/src/subtitle_format_ebu3264.cpp index 9b97e9bbd75c75801585aab49fd3df6bef4088d2..2e3d981722a9302d1dcb0c42d1f05c056485a398 100644 --- a/src/subtitle_format_ebu3264.cpp +++ b/src/subtitle_format_ebu3264.cpp @@ -28,6 +28,7 @@ #include "ass_style.h" #include "compat.h" #include "dialog_export_ebu3264.h" +#include "format.h" #include "options.h" #include "text_file_writer.h" @@ -404,7 +405,7 @@ namespace else if (!imline.CheckLineLengths(export_settings.max_line_length)) { if (export_settings.line_wrapping_mode == EbuExportSettings::AbortOverLength) - throw Ebu3264SubtitleFormat::ConversionFailed(from_wx(wxString::Format(_("Line over maximum length: %s"), line.Text.get()))); + throw Ebu3264SubtitleFormat::ConversionFailed(agi::format(_("Line over maximum length: %s"), line.Text)); else // skip over-long lines subs_list.pop_back(); } diff --git a/src/utils.cpp b/src/utils.cpp index 6eb8afc3f03f73afd3249ae694c135842b239abe..1eefa80daa10cc31f4419c4d539d3fef3d42773f 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -30,11 +30,11 @@ #include "utils.h" #include "compat.h" +#include "format.h" #include "options.h" #include "retina_helper.h" #include <libaegisub/dispatch.h> -#include <libaegisub/format.h> #include <libaegisub/fs.h> #include <libaegisub/log.h> @@ -72,7 +72,7 @@ wxString PrettySize(int bytes) { fmt = "%.2f"; else if (size < 100) fmt = "%1.f"; - return wxString::Format(fmt, size) + " " + suffix[i]; + return agi::wxformat(fmt, size) + " " + suffix[i]; } std::string float_to_string(double val) { @@ -104,7 +104,7 @@ void RestartAegisub() { std::string helper_path = agi::util::GetBundleAuxillaryExecutablePath("restart-helper"); if (bundle_path.empty() || helper_path.empty()) return; - wxString exec = wxString::Format("\"%s\" /usr/bin/open -n \"%s\"'", to_wx(helper_path), to_wx(bundle_path)); + wxString exec = fmt_wx("\"%s\" /usr/bin/open -n \"%s\"'", helper_path, bundle_path); LOG_I("util/restart/exec") << exec; wxExecute(exec); #else diff --git a/src/validators.cpp b/src/validators.cpp index f839722bedf8561c9035ef0984309c2cd237e83a..2ec12cd65404c027e0762803c2aeaebd56f07ed7 100644 --- a/src/validators.cpp +++ b/src/validators.cpp @@ -132,12 +132,12 @@ void DoubleValidator::OnChar(wxKeyEvent& event) { } bool DoubleValidator::TransferToWindow() { - auto str = wxString::Format("%g", *value); + auto str = std::to_wstring(*value); if (decimal_sep != '.') - std::replace(str.begin(), str.end(), (wxChar)'.', decimal_sep); + std::replace(str.begin(), str.end(), L'.', decimal_sep); if (str.find(decimal_sep) != str.npos) { - while (str.Last() == '0') - str.RemoveLast(); + while (str.back() == '0') + str.pop_back(); } static_cast<wxTextCtrl *>(GetWindow())->SetValue(str); return true; diff --git a/src/video_box.cpp b/src/video_box.cpp index b8a120e55b800a0ddf1d6bbb35d35443e7d02297..96a1d5948043490ad2cb9c5838388641b7fd5969 100644 --- a/src/video_box.cpp +++ b/src/video_box.cpp @@ -32,6 +32,7 @@ #include "ass_dialogue.h" #include "ass_file.h" #include "compat.h" +#include "format.h" #include "include/aegisub/context.h" #include "include/aegisub/toolbar.h" #include "options.h" @@ -65,7 +66,7 @@ VideoBox::VideoBox(wxWindow *parent, bool isDetached, agi::Context *context) wxArrayString choices; for (int i = 1; i <= 24; ++i) - choices.Add(wxString::Format("%g%%", i * 12.5)); + choices.Add(fmt_wx("%g%%", i * 12.5)); auto zoomBox = new wxComboBox(this, -1, "75%", wxDefaultPosition, wxDefaultSize, choices, wxCB_DROPDOWN | wxTE_PROCESS_ENTER); auto visualToolBar = toolbar::GetToolbar(this, "visual_tools", context, "Video", true); @@ -114,7 +115,7 @@ void VideoBox::UpdateTimeBoxes() { int time = context->videoController->TimeAtFrame(frame, agi::vfr::EXACT); // Set the text box for frame number and time - VideoPosition->SetValue(wxString::Format("%s - %d", AssTime(time).GetAssFormated(true), frame)); + VideoPosition->SetValue(fmt_wx("%s - %d", AssTime(time).GetAssFormated(true), frame)); if (boost::binary_search(context->project->Keyframes(), frame)) { // Set the background color to indicate this is a keyframe VideoPosition->SetBackgroundColour(to_wx(OPT_GET("Colour/Subtitle Grid/Background/Selection")->GetColor())); @@ -129,7 +130,7 @@ void VideoBox::UpdateTimeBoxes() { if (!active_line) VideoSubsPos->SetValue(""); else { - VideoSubsPos->SetValue(wxString::Format( + VideoSubsPos->SetValue(fmt_wx( "%+dms; %+dms", time - active_line->Start, time - active_line->End)); diff --git a/src/video_display.cpp b/src/video_display.cpp index 83268e3dc3a5db7e5e2916f9b271339f083f7e7f..620869b50ddb1ae6736859232235f3f0cf6c29c5 100644 --- a/src/video_display.cpp +++ b/src/video_display.cpp @@ -38,6 +38,7 @@ #include "async_video_provider.h" #include "command/command.h" #include "compat.h" +#include "format.h" #include "include/aegisub/context.h" #include "include/aegisub/hotkey.h" #include "include/aegisub/menu.h" @@ -71,7 +72,7 @@ int attribList[] = { WX_GL_RGBA , WX_GL_DOUBLEBUFFER, WX_GL_STENCIL_SIZE, 8, 0 } class OpenGlException final : public agi::Exception { public: OpenGlException(const char *func, int err) - : agi::Exception(from_wx(wxString::Format("%s failed with error code %d", func, err))) + : agi::Exception(agi::format("%s failed with error code %d", func, err)) { } }; @@ -93,7 +94,7 @@ VideoDisplay::VideoDisplay(wxToolBar *toolbar, bool freeSize, wxComboBox *zoomBo SetZoom(new_zoom); })) { - zoomBox->SetValue(wxString::Format("%g%%", zoomValue * 100.)); + zoomBox->SetValue(fmt_wx("%g%%", zoomValue * 100.)); zoomBox->Bind(wxEVT_COMBOBOX, &VideoDisplay::SetZoomFromBox, this); zoomBox->Bind(wxEVT_TEXT_ENTER, &VideoDisplay::SetZoomFromBoxText, this); @@ -335,7 +336,7 @@ void VideoDisplay::OnSizeEvent(wxSizeEvent &event) { videoSize = GetClientSize() * scale_factor; PositionVideo(); zoomValue = double(viewport_height) / con->project->VideoProvider()->GetHeight(); - zoomBox->ChangeValue(wxString::Format("%g%%", zoomValue * 100.)); + zoomBox->ChangeValue(fmt_wx("%g%%", zoomValue * 100.)); con->ass->Properties.video_zoom = zoomValue; } else { @@ -382,7 +383,7 @@ void VideoDisplay::SetZoom(double value) { size_t selIndex = zoomValue / .125 - 1; if (selIndex < zoomBox->GetCount()) zoomBox->SetSelection(selIndex); - zoomBox->ChangeValue(wxString::Format("%g%%", zoomValue * 100.)); + zoomBox->ChangeValue(fmt_wx("%g%%", zoomValue * 100.)); con->ass->Properties.video_zoom = zoomValue; UpdateSize(); } diff --git a/tests/tests/format.cpp b/tests/tests/format.cpp index 3e8f8d453fc0a83cc8d5ca0125076ca7e0caa98d..551dfc5728ddd650da5ee471940b431a9422f58a 100644 --- a/tests/tests/format.cpp +++ b/tests/tests/format.cpp @@ -18,6 +18,7 @@ #include "util.h" #include <libaegisub/format.h> +#include <libaegisub/format_path.h> TEST(lagi_format, s) { EXPECT_EQ("hello", agi::format("%s", "hello")); @@ -117,3 +118,22 @@ TEST(lagi_format, bad_cast) { EXPECT_THROW(agi::format("%d", "hello"), std::bad_cast); EXPECT_THROW(agi::format("%.*s", "hello", "str"), std::bad_cast); } + +TEST(lagi_format, wchar_t) { + EXPECT_EQ(L"asdf", agi::format(L"%s", L"asdf")); + EXPECT_EQ(L"asdf", agi::format(L"%s", "asdf")); + EXPECT_EQ("asdf", agi::format("%s", L"asdf")); + + EXPECT_EQ(L"\x2603", agi::format(L"%s", L"\x2603")); + EXPECT_EQ(L"\x2603", agi::format(L"%s", "\xE2\x98\x83")); + EXPECT_EQ("\xE2\x98\x83", agi::format("%s", L"\x2603")); + + EXPECT_EQ(L"asdf", agi::format(L"%s", std::wstring(L"asdf"))); + EXPECT_EQ(L"asdf", agi::format(L"%s", std::string("asdf"))); + EXPECT_EQ("asdf", agi::format("%s", std::wstring(L"asdf"))); +} + +TEST(lagi_format, path) { + EXPECT_EQ("/usr/bin", agi::format("%s", agi::fs::path("/usr/bin"))); + EXPECT_EQ(L"/usr/bin", agi::format(L"%s", agi::fs::path("/usr/bin"))); +}