diff --git a/aegilib/aegilib.vcproj b/aegilib/aegilib.vcproj index 001153252..133c9aa5c 100644 --- a/aegilib/aegilib.vcproj +++ b/aegilib/aegilib.vcproj @@ -394,6 +394,14 @@ RelativePath=".\include\aegilib\actionlist.h" > + + + + entry,int line,const String §ion); - virtual ~ActionInsert() {} + ~ActionInsert() {} ActionPtr GetAntiAction(const Model &model) const; void Execute(Model &model); @@ -79,7 +79,7 @@ namespace Gorgonsub { public: ActionRemove(int line,const String §ion); - virtual ~ActionRemove() {} + ~ActionRemove() {} ActionPtr GetAntiAction(const Model &model) const; void Execute(Model &model); @@ -92,11 +92,30 @@ namespace Gorgonsub { shared_ptr delta; const String section; int lineNumber; + bool noTextFields; public: - ActionModify(shared_ptr entry,int line,const String §ion); + ActionModify(shared_ptr entry,int line,const String §ion,bool noTextFields); ActionModify(shared_ptr delta,int line,const String §ion); - virtual ~ActionModify() {} + ~ActionModify() {} + + ActionPtr GetAntiAction(const Model &model) const; + void Execute(Model &model); + }; + + // Modify several line + class ActionModifyBatch : public Action { + private: + std::vector > entries; + std::vector > deltas; + std::vector lines; + const String section; + bool noTextFields; + + public: + ActionModifyBatch(shared_ptr entry,int line,const String §ion,bool noTextFields); + ActionModifyBatch(shared_ptr delta,int line,const String §ion); + ~ActionModifyBatch() {} ActionPtr GetAntiAction(const Model &model) const; void Execute(Model &model); diff --git a/aegilib/include/aegilib/actionlist.h b/aegilib/include/aegilib/actionlist.h index abc65a029..26e009ad8 100644 --- a/aegilib/include/aegilib/actionlist.h +++ b/aegilib/include/aegilib/actionlist.h @@ -38,6 +38,7 @@ #include "action.h" #include "gorgonstring.h" #include "section_entry.h" +#include "selection.h" namespace Gorgonsub { @@ -73,6 +74,7 @@ namespace Gorgonsub { void InsertLine(SectionEntryPtr line,int position=-1,const String section=L""); void RemoveLine(int position,const String section); SectionEntryPtr ModifyLine(int position,const String section); + SectionEntryPtr ModifyLines(Selection selection,const String section); }; typedef shared_ptr ActionListPtr; diff --git a/aegilib/include/aegilib/deltacoder.h b/aegilib/include/aegilib/deltacoder.h index d73a12568..16b511b76 100644 --- a/aegilib/include/aegilib/deltacoder.h +++ b/aegilib/include/aegilib/deltacoder.h @@ -45,7 +45,7 @@ namespace Gorgonsub { class DeltaCoder { public: virtual ~DeltaCoder() {} - virtual VoidPtr EncodeDelta(VoidPtr from,VoidPtr to) const = 0; + virtual VoidPtr EncodeDelta(VoidPtr from,VoidPtr to,bool withTextFields=true) const = 0; virtual VoidPtr EncodeReverseDelta(VoidPtr delta,VoidPtr object) const = 0; virtual void ApplyDelta(VoidPtr delta,VoidPtr object) const = 0; }; diff --git a/aegilib/include/aegilib/selection.h b/aegilib/include/aegilib/selection.h new file mode 100644 index 000000000..3cea70637 --- /dev/null +++ b/aegilib/include/aegilib/selection.h @@ -0,0 +1,80 @@ +// Copyright (c) 2008, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB/GORGONSUB +// +// Website: http://www.aegisub.net +// Contact: mailto:amz@aegisub.net +// + +#pragma once +#include + +namespace Gorgonsub { + + // Range class + class Range { + private: + size_t start,end; + + public: + Range() : start(0), end(0) {} + Range(size_t _start,size_t _end) : start(_start), end(_end) {} + + size_t GetLine(size_t n) const; + size_t GetSize() const { return end-start; } + size_t GetStart() const { return start; } + size_t GetEnd() const { return end; } + }; + + // Selection class + class Selection { + private: + std::vector ranges; + size_t count; + + public: + Selection(); + + void AddLine(size_t line) { AddRange(Range(line,line+1)); } + void AddRange(const Range &range); + void RemoveLine(size_t line) { RemoveRange(Range(line,line+1)); } + void RemoveRange(const Range &range); + void AddSelection (const Selection ¶m); + void RemoveSelection (const Selection ¶m); + + size_t GetCount() const { return count; } + size_t GetRanges() const { return ranges.size(); } + size_t GetLine(size_t n) const; + size_t GetLineInRange(size_t n,size_t range) const { return ranges.at(range).GetLine(n); } + bool IsContiguous() const { return GetRanges() <= 1; } + + }; + +} diff --git a/aegilib/include/aegilib/utils.h b/aegilib/include/aegilib/utils.h index 378025e27..19ab0e73f 100644 --- a/aegilib/include/aegilib/utils.h +++ b/aegilib/include/aegilib/utils.h @@ -96,4 +96,8 @@ namespace Gorgonsub { const wxChar *StringPtrTrim(wxChar *str,size_t len,size_t start); const wxChar *StringTrim(wxString &str,size_t start); bool AsciiStringCompareNoCase(const wxString &str1,const wxChar *str2); + + // Unicode routines + size_t GetUTF8Len(const wchar_t *utf16); + size_t UTF16toUTF8(const wchar_t *utf16,char *utf8); } diff --git a/aegilib/src/action.cpp b/aegilib/src/action.cpp index 0ef58f2a1..86db76105 100644 --- a/aegilib/src/action.cpp +++ b/aegilib/src/action.cpp @@ -116,11 +116,11 @@ void ActionRemove::Execute(Model &model) //////////////// // Constructors -ActionModify::ActionModify(shared_ptr data,int line,const String &sName) -: entry(data), lineNumber(line), section(sName) {} +ActionModify::ActionModify(shared_ptr data,int line,const String &sName,bool _noTextFields) +: entry(data), lineNumber(line), section(sName), noTextFields(_noTextFields) {} ActionModify::ActionModify(shared_ptr _delta,int line,const String &sName) -: delta(_delta), lineNumber(line), section(sName) {} +: delta(_delta), lineNumber(line), section(sName), noTextFields(false) {} ///////////////////////////////// @@ -135,7 +135,7 @@ ActionPtr ActionModify::GetAntiAction(const Model &model) const DeltaCoderPtr deltaCoder = oldEntry->GetDeltaCoder(); if (deltaCoder) { VoidPtr _delta; - if (entry) _delta = deltaCoder->EncodeDelta(entry,oldEntry); + if (entry) _delta = deltaCoder->EncodeDelta(entry,oldEntry,!noTextFields); else _delta = deltaCoder->EncodeReverseDelta(delta,oldEntry); return ActionPtr(new ActionModify(_delta,lineNumber,section)); } diff --git a/aegilib/src/actionlist.cpp b/aegilib/src/actionlist.cpp index acc1f5283..370ab71c1 100644 --- a/aegilib/src/actionlist.cpp +++ b/aegilib/src/actionlist.cpp @@ -126,7 +126,7 @@ SectionEntryPtr ActionList::ModifyLine(int position,const String section) { SectionPtr sect = model.GetSection(section); SectionEntryPtr entry = sect->GetEntry(position)->Clone(); - ActionPtr action = ActionPtr (new ActionModify(entry,position,section)); + ActionPtr action = ActionPtr (new ActionModify(entry,position,section,false)); AddAction(action); return entry; } diff --git a/aegilib/src/formats/format_ass_dialogue_delta.cpp b/aegilib/src/formats/format_ass_dialogue_delta.cpp index 8a300b7c8..a259d6992 100644 --- a/aegilib/src/formats/format_ass_dialogue_delta.cpp +++ b/aegilib/src/formats/format_ass_dialogue_delta.cpp @@ -45,7 +45,7 @@ using namespace Gorgonsub; //////////////////////////////////// // Encode delta between two entries -VoidPtr DialogueASSDeltaCoder::EncodeDelta(VoidPtr _from,VoidPtr _to) const +VoidPtr DialogueASSDeltaCoder::EncodeDelta(VoidPtr _from,VoidPtr _to,bool withTextFields) const { // Cast pointers shared_ptr from = static_pointer_cast (_from); @@ -59,7 +59,7 @@ VoidPtr DialogueASSDeltaCoder::EncodeDelta(VoidPtr _from,VoidPtr _to) const if (from->time[1] != to->time[1]) mask |= 0x0008; for (size_t i=0;i<4;i++) { if (from->margin[i] != to->margin[i]) mask |= 0x0010 << i; - if (from->text[i] != to->text[i]) mask |= 0x0100 << i; + if (withTextFields && from->text[i] != to->text[i]) mask |= 0x0100 << i; } // Calculate final size and allocate diff --git a/aegilib/src/formats/format_ass_dialogue_delta.h b/aegilib/src/formats/format_ass_dialogue_delta.h index fce79c17f..51c3392da 100644 --- a/aegilib/src/formats/format_ass_dialogue_delta.h +++ b/aegilib/src/formats/format_ass_dialogue_delta.h @@ -45,7 +45,7 @@ namespace Gorgonsub { void GetDelta(int mask,char *dst,shared_ptr to) const; public: - VoidPtr EncodeDelta(VoidPtr from,VoidPtr to) const; + VoidPtr EncodeDelta(VoidPtr from,VoidPtr to,bool withTextFields=true) const; VoidPtr EncodeReverseDelta(VoidPtr from,VoidPtr to) const; void ApplyDelta(VoidPtr delta,VoidPtr object) const; }; diff --git a/aegilib/src/selection.cpp b/aegilib/src/selection.cpp new file mode 100644 index 000000000..fd918e517 --- /dev/null +++ b/aegilib/src/selection.cpp @@ -0,0 +1,95 @@ +// Copyright (c) 2008, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB/GORGONSUB +// +// Website: http://www.aegisub.net +// Contact: mailto:amz@aegisub.net +// + +#include "selection.h" +using namespace Gorgonsub; + + +/////////////// +// Constructor +Selection::Selection() +: count(0) +{ +} + + +//////////////// +// Adds a range +void Selection::AddRange(const Range &range) +{ + // TODO + (void) range; +} + + +/////////////////// +// Removes a range +void Selection::RemoveRange(const Range &range) +{ + // TODO + (void) range; +} + + +////////////////// +// Get a specific +size_t Selection::GetLine(size_t n) const +{ + // Find the nth line + size_t cur = 0; + size_t len = ranges.size(); + for (size_t i=0;i n) return ranges[i].GetLine(n-ranges[i].GetStart()); + } + return ~0UL; +} + + +//////////////////////////// +// Append another selection +void Selection::AddSelection (const Selection ¶m) +{ + (void) param; +} + + +////////////////////////////// +// Subtract another selection +void Selection::RemoveSelection (const Selection ¶m) +{ + (void) param; +} + diff --git a/aegilib/src/text_file_writer.cpp b/aegilib/src/text_file_writer.cpp index d1eb9d001..3a84fcf88 100644 --- a/aegilib/src/text_file_writer.cpp +++ b/aegilib/src/text_file_writer.cpp @@ -38,6 +38,7 @@ // Headers #include #include "text_file_writer.h" +#include "utils.h" using namespace Gorgonsub; @@ -83,11 +84,20 @@ void TextFileWriter::WriteLineToFile(Gorgonsub::String line,bool addLineBreak) { // 8-bit else { - wxCharBuffer buf = temp.mb_str(*conv); - if (!buf.data()) - return; - size_t len = strlen(buf.data()); - file.Write(buf.data(),(std::streamsize)len); + if (encoding == _T("UTF-8")) { + const wchar_t* src = temp.c_str(); + //size_t len = GetUTF8Len(src); + size_t len = temp.Length() * 2 + 2; + if (buffer.size() < len) buffer.resize(len); + size_t toWrite = UTF16toUTF8(src,&buffer[0]); + file.Write(&buffer[0],(std::streamsize)toWrite); + } + else { + wxCharBuffer buf = temp.mb_str(*conv); + if (!buf.data()) return; + size_t len = strlen(buf.data()); + file.Write(buf.data(),(std::streamsize)len); + } } } diff --git a/aegilib/src/text_file_writer.h b/aegilib/src/text_file_writer.h index 9b30f63ba..d24d42651 100644 --- a/aegilib/src/text_file_writer.h +++ b/aegilib/src/text_file_writer.h @@ -44,6 +44,7 @@ namespace Gorgonsub { private: wxString encoding; wxOutputStream &file; + std::vector buffer; shared_ptr conv; bool Is16; diff --git a/aegilib/src/utils.cpp b/aegilib/src/utils.cpp index 1ca413943..56d0c2225 100644 --- a/aegilib/src/utils.cpp +++ b/aegilib/src/utils.cpp @@ -200,3 +200,86 @@ bool Gorgonsub::AsciiStringCompareNoCase(const wxString &str1,const wxChar *str2 // Equal strings return true; } + + +/////////////////////////////////////////////// +// Get the UTF-8 length out of a UTF-16 string +size_t Gorgonsub::GetUTF8Len(const wchar_t *utf16) +{ + size_t len = 0; + wchar_t curChar = utf16[0]; + for (size_t i=0;curChar;i++) { + // 1 byte + if ((curChar & 0xFF80) == 0) len++; + + // Surrogate pair UTF-16, 4 bytes + else if ((curChar & 0xFC00) == 0xD800) { + len += 4; + i++; + } + + // 3 bytes + else if (curChar & 0xF800) len += 3; + + // 2 bytes + else if (curChar & 0xFF80) len += 2; + + // Get next + curChar = utf16[i]; + } + + return len; +} + + +/////////////////////////// +// Convert UTF-16 to UTF-8 +size_t Gorgonsub::UTF16toUTF8(const wchar_t *utf16,char *utf8) +{ + wchar_t curChar = 1; + size_t value; + size_t written = 0; + for (size_t i=0;curChar;i++) { + // Get next + curChar = utf16[i]; + + // 1 byte + if ((curChar & 0xFF80) == 0) { + *utf8++ = char(curChar); + if (curChar == 0) break; + written++; + } + + // 2 bytes + else if ((curChar & 0xF800) == 0) { + *utf8++ = char(((curChar & 0x07C0) >> 6) | 0xC0); + *utf8++ = char((curChar & 0x003F) | 0x80); + written += 2; + } + + // Surrogate pair UTF-16 + else if ((curChar & 0xFC00) == 0xD800) { + // Read + value = (curChar - 0xD800) << 10; + i++; + value |= utf16[i] & 0x3FF; + + // Write + *utf8++ = char(((value & 0x1C0000) >> 18) | 0xF0); + *utf8++ = char(((value & 0x03F000) >> 12) | 0x80); + *utf8++ = char(((value & 0x000FC0) >> 6) | 0x80); + *utf8++ = char((value & 0x00003F) | 0x80); + written += 4; + } + + // 3 bytes + else if (curChar & 0xF800) { + *utf8++ = char(((curChar & 0xF000) >> 12) | 0xE0); + *utf8++ = char(((curChar & 0x0FC0) >> 6) | 0x80); + *utf8++ = char((curChar & 0x003F) | 0x80); + written += 3; + } + } + return written; +} + diff --git a/aegilib/test/src/main.cpp b/aegilib/test/src/main.cpp index 1624bb053..29d28d2f0 100644 --- a/aegilib/test/src/main.cpp +++ b/aegilib/test/src/main.cpp @@ -78,17 +78,11 @@ int main() line->SetText(L"Hi, testing insertion of lines!"); cout << "Done.\n"; - // Create action list - cout << "Processing actions... "; - timer.Start(); - ActionListPtr actions = control.CreateActionList(L"Insert line"); - //actions->InsertLine(line,2); - //actions->RemoveLine(3,L"Events"); + // Issue an action + ActionListPtr actions = control.CreateActionList(L"Test"); SectionEntryDialoguePtr diag = dynamic_pointer_cast (actions->ModifyLine(10,L"Events")); diag->SetText(L"Hay guise sup"); actions->Finish(); - timer.Pause(); - cout << "Done in " << timer.Time() << " ms.\n"; // Undo cout << "Undoing and redoing 1000 times... "; @@ -108,5 +102,15 @@ int main() cout << "\n\nException: " << e.what() << endl << endl; } + if (false) { + wchar_t myArray[] = { 0xD834, 0xDD1E, 0 }; + String str = wxString(myArray); + cout << "Length: " << str.Length() << ". Contents: " << str[0] << "," << str[1] << endl; + wxCharBuffer buf = str.mb_str(wxConvUTF8); + unsigned char *chr = (unsigned char *) buf.data(); + cout << "UTF-8 Length: " << strlen(buf) << ". Contents: " << (size_t)chr[0] << "," << (size_t)chr[1] << "," << (size_t)chr[2] << "," << (size_t)chr[3] << endl; + str = wxString(buf,wxConvUTF8); + cout << "Length: " << str.Length() << ". Contents: " << str[0] << "," << str[1] << endl; + } return true; }