diff --git a/build/libaegisub/libaegisub.vcxproj b/build/libaegisub/libaegisub.vcxproj index 998e13337..9a70323fa 100644 --- a/build/libaegisub/libaegisub.vcxproj +++ b/build/libaegisub/libaegisub.vcxproj @@ -125,6 +125,7 @@ + @@ -151,6 +152,7 @@ + diff --git a/build/libaegisub/libaegisub.vcxproj.filters b/build/libaegisub/libaegisub.vcxproj.filters index 06a808a0d..3fb34bf49 100644 --- a/build/libaegisub/libaegisub.vcxproj.filters +++ b/build/libaegisub/libaegisub.vcxproj.filters @@ -238,6 +238,9 @@ Source Files\Windows + + Source Files\Common + Source Files\Common @@ -286,6 +289,9 @@ ASS + + ASS + Source Files\Common diff --git a/libaegisub/Makefile b/libaegisub/Makefile index 64204ef91..934ee5cf5 100644 --- a/libaegisub/Makefile +++ b/libaegisub/Makefile @@ -4,6 +4,7 @@ aegisub_OBJ := \ $(d)common/parser.o \ $(d)ass/dialogue_parser.o \ $(d)ass/time.o \ + $(d)ass/uuencode.o \ $(subst .cpp,.o,$(wildcard $(d)audio/*.cpp)) \ $(subst .cpp,.o,$(wildcard $(d)common/cajun/*.cpp)) \ $(subst .cpp,.o,$(wildcard $(d)lua/modules/*.cpp)) \ @@ -25,6 +26,7 @@ aegisub_OBJ := \ $(d)common/kana_table.o \ $(d)common/karaoke_matcher.o \ $(d)common/keyframe.o \ + $(d)common/line_iterator.o \ $(d)common/log.o \ $(d)common/mru.o \ $(d)common/option.o \ diff --git a/libaegisub/ass/uuencode.cpp b/libaegisub/ass/uuencode.cpp new file mode 100644 index 000000000..182472926 --- /dev/null +++ b/libaegisub/ass/uuencode.cpp @@ -0,0 +1,85 @@ +// Copyright (c) 2013, 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 + +// Despite being called uuencoding by ass_specs.doc, the format is actually +// somewhat different from real uuencoding. Each 3-byte chunk is split into 4 +// 6-bit pieces, then 33 is added to each piece. Lines are wrapped after 80 +// characters, and files with non-multiple-of-three lengths are padded with +// zero. + +namespace agi { namespace ass { + +std::string UUEncode(const char *begin, const char *end, bool insert_linebreaks) { + size_t size = std::distance(begin, end); + std::string ret; + ret.reserve((size * 4 + 2) / 3 + size / 80 * 2); + + size_t written = 0; + for (size_t pos = 0; pos < size; pos += 3) { + unsigned char src[3] = { '\0', '\0', '\0' }; + memcpy(src, begin + pos, std::min(3u, size - pos)); + + unsigned char dst[4] = { + static_cast(src[0] >> 2), + static_cast(((src[0] & 0x3) << 4) | ((src[1] & 0xF0) >> 4)), + static_cast(((src[1] & 0xF) << 2) | ((src[2] & 0xC0) >> 6)), + static_cast(src[2] & 0x3F) + }; + + for (size_t i = 0; i < std::min(size - pos + 1, 4u); ++i) { + ret += dst[i] + 33; + + if (insert_linebreaks && ++written == 80 && pos + 3 < size) { + written = 0; + ret += "\r\n"; + } + } + } + + return ret; +} + +std::vector UUDecode(const char *begin, const char *end) { + std::vector ret; + size_t len = end - begin; + ret.reserve(len * 3 / 4); + + for (size_t pos = 0; pos + 1 < len; ) { + size_t bytes = 0; + unsigned char src[4] = { '\0', '\0', '\0', '\0' }; + for (size_t i = 0; i < 4 && pos < len; ++pos) { + char c = begin[pos]; + if (c && c != '\n' && c != '\r') { + src[i++] = c - 33; + ++bytes; + } + } + + if (bytes > 1) + ret.push_back((src[0] << 2) | (src[1] >> 4)); + if (bytes > 2) + ret.push_back(((src[1] & 0xF) << 4) | (src[2] >> 2)); + if (bytes > 3) + ret.push_back(((src[2] & 0x3) << 6) | (src[3])); + } + + return ret; +} +} } diff --git a/libaegisub/common/line_iterator.cpp b/libaegisub/common/line_iterator.cpp new file mode 100644 index 000000000..b288c831b --- /dev/null +++ b/libaegisub/common/line_iterator.cpp @@ -0,0 +1,79 @@ +// Copyright (c) 2013, 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. + +#include +#include + +#include + +namespace agi { + +line_iterator_base::line_iterator_base(std::istream &stream, std::string encoding) +: stream(&stream) +{ + boost::to_lower(encoding); + if (encoding != "utf-8") { + agi::charset::IconvWrapper c("utf-8", encoding.c_str()); + c.Convert("\r", 1, reinterpret_cast(&cr), sizeof(int)); + c.Convert("\n", 1, reinterpret_cast(&lf), sizeof(int)); + width = c.RequiredBufferSize("\n"); + conv = std::make_shared(encoding.c_str(), "utf-8"); + } +} + +bool line_iterator_base::getline(std::string &str) { + if (!stream) return false; + if (!stream->good()) { + stream = nullptr; + return false; + } + + if (width == 1) { + std::getline(*stream, str); + if (str.size() && str.back() == '\r') + str.pop_back(); + } + else { + union { + int32_t chr; + char buf[4]; + } u; + + for (;;) { + u.chr = 0; + std::streamsize read = stream->rdbuf()->sgetn(u.buf, width); + if (read < (std::streamsize)width) { + for (int i = 0; i < read; i++) { + str += u.buf[i]; + } + stream->setstate(std::ios::eofbit); + break; + } + if (u.chr == cr) continue; + if (u.chr == lf) break; + for (int i = 0; i < read; i++) { + str += u.buf[i]; + } + } + } + + if (conv.get()) { + std::string tmp; + conv->Convert(str, tmp); + str = std::move(tmp); + } + + return true; +} +} diff --git a/libaegisub/common/option.cpp b/libaegisub/common/option.cpp index 0fda90e4f..cdf5a07b8 100644 --- a/libaegisub/common/option.cpp +++ b/libaegisub/common/option.cpp @@ -61,15 +61,8 @@ class ConfigVisitor final : public json::ConstVisitor { void ReadArray(json::Array const& src, std::string const& array_type) { typename OptionValueType::value_type arr; arr.reserve(src.size()); - - for (json::Object const& obj : src) { - if (obj.size() != 1) - return Error("Invalid array member"); - if (obj.begin()->first != array_type) - return Error("Attempt to insert value into array of wrong type"); - + for (json::Object const& obj : src) arr.push_back((typename OptionValueType::value_type::value_type)(obj.begin()->second)); - } values.push_back(agi::make_unique(name, std::move(arr))); } @@ -92,6 +85,13 @@ class ConfigVisitor final : public json::ConstVisitor { return Error("Invalid array member"); auto const& array_type = front.begin()->first; + for (json::Object const& obj : array) { + if (obj.size() != 1) + return Error("Invalid array member"); + if (obj.begin()->first != array_type) + return Error("Attempt to insert value into array of wrong type"); + } + if (array_type == "string") ReadArray(array, array_type); else if (array_type == "int") @@ -158,11 +158,9 @@ void put_option(json::Object &obj, const std::string &path, json::UnknownElement template void put_array(json::Object &obj, const std::string &path, const char *element_key, std::vector const& value) { json::Array array; - for (T const& elem : value) { - array.push_back(json::Object()); - static_cast(array.back())[element_key] = (json::UnknownElement)elem; - } - + array.resize(value.size()); + for (size_t i = 0, size = value.size(); i < size; ++i) + static_cast(array[i])[element_key] = (json::UnknownElement)value[i]; put_option(obj, path, std::move(array)); } diff --git a/libaegisub/include/libaegisub/ass/uuencode.h b/libaegisub/include/libaegisub/ass/uuencode.h index b6b30e442..98e37b44d 100644 --- a/libaegisub/include/libaegisub/ass/uuencode.h +++ b/libaegisub/include/libaegisub/ass/uuencode.h @@ -14,79 +14,13 @@ // // Aegisub Project http://www.aegisub.org/ -#include #include #include -// Despite being called uuencoding by ass_specs.doc, the format is actually -// somewhat different from real uuencoding. Each 3-byte chunk is split into 4 -// 6-bit pieces, then 33 is added to each piece. Lines are wrapped after 80 -// characters, and files with non-multiple-of-three lengths are padded with -// zero. - namespace agi { namespace ass { - /// Encode a blob of data, using ASS's nonstandard variant -template -std::string UUEncode(RandomAccessRange const& data, bool insert_linebreaks=true) { - using std::begin; - using std::end; - - size_t size = std::distance(begin(data), end(data)); - std::string ret; - ret.reserve((size * 4 + 2) / 3 + size / 80 * 2); - - size_t written = 0; - for (size_t pos = 0; pos < size; pos += 3) { - unsigned char src[3] = { '\0', '\0', '\0' }; - memcpy(src, &data[pos], std::min(3u, size - pos)); - - unsigned char dst[4] = { - static_cast(src[0] >> 2), - static_cast(((src[0] & 0x3) << 4) | ((src[1] & 0xF0) >> 4)), - static_cast(((src[1] & 0xF) << 2) | ((src[2] & 0xC0) >> 6)), - static_cast(src[2] & 0x3F) - }; - - for (size_t i = 0; i < std::min(size - pos + 1, 4u); ++i) { - ret += dst[i] + 33; - - if (insert_linebreaks && ++written == 80 && pos + 3 < size) { - written = 0; - ret += "\r\n"; - } - } - } - - return ret; -} +std::string UUEncode(const char *begin, const char *end, bool insert_linebreaks=true); /// Decode an ASS uuencoded string -template -std::vector UUDecode(String const& str) { - std::vector ret; - size_t len = std::end(str) - std::begin(str); - ret.reserve(len * 3 / 4); - - for (size_t pos = 0; pos + 1 < len; ) { - size_t bytes = 0; - unsigned char src[4] = { '\0', '\0', '\0', '\0' }; - for (size_t i = 0; i < 4 && pos < len; ++pos) { - char c = str[pos]; - if (c && c != '\n' && c != '\r') { - src[i++] = c - 33; - ++bytes; - } - } - - if (bytes > 1) - ret.push_back((src[0] << 2) | (src[1] >> 4)); - if (bytes > 2) - ret.push_back(((src[1] & 0xF) << 4) | (src[2] >> 2)); - if (bytes > 3) - ret.push_back(((src[2] & 0x3) << 6) | (src[3])); - } - - return ret; -} +std::vector UUDecode(const char *begin, const char *end); } } diff --git a/libaegisub/include/libaegisub/line_iterator.h b/libaegisub/include/libaegisub/line_iterator.h index 127014c76..223aeb96b 100644 --- a/libaegisub/include/libaegisub/line_iterator.h +++ b/libaegisub/include/libaegisub/line_iterator.h @@ -23,22 +23,44 @@ #include #include -#include - -#include namespace agi { +namespace charset { class IconvWrapper; } + +class line_iterator_base { + std::istream *stream = nullptr; ///< Stream to iterate over + std::shared_ptr conv; + int cr = '\r'; ///< CR character in the source encoding + int lf = '\n'; ///< LF character in the source encoding + size_t width = 1; ///< width of LF character in the source encoding + +protected: + bool getline(std::string &str); + +public: + line_iterator_base(std::istream &stream, std::string encoding = "utf-8"); + + line_iterator_base() = default; + line_iterator_base(line_iterator_base const&) = default; +#ifndef _MSC_VER + line_iterator_base(line_iterator_base&&) = default; +#endif + + line_iterator_base& operator=(line_iterator_base const&) = default; +#ifndef _MSC_VER + line_iterator_base& operator=(line_iterator_base&&) = default; +#endif + + bool operator==(line_iterator_base const& rgt) const { return stream == rgt.stream; } + bool operator!=(line_iterator_base const& rgt) const { return !operator==(rgt); } +}; + /// @class line_iterator /// @brief An iterator over lines in a stream template -class line_iterator final : public std::iterator { - std::istream *stream = nullptr; ///< Stream to iterator over +class line_iterator final : public line_iterator_base, public std::iterator { OutputType value; ///< Value to return when this is dereference - std::shared_ptr conv; - int cr; ///< CR character in the source encoding - int lf; ///< LF character in the source encoding - size_t width; ///< width of LF character in the source encoding /// @brief Convert a string to the output type /// @param str Line read from the file @@ -47,9 +69,6 @@ class line_iterator final : public std::iterator(&cr), sizeof(int)); - c.Convert("\n", 1, reinterpret_cast(&lf), sizeof(int)); - width = c.RequiredBufferSize("\n"); - conv = std::make_shared(encoding.c_str(), "utf-8"); - } - ++(*this); } @@ -96,30 +104,11 @@ public: return tmp; } - bool operator==(line_iterator const& rgt) const { return stream == rgt.stream; } - bool operator!=(line_iterator const& rgt) const { return !operator==(rgt); } - // typedefs needed by some stl algorithms typedef OutputType* pointer; typedef OutputType& reference; typedef const OutputType* const_pointer; typedef const OutputType& const_reference; - - line_iterator& operator=(line_iterator that) { - using std::swap; - swap(*this, that); - return *this; - } - - void swap(line_iterator &that) throw() { - using std::swap; - swap(stream, that.stream); - swap(value, that.value); - swap(conv, that.conv); - swap(lf, that.lf); - swap(cr, that.cr); - swap(width, that.width); - } }; // Enable range-based for @@ -129,78 +118,19 @@ line_iterator& begin(line_iterator& it) { return it; } template line_iterator end(line_iterator&) { return agi::line_iterator(); } -template -void line_iterator::getline(std::string &str) { - union { - int32_t chr; - char buf[4]; - } u; - - for (;;) { - u.chr = 0; - std::streamsize read = stream->rdbuf()->sgetn(u.buf, width); - if (read < (std::streamsize)width) { - for (int i = 0; i < read; i++) { - str += u.buf[i]; - } - stream->setstate(std::ios::eofbit); - return; - } - if (u.chr == cr) continue; - if (u.chr == lf) return; - for (int i = 0; i < read; i++) { - str += u.buf[i]; - } - } -} - template void line_iterator::next() { - if (!stream) return; - if (!stream->good()) { - stream = nullptr; + std::string str; + if (!getline(str)) return; - } - std::string str, cstr, *target; - if (width == 1) { - std::getline(*stream, str); - if (str.size() && str.back() == '\r') - str.pop_back(); - } - else { - getline(str); - } - if (conv.get()) { - conv->Convert(str, cstr); - target = &cstr; - } - else { - target = &str; - } - if (!convert(*target)) + if (!convert(str)) next(); } template<> inline void line_iterator::next() { - if (!stream) return; - if (!stream->good()) { - stream = nullptr; - return; - } - std::string cstr; - std::string *target = conv ? &cstr : &value; - if (width == 1) { - std::getline(*stream, *target); - if (target->size() && target->back() == '\r') - target->pop_back(); - } - else - getline(*target); - if (conv.get()) { - value.clear(); - conv->Convert(*target, value); - } + value.clear(); + getline(value); } template @@ -210,9 +140,4 @@ inline bool line_iterator::convert(std::string &str) { return !ss.fail(); } -template -void swap(agi::line_iterator &lft, agi::line_iterator &rgt) { - lft.swap(rgt); -} - } diff --git a/libaegisub/include/libaegisub/lua/ffi.h b/libaegisub/include/libaegisub/lua/ffi.h index 0dbf166cb..848cf0805 100644 --- a/libaegisub/include/libaegisub/lua/ffi.h +++ b/libaegisub/include/libaegisub/lua/ffi.h @@ -20,19 +20,17 @@ #include namespace agi { namespace lua { +void do_register_lib_function(lua_State *L, const char *name, const char *type_name, void *func); +void do_register_lib_table(lua_State *L, std::initializer_list types); + static void register_lib_functions(lua_State *) { // Base case of recursion; nothing to do } template void register_lib_functions(lua_State *L, const char *name, Func *func, Rest... rest) { - lua_pushvalue(L, -2); // push cast function - lua_pushstring(L, type_name::name().c_str()); // This cast isn't legal, but LuaJIT internally requires that it work, so we can rely on it too - lua_pushlightuserdata(L, (void *)func); - lua_call(L, 2, 1); - lua_setfield(L, -2, name); - + do_register_lib_function(L, name, type_name::name().c_str(), (void *)func); register_lib_functions(L, rest...); } @@ -40,20 +38,7 @@ template void register_lib_table(lua_State *L, std::initializer_list types, Args... functions) { static_assert((sizeof...(functions) & 1) == 0, "Functions must be alternating names and function pointers"); - lua_getglobal(L, "require"); - lua_pushstring(L, "ffi"); - lua_call(L, 1, 1); - - // Register all passed type with the ffi - for (auto type : types) { - lua_getfield(L, -1, "cdef"); - lua_pushfstring(L, "typedef struct %s %s;", type, type); - lua_call(L, 1, 0); - } - - lua_getfield(L, -1, "cast"); - lua_remove(L, -2); // ffi table - + do_register_lib_table(L, types); // leaves ffi.cast on the stack lua_createtable(L, 0, sizeof...(functions) / 2); register_lib_functions(L, functions...); lua_remove(L, -2); // ffi.cast function diff --git a/libaegisub/include/libaegisub/lua/utils.h b/libaegisub/include/libaegisub/lua/utils.h index ff57e863e..a7beb8ebc 100644 --- a/libaegisub/include/libaegisub/lua/utils.h +++ b/libaegisub/include/libaegisub/lua/utils.h @@ -77,10 +77,7 @@ void push_value(lua_State *L, std::vector const& value) { } } -/// Wrap a function which may throw exceptions and make it trigger lua errors -/// whenever it throws -template -int exception_wrapper(lua_State *L) { +inline int exception_wrapper(lua_State *L, int (*func)(lua_State *L)) { try { return func(L); } @@ -101,6 +98,13 @@ int exception_wrapper(lua_State *L) { } } +/// Wrap a function which may throw exceptions and make it trigger lua errors +/// whenever it throws +template +int exception_wrapper(lua_State *L) { + return exception_wrapper(L, func); +} + template void set_field(lua_State *L, const char *name, T value) { push_value(L, value); diff --git a/libaegisub/lua/modules.cpp b/libaegisub/lua/modules.cpp index 4777bb396..061c0b8c0 100644 --- a/libaegisub/lua/modules.cpp +++ b/libaegisub/lua/modules.cpp @@ -16,6 +16,7 @@ #include "libaegisub/lua/modules.h" +#include "libaegisub/lua/ffi.h" #include "libaegisub/lua/utils.h" extern "C" int luaopen_luabins(lua_State *L); @@ -40,5 +41,33 @@ void preload_modules(lua_State *L) { set_field(L, "luabins", luaopen_luabins); lua_pop(L, 2); + + register_lib_functions(L); // silence an unused static function warning +} + +void do_register_lib_function(lua_State *L, const char *name, const char *type_name, void *func) { + lua_pushvalue(L, -2); // push cast function + lua_pushstring(L, type_name); + lua_pushlightuserdata(L, func); + lua_call(L, 2, 1); + lua_setfield(L, -2, name); +} + +void do_register_lib_table(lua_State *L, std::initializer_list types) { + lua_getglobal(L, "require"); + lua_pushstring(L, "ffi"); + lua_call(L, 1, 1); + + // Register all passed type with the ffi + for (auto type : types) { + lua_getfield(L, -1, "cdef"); + lua_pushfstring(L, "typedef struct %s %s;", type, type); + lua_call(L, 1, 0); + } + + lua_getfield(L, -1, "cast"); + lua_remove(L, -2); // ffi table + + // leaves ffi.cast on the stack } } } diff --git a/src/ass_attachment.cpp b/src/ass_attachment.cpp index ae967c85c..6f5bd3a08 100644 --- a/src/ass_attachment.cpp +++ b/src/ass_attachment.cpp @@ -21,7 +21,6 @@ #include #include -#include AssAttachment::AssAttachment(AssAttachment const& rgt) : entry_data(rgt.entry_data) @@ -49,7 +48,7 @@ AssAttachment::AssAttachment(agi::fs::path const& name, AssEntryGroup group) agi::read_file_mapping file(name); auto buff = file.read(); entry_data = (group == AssEntryGroup::FONT ? "fontname: " : "filename: ") + filename.get() + "\r\n"; - entry_data = entry_data.get() + agi::ass::UUEncode(boost::make_iterator_range(buff, buff + file.size())); + entry_data = entry_data.get() + agi::ass::UUEncode(buff, buff + file.size()); } size_t AssAttachment::GetSize() const { @@ -59,7 +58,7 @@ size_t AssAttachment::GetSize() const { void AssAttachment::Extract(agi::fs::path const& filename) const { auto header_end = entry_data.get().find('\n'); - auto decoded = agi::ass::UUDecode(boost::make_iterator_range(entry_data.get().begin() + header_end + 1, entry_data.get().end())); + auto decoded = agi::ass::UUDecode(entry_data.get().c_str() + header_end + 1, &entry_data.get().back() + 1); agi::io::Save(filename, true).Get().write(&decoded[0], decoded.size()); } diff --git a/src/ass_parser.cpp b/src/ass_parser.cpp index fa1df54eb..f55fc3567 100644 --- a/src/ass_parser.cpp +++ b/src/ass_parser.cpp @@ -210,7 +210,7 @@ void AssParser::ParseExtradataLine(std::string const &data) { value = inline_string_decode(value); } else if (valuetype == "u") { // ass uuencoded - auto valuedata = agi::ass::UUDecode(value); + auto valuedata = agi::ass::UUDecode(value.c_str(), value.c_str() + value.size()); value = std::string(valuedata.begin(), valuedata.end()); } else { // unknown, error? diff --git a/src/command/edit.cpp b/src/command/edit.cpp index f6a0a1e9e..8d0b2b273 100644 --- a/src/command/edit.cpp +++ b/src/command/edit.cpp @@ -94,6 +94,23 @@ struct validate_sel_multiple : public Command { } }; +template +AssDialogue *get_dialogue(String data) { + boost::trim(data); + try { + // Try to interpret the line as an ASS line + return new AssDialogue(data); + } + catch (...) { + // Line didn't parse correctly, assume it's plain text that + // should be pasted in the Text field only + auto d = new AssDialogue; + d->End = 0; + d->Text = data; + return d; + } +} + template void paste_lines(agi::Context *c, bool paste_over, Paster&& paste_line) { std::string data = GetClipboard(); @@ -104,21 +121,7 @@ void paste_lines(agi::Context *c, bool paste_over, Paster&& paste_line) { boost::char_separator sep("\r\n"); for (auto curdata : boost::tokenizer>(data, sep)) { - boost::trim(curdata); - AssDialogue *curdiag; - try { - // Try to interpret the line as an ASS line - curdiag = new AssDialogue(curdata); - } - catch (...) { - // Line didn't parse correctly, assume it's plain text that - // should be pasted in the Text field only - curdiag = new AssDialogue; - curdiag->End = 0; - curdiag->Text = curdata; - } - - AssDialogue *inserted = paste_line(curdiag); + AssDialogue *inserted = paste_line(get_dialogue(curdata)); if (!inserted) break; @@ -167,14 +170,21 @@ struct parsed_line { parsed_line(parsed_line&& r) = default; #endif - template - T get_value(int blockn, T initial, std::string const& tag_name, std::string alt = "") const { + const AssOverrideTag *find_tag(int blockn, std::string const& tag_name, std::string const& alt) const { for (auto ovr : blocks | sliced(0, blockn + 1) | reversed | agi::of_type()) { for (auto const& tag : ovr->Tags | reversed) { if (tag.Name == tag_name || tag.Name == alt) - return tag.Params[0].template Get(initial); + return &tag; } } + return nullptr; + } + + template + T get_value(int blockn, T initial, std::string const& tag_name, std::string const& alt = "") const { + auto tag = find_tag(blockn, tag_name, alt); + if (tag) + return tag->Params[0].template Get(initial); return initial; } @@ -1076,18 +1086,22 @@ struct edit_line_split_by_karaoke final : public validate_sel_nonempty { } }; -template -void split_lines(agi::Context *c, Func&& set_time) { +void split_lines(agi::Context *c, AssDialogue *&n1, AssDialogue *&n2) { int pos = c->textSelectionController->GetSelectionStart(); - AssDialogue *n1 = c->selectionController->GetActiveLine(); - auto n2 = new AssDialogue(*n1); + n1 = c->selectionController->GetActiveLine(); + n2 = new AssDialogue(*n1); c->ass->Events.insert(++c->ass->iterator_to(*n1), *n2); std::string orig = n1->Text; n1->Text = boost::trim_right_copy(orig.substr(0, pos)); n2->Text = boost::trim_left_copy(orig.substr(pos)); +} +template +void split_lines(agi::Context *c, Func&& set_time) { + AssDialogue *n1, *n2; + split_lines(c, n1, n2); set_time(n1, n2); c->ass->Commit(_("split"), AssFile::COMMIT_DIAG_ADDREM | AssFile::COMMIT_DIAG_FULL); diff --git a/src/dialog_dummy_video.cpp b/src/dialog_dummy_video.cpp index 90023864b..8b6a6ee4f 100644 --- a/src/dialog_dummy_video.cpp +++ b/src/dialog_dummy_video.cpp @@ -143,12 +143,16 @@ DialogDummyVideo::DialogDummyVideo(wxWindow *parent) }); } -template -void DialogDummyVideo::AddCtrl(wxString const& label, T *ctrl) { +static void add_label(wxWindow *parent, wxSizer *sizer, wxString const& label) { if (!label) sizer->AddStretchSpacer(); else - sizer->Add(new wxStaticText(&d, -1, label), wxSizerFlags().Center().Left()); + sizer->Add(new wxStaticText(parent, -1, label), wxSizerFlags().Center().Left()); +} + +template +void DialogDummyVideo::AddCtrl(wxString const& label, T *ctrl) { + add_label(&d, sizer, label); sizer->Add(ctrl, wxSizerFlags().Expand().Center().Left()); } diff --git a/src/dialog_manager.h b/src/dialog_manager.h index 3bda704a2..b81e2be67 100644 --- a/src/dialog_manager.h +++ b/src/dialog_manager.h @@ -38,7 +38,10 @@ class DialogManager { template void OnClose(Event &evt) { evt.Skip(); - auto dialog = static_cast(evt.GetEventObject()); + Destroy(static_cast(evt.GetEventObject())); + } + + void Destroy(wxWindow *dialog) { while (!dialog->IsTopLevel()) dialog = dialog->GetParent(); dialog->Destroy(); @@ -50,10 +53,9 @@ class DialogManager { } } - template - std::vector::iterator Find() { + std::vector::iterator Find(std::type_info const& type) { for (auto it = begin(created_dialogs); it != end(created_dialogs); ++it) { - if (*it->first == typeid(T)) + if (*it->first == type) return it; } return end(created_dialogs); @@ -92,10 +94,10 @@ public: diag.ShowModal(); } catch (...) { - created_dialogs.erase(Find()); + created_dialogs.erase(Find(typeid(DialogType))); throw; } - created_dialogs.erase(Find()); + created_dialogs.erase(Find(typeid(DialogType))); } /// Get the dialog of the given type @@ -103,7 +105,7 @@ public: /// @return A pointer to a DialogType or nullptr if no dialog of the given type has been created template DialogType *Get() const { - auto it = const_cast(this)->Find(); + auto it = const_cast(this)->Find(typeid(DialogType)); return it != created_dialogs.end() ? static_cast(it->second) : nullptr; } diff --git a/src/grid_column.cpp b/src/grid_column.cpp index 0cdec482b..8c6444742 100644 --- a/src/grid_column.cpp +++ b/src/grid_column.cpp @@ -215,35 +215,40 @@ struct GridColumnActor final : GridColumn { } }; -template struct GridColumnMargin : GridColumn { + int index; + GridColumnMargin(int index) : index(index) { } + bool Centered() const override { return true; } wxString Value(const AssDialogue *d, const agi::Context *) const override { - return d->Margin[Index] ? wxString(std::to_wstring(d->Margin[Index])) : wxString(); + return d->Margin[index] ? wxString(std::to_wstring(d->Margin[index])) : wxString(); } int Width(const agi::Context *c, WidthHelper &helper) const override { int max = 0; for (AssDialogue const& line : c->ass->Events) { - if (line.Margin[Index] > max) - max = line.Margin[Index]; + if (line.Margin[index] > max) + max = line.Margin[index]; } return max == 0 ? 0 : helper(std::to_wstring(max)); } }; -struct GridColumnMarginLeft final : GridColumnMargin<0> { +struct GridColumnMarginLeft final : GridColumnMargin { + GridColumnMarginLeft() : GridColumnMargin(0) { } COLUMN_HEADER(_("Left")) COLUMN_DESCRIPTION(_("Left Margin")) }; -struct GridColumnMarginRight final : GridColumnMargin<1> { +struct GridColumnMarginRight final : GridColumnMargin { + GridColumnMarginRight() : GridColumnMargin(1) { } COLUMN_HEADER(_("Right")) COLUMN_DESCRIPTION(_("Right Margin")) }; -struct GridColumnMarginVert final : GridColumnMargin<2> { +struct GridColumnMarginVert final : GridColumnMargin { + GridColumnMarginVert() : GridColumnMargin(2) { } COLUMN_HEADER(_("Vert")) COLUMN_DESCRIPTION(_("Vertical Margin")) }; diff --git a/src/subs_edit_box.cpp b/src/subs_edit_box.cpp index 2fd2fcf70..7ed9111bc 100644 --- a/src/subs_edit_box.cpp +++ b/src/subs_edit_box.cpp @@ -443,13 +443,9 @@ void SubsEditBox::OnChange(wxStyledTextEvent &event) { } } -template -void SubsEditBox::SetSelectedRows(setter set, wxString const& desc, int type, bool amend) { - auto const& sel = c->selectionController->GetSelectedSet(); - for_each(sel.begin(), sel.end(), set); - +void SubsEditBox::Commit(wxString const& desc, int type, bool amend, AssDialogue *line) { file_changed_slot.Block(); - commit_id = c->ass->Commit(desc, type, (amend && desc == last_commit_type) ? commit_id : -1, sel.size() == 1 ? *sel.begin() : nullptr); + commit_id = c->ass->Commit(desc, type, (amend && desc == last_commit_type) ? commit_id : -1, line); file_changed_slot.Unblock(); last_commit_type = desc; last_time_commit_type = -1; @@ -457,6 +453,13 @@ void SubsEditBox::SetSelectedRows(setter set, wxString const& desc, int type, bo undo_timer.Start(30000, wxTIMER_ONE_SHOT); } +template +void SubsEditBox::SetSelectedRows(setter set, wxString const& desc, int type, bool amend) { + auto const& sel = c->selectionController->GetSelectedSet(); + for_each(sel.begin(), sel.end(), set); + Commit(desc, type, amend, sel.size() == 1 ? *sel.begin() : nullptr); +} + template void SubsEditBox::SetSelectedRows(T AssDialogueBase::*field, T value, wxString const& desc, int type, bool amend) { SetSelectedRows([&](AssDialogue *d) { d->*field = value; }, desc, type, amend); diff --git a/src/subs_edit_box.h b/src/subs_edit_box.h index 81b82aed6..3cf9497e1 100644 --- a/src/subs_edit_box.h +++ b/src/subs_edit_box.h @@ -110,6 +110,7 @@ class SubsEditBox final : public wxPanel { /// @brief Commits the current edit box contents /// @param desc Undo description to use void CommitText(wxString const& desc); + void Commit(wxString const& desc, int type, bool amend, AssDialogue *line); /// Last commit ID for undo coalescing int commit_id = -1; diff --git a/src/subtitle_format_ass.cpp b/src/subtitle_format_ass.cpp index a2a134ef7..6815c229f 100644 --- a/src/subtitle_format_ass.cpp +++ b/src/subtitle_format_ass.cpp @@ -140,7 +140,7 @@ struct Writer { // the inline_string encoding grew the data by more than uuencoding would // so base64 encode it instead line += "u"; // marker for uuencoding - line += agi::ass::UUEncode(edi.value, false); + line += agi::ass::UUEncode(edi.value.c_str(), edi.value.c_str() + edi.value.size(), false); } else { line += "e"; // marker for inline_string encoding (escaping) line += encoded_data; diff --git a/src/validators.cpp b/src/validators.cpp index 2ec12cd65..20b4c596b 100644 --- a/src/validators.cpp +++ b/src/validators.cpp @@ -168,6 +168,22 @@ bool DoubleSpinValidator::TransferFromWindow() { return true; } +int EnumBinderBase::Get() { + if (auto rb = dynamic_cast(GetWindow())) + return rb->GetSelection(); + if (auto rb = dynamic_cast(GetWindow())) + return rb->GetSelection(); + throw agi::InternalError("Control type not supported by EnumBinder"); +} + +void EnumBinderBase::Set(int value) { + if (auto rb = dynamic_cast(GetWindow())) + rb->SetSelection(value); + else if (auto rb = dynamic_cast(GetWindow())) + rb->SetSelection(value); + throw agi::InternalError("Control type not supported by EnumBinder"); +} + bool StringBinder::TransferFromWindow() { wxWindow *window = GetWindow(); if (wxTextCtrl *ctrl = dynamic_cast(window)) diff --git a/src/validators.h b/src/validators.h index 07afe6037..ae55d192c 100644 --- a/src/validators.h +++ b/src/validators.h @@ -71,36 +71,33 @@ public: DoubleSpinValidator(double *value) : value(value) { } }; +class EnumBinderBase : public wxValidator { + bool Validate(wxWindow *) override { return true; } + +protected: + int Get(); + void Set(int value); +}; + template -class EnumBinder final : public wxValidator { +class EnumBinder final : public EnumBinderBase { T *value; wxObject *Clone() const override { return new EnumBinder(value); } - bool Validate(wxWindow *) override { return true; } bool TransferFromWindow() override { - if (auto rb = dynamic_cast(GetWindow())) - *value = static_cast(rb->GetSelection()); - else if (auto rb = dynamic_cast(GetWindow())) - *value = static_cast(rb->GetSelection()); - else - throw agi::InternalError("Control type not supported by EnumBinder"); + *value = static_cast(Get()); return true; } bool TransferToWindow() override { - if (auto rb = dynamic_cast(GetWindow())) - rb->SetSelection(static_cast(*value)); - else if (auto cb = dynamic_cast(GetWindow())) - cb->SetSelection(static_cast(*value)); - else - throw agi::InternalError("Control type not supported by EnumBinder"); + Set(static_cast(*value)); return true; } public: explicit EnumBinder(T *value) : value(value) { } - EnumBinder(EnumBinder const& rhs) : wxValidator(rhs), value(rhs.value) { } + EnumBinder(EnumBinder const& rhs) : EnumBinderBase(rhs), value(rhs.value) { } }; template diff --git a/tests/tests/line_iterator.cpp b/tests/tests/line_iterator.cpp index e3901be29..41e6ae2f9 100644 --- a/tests/tests/line_iterator.cpp +++ b/tests/tests/line_iterator.cpp @@ -12,6 +12,7 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +#include #include #include diff --git a/tests/tests/uuencode.cpp b/tests/tests/uuencode.cpp index 09688675c..c7c105e1d 100644 --- a/tests/tests/uuencode.cpp +++ b/tests/tests/uuencode.cpp @@ -24,31 +24,34 @@ using namespace agi::ass; TEST(lagi_uuencode, short_blobs) { std::vector data; + auto encode = [&] { return UUEncode(&data[0], &data.back() + 1); }; data.push_back(120); - EXPECT_STREQ("?!", UUEncode(data).c_str()); + EXPECT_STREQ("?!", encode().c_str()); data.push_back(121); - EXPECT_STREQ("?(E", UUEncode(data).c_str()); + EXPECT_STREQ("?(E", encode().c_str()); data.push_back(122); - EXPECT_STREQ("?(F[", UUEncode(data).c_str()); + EXPECT_STREQ("?(F[", encode().c_str()); } TEST(lagi_uuencode, short_strings) { std::vector data; + auto decode = [](const char *str) { return UUDecode(str, str + strlen(str)); }; data.push_back(120); - EXPECT_EQ(data, UUDecode("?!")); + EXPECT_EQ(data, decode("?!")); data.push_back(121); - EXPECT_EQ(data, UUDecode("?(E")); + EXPECT_EQ(data, decode("?(E")); data.push_back(122); - EXPECT_EQ(data, UUDecode("?(F[")); + EXPECT_EQ(data, decode("?(F[")); } TEST(lagi_uuencode, random_blobs_roundtrip) { std::vector data; for (size_t len = 0; len < 200; ++len) { - EXPECT_EQ(data, UUDecode(UUEncode(data))); + auto encoded = UUEncode(data.data(), data.data() + data.size()); + EXPECT_EQ(data, UUDecode(encoded.data(), encoded.data() + encoded.size())); data.push_back(rand()); } }