diff --git a/build/libaegisub/libaegisub.vcxproj b/build/libaegisub/libaegisub.vcxproj index a778d8d15..7e3cd44c0 100644 --- a/build/libaegisub/libaegisub.vcxproj +++ b/build/libaegisub/libaegisub.vcxproj @@ -55,6 +55,8 @@ + + diff --git a/build/libaegisub/libaegisub.vcxproj.filters b/build/libaegisub/libaegisub.vcxproj.filters index 71d5059d4..474ac2553 100644 --- a/build/libaegisub/libaegisub.vcxproj.filters +++ b/build/libaegisub/libaegisub.vcxproj.filters @@ -173,6 +173,12 @@ Header Files + + Header Files + + + Header Files + Header Files diff --git a/build/tests/tests.vcxproj b/build/tests/tests.vcxproj index 7ab92bbb7..0cc0e1ab7 100644 --- a/build/tests/tests.vcxproj +++ b/build/tests/tests.vcxproj @@ -42,6 +42,7 @@ + diff --git a/libaegisub/include/libaegisub/format.h b/libaegisub/include/libaegisub/format.h index bda534f2a..17781ae4c 100644 --- a/libaegisub/include/libaegisub/format.h +++ b/libaegisub/include/libaegisub/format.h @@ -14,11 +14,15 @@ // // Aegisub Project http://www.aegisub.org/ +#include + #include #include -#include +#include #include +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::cast(value); } -template -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 +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 +int actual_len(int max_len, std::basic_string value) { if (max_len > 0 && static_cast(max_len) < value.size()) - out.write(value.data(), max_len); - else - out << value; + return max_len; + return value.size(); } +template +inline void do_write_str(std::basic_ostream& 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> 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> utf8_conv; + auto wc = utf8_conv.from_bytes(str, str + len); + out.write(wc.data(), wc.size()); +} +} + +template +struct writer { + static void write(std::basic_ostream& out, int, T const& value) { + out << value; + } +}; + +// Ensure things with specializations don't get implicitly initialized +template<> struct writer; +template<> struct writer; +template<> struct writer; +template<> struct writer; + +template +struct writer { + static void write(std::basic_ostream& out, int max_len, const Char *value) { + format_detail::do_write_str(out, value, format_detail::actual_len(max_len, value)); + } +}; + +template +struct writer> { + static void write(std::basic_ostream& out, int max_len, std::basic_string const& value) { + format_detail::do_write_str(out, value.data(), format_detail::actual_len(max_len, value)); + } +}; + +namespace format_detail { +template 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& 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 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& 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(value); + out << runtime_cast(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::type>::write(out, precision, value); break; } @@ -301,23 +344,24 @@ public: }; // Base case for variadic template recursion -inline void format(formatter&&) { } +template +inline void format(formatter&&) { } -template -void format(formatter&& fmt, T&& first, Args&&... rest) { +template +void format(formatter&& fmt, T&& first, Args&&... rest) { fmt(first); format(std::move(fmt), std::forward(rest)...); } } // namespace format_detail -template -void format(std::ostream& out, const char *fmt, Args&&... args) { - format(format_detail::formatter(out, fmt), std::forward(args)...); +template +void format(std::basic_ostream& out, const Char *fmt, Args&&... args) { + format(format_detail::formatter(out, fmt), std::forward(args)...); } -template -std::string format(const char *fmt, Args&&... args) { - boost::interprocess::basic_vectorstream out; +template +std::basic_string format(const Char *fmt, Args&&... args) { + boost::interprocess::basic_vectorstream> out; format(out, fmt, std::forward(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 000000000..aa0706127 --- /dev/null +++ b/libaegisub/include/libaegisub/format_flyweight.h @@ -0,0 +1,36 @@ +// Copyright (c) 2014, Thomas Goyne +// +// 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 + +#include + +namespace agi { +template<> +struct writer> { + static void write(std::basic_ostream& out, int max_len, boost::flyweight const& value) { + writer::write(out, max_len, value.get()); + } +}; + +template<> +struct writer> { + static void write(std::basic_ostream& out, int max_len, boost::flyweight const& value) { + writer::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 000000000..ff195ece6 --- /dev/null +++ b/libaegisub/include/libaegisub/format_path.h @@ -0,0 +1,36 @@ +// Copyright (c) 2014, Thomas Goyne +// +// 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 + +#include + +namespace agi { +// Default version quotes the path +template<> +struct writer { + static void write(std::basic_ostream& out, int max_len, agi::fs::path const& value) { + out << value.string(); + } +}; + +template<> +struct writer { + static void write(std::basic_ostream& 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 58e77b035..d19079ad7 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 89e2827ba..b31970552 100644 --- a/src/agi_pre.h +++ b/src/agi_pre.h @@ -72,6 +72,7 @@ // Common C++ #include #include +#include #include #include #include @@ -81,9 +82,10 @@ #include #include #include +#include #include -#include #include +#include #include #ifdef _MSC_VER @@ -100,6 +102,7 @@ // Boost #include #include +#include #include #include #include @@ -109,6 +112,7 @@ #include #undef BOOST_NO_SCOPED_ENUMS #include +#include // wxWidgets headers #include // Leave this first. diff --git a/src/audio_display.cpp b/src/audio_display.cpp index 8f086dc75..5c9135143 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 2feaaf60e..edc041b6a 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 @@ -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 196cc79d3..f6a0a1e9e 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 -#include #include #include @@ -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 115aa6c86..84dbb24e3 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 #include #include #include @@ -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 75d51f0cf..d56de1148 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 @@ -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 69ef63033..dd53db7b4 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 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 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 b06c94c0b..73981005f 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 &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 727eca4fd..80995a8ca 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 #include #include @@ -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 85e5aed89..d46e6f53d 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 f2eb54c78..9216416bd 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 @@ -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 d37d8945a..86938a7ee 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 +#include #include #include #include @@ -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(*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 1c5fce2b8..ec7919aaf 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 89489653d..b7de8892d 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 @@ -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 260ddde84..2adfa764c 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 717428b7e..c6ade8b1f 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 a1b947b11..307565dd4 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 07bf4f268..6231b4c4f 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 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 39860d5c0..cce9ab98e 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 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 a78a6ebc4..e8bdf77cf 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 865d32b2d..b5d95910e 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 #include -#include #include #include @@ -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::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 436ad2d56..7ed9c5141 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 00ece3407..3d2dad45b 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 d27b9e4e7..024489f64 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 9c806dd94..1ff0776e9 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 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 0a40a68d0..8c0b6becb 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 +#include #include #include @@ -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 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 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 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 000000000..381fb900c --- /dev/null +++ b/src/format.h @@ -0,0 +1,64 @@ +// Copyright (c) 2014, Thomas Goyne +// +// 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 + +#include +#include + +namespace agi { +template<> +struct writer { + static void write(std::basic_ostream& 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 { + static void write(std::basic_ostream& 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 +std::string format(wxString const& fmt, Args&&... args) { + boost::interprocess::basic_vectorstream> out; + format(out, (const char *)fmt.utf8_str(), std::forward(args)...); + return out.vector(); +} + +template +wxString wxformat(wxString const& fmt, Args&&... args) { + boost::interprocess::basic_vectorstream> out; + format(out, fmt.wx_str(), std::forward(args)...); + return out.vector(); +} + +template +wxString wxformat(const wxChar *fmt, Args&&... args) { + boost::interprocess::basic_vectorstream> out; + format(out, fmt, std::forward(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 674b4215e..23db3d3b6 100644 --- a/src/help_button.cpp +++ b/src/help_button.cpp @@ -29,6 +29,8 @@ #include "help_button.h" +#include "format.h" + #include #include @@ -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 5714f9f40..de7112113 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 -#include +#include #include #include #include @@ -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 8a51c4d7b..4a34312da 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 #include +#include #include #include #include @@ -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 25e66fab4..7a664f0fe 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 395fd52f7..1471d436d 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 #include #include #include @@ -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 9faf90db9..e0e2066b4 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 358811ba2..a2d9f8b1b 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 +#include #include #include #include @@ -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 8c4f78680..2680d9fba 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 59dc2de1d..9177659dc 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 f8531c93f..11fe3b19a 100644 --- a/src/subtitle_format.cpp +++ b/src/subtitle_format.cpp @@ -34,12 +34,10 @@ #include "subtitle_format.h" -#include -#include // 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 #include #include +#include namespace { std::vector> 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 9b97e9bbd..2e3d98172 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 6eb8afc3f..1eefa80da 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 -#include #include #include @@ -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 f839722be..2ec12cd65 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(GetWindow())->SetValue(str); return true; diff --git a/src/video_box.cpp b/src/video_box.cpp index b8a120e55..96a1d5948 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 83268e3dc..620869b50 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 3e8f8d453..551dfc572 100644 --- a/tests/tests/format.cpp +++ b/tests/tests/format.cpp @@ -18,6 +18,7 @@ #include "util.h" #include +#include 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"))); +}