From 5398ccbd5e52c7623dcfa1e10a314039a1684627 Mon Sep 17 00:00:00 2001 From: Simon Rozman Date: Wed, 2 Mar 2016 12:29:54 +0100 Subject: [PATCH] Support for dynamic keyboard shortcuts added --- ZRColaCompile/ZRColaCompile.props | 2 +- ZRColaCompile/ZRColaCompile.vcxproj | 3 + ZRColaCompile/dbsource.cpp | 154 +++++++++-- ZRColaCompile/dbsource.h | 93 +++++-- ZRColaCompile/main.cpp | 258 +++++++++++++++--- ZRColaCompile/stdafx.h | 1 + lib/libZRColaUI/build/libZRColaUI.props | 1 + lib/libZRColaUI/build/libZRColaUI.vcxproj | 2 + .../build/libZRColaUI.vcxproj.filters | 6 + lib/libZRColaUI/include/zrcolaui/common.h | 12 +- lib/libZRColaUI/include/zrcolaui/keyboard.h | 113 ++++++++ lib/libZRColaUI/src/keyboard.cpp | 20 ++ lib/libZRColaUI/src/stdafx.h | 1 + output/data/ZRCola.zrcdb | Bin 98214 -> 107942 bytes 14 files changed, 575 insertions(+), 91 deletions(-) create mode 100644 lib/libZRColaUI/include/zrcolaui/keyboard.h create mode 100644 lib/libZRColaUI/src/keyboard.cpp diff --git a/ZRColaCompile/ZRColaCompile.props b/ZRColaCompile/ZRColaCompile.props index ceb8841..b15055c 100644 --- a/ZRColaCompile/ZRColaCompile.props +++ b/ZRColaCompile/ZRColaCompile.props @@ -7,7 +7,7 @@ - ..\lib\wxExtend\include;..\lib\stdex\include;..\lib\libZRCola\include + ..\lib\wxExtend\include;..\lib\stdex\include;..\lib\libZRCola\include;..\lib\libZRColaUI\include _CONSOLE;%(PreprocessorDefinitions) diff --git a/ZRColaCompile/ZRColaCompile.vcxproj b/ZRColaCompile/ZRColaCompile.vcxproj index 5b5d1e3..8db53c7 100644 --- a/ZRColaCompile/ZRColaCompile.vcxproj +++ b/ZRColaCompile/ZRColaCompile.vcxproj @@ -57,6 +57,9 @@ + + {c0a84bd2-3870-4cd6-b281-0ab322e3c579} + {3c61929e-7289-4101-8d0a-da22d6e1aea8} diff --git a/ZRColaCompile/dbsource.cpp b/ZRColaCompile/dbsource.cpp index 473f3df..a850611 100644 --- a/ZRColaCompile/dbsource.cpp +++ b/ZRColaCompile/dbsource.cpp @@ -29,10 +29,13 @@ ZRCola::DBSource::~DBSource() { if (m_db) m_db->Close(); + + if (m_locale) + _free_locale(m_locale); } -bool ZRCola::DBSource::Open(LPCTSTR _filename) +bool ZRCola::DBSource::Open(LPCTSTR filename) { wxASSERT_MSG(!m_db, wxT("database already open")); @@ -43,20 +46,21 @@ bool ZRCola::DBSource::Open(LPCTSTR _filename) std::wstring cn; cn = L"Driver={Microsoft Access Driver (*.mdb)};"; cn += L"Dbq="; - cn += _filename; + cn += filename; cn += L";Uid=;Pwd=;"; hr = m_db->Open(ATL::CComBSTR(cn.c_str())); if (SUCCEEDED(hr)) { // Database open and ready. - filename = _filename; + m_filename = filename; + m_locale = _create_locale(LC_ALL, "Slovenian_Slovenia.1250"); return true; } else { - _ftprintf(stderr, wxT("%s: error ZCC0011: Could not open database (0x%x).\n"), (LPCTSTR)_filename, hr); + _ftprintf(stderr, wxT("%s: error ZCC0011: Could not open database (0x%x).\n"), (LPCTSTR)filename, hr); LogErrors(); } m_db.Release(); } else - _ftprintf(stderr, wxT("%s: error ZCC0012: Creating ADOConnection object failed (0x%x).\n"), (LPCTSTR)_filename, hr); + _ftprintf(stderr, wxT("%s: error ZCC0012: Creating ADOConnection object failed (0x%x).\n"), (LPCTSTR)filename, hr); return false; } @@ -95,7 +99,38 @@ void ZRCola::DBSource::LogErrors() const } -bool ZRCola::DBSource::GetUnicodeString(const CComPtr& f, std::wstring& str) const +bool ZRCola::DBSource::GetUnicodeCharacter(const ATL::CComPtr& f, wchar_t& chr) const +{ + wxASSERT_MSG(f, wxT("field is empty")); + + CComVariant v; + wxVERIFY(SUCCEEDED(f->get_Value(&v))); + + // Parse the field. Must be exactly one Unicode code. + wxVERIFY(SUCCEEDED(v.ChangeType(VT_BSTR))); + UINT i = 0, n = ::SysStringLen(V_BSTR(&v)); + chr = 0; + for (; i < n && V_BSTR(&v)[i]; i++) { + if (L'0' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'9') chr = chr*0x10 + (V_BSTR(&v)[i] - L'0'); + else if (L'A' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'F') chr = chr*0x10 + (V_BSTR(&v)[i] - L'A' + 10); + else if (L'a' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'f') chr = chr*0x10 + (V_BSTR(&v)[i] - L'a' + 10); + else break; + } + if (i <= 0 && 4 < i) { + CComBSTR fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); + _ftprintf(stderr, wxT("%s: error ZCC0030: Syntax error in \"%.*ls\" field (\"%.*ls\"). Unicode code must be one to four hexadecimal characters long.\n"), m_filename.c_str(), fieldname.Length(), (BSTR)fieldname, n, V_BSTR(&v)); + return false; + } else if (i != n) { + CComBSTR fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); + _ftprintf(stderr, wxT("%s: error ZCC0031: Syntax error in \"%.*ls\" field (\"%.*ls\"). Extra trailing characters.\n"), m_filename.c_str(), fieldname.Length(), (BSTR)fieldname, n, V_BSTR(&v)); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetUnicodeString(const ATL::CComPtr& f, std::wstring& str) const { wxASSERT_MSG(f, wxT("field is empty")); @@ -117,44 +152,63 @@ bool ZRCola::DBSource::GetUnicodeString(const CComPtr& f, std::wstring } if (j <= 0 || 4 < j) { CComBSTR fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); - _ftprintf(stderr, wxT("%s: error ZCC0020: Syntax error in \"%.*ls\" field (\"%.*ls\"). Unicode code must be one to four hexadecimal characters long.\n"), filename.c_str(), fieldname.Length(), (BSTR)fieldname, n, V_BSTR(&v)); + _ftprintf(stderr, wxT("%s: error ZCC0020: Syntax error in \"%.*ls\" field (\"%.*ls\"). Unicode code must be one to four hexadecimal characters long.\n"), m_filename.c_str(), fieldname.Length(), (BSTR)fieldname, n, V_BSTR(&v)); return false; } str += c; // Skip delimiter(s) and whitespace. - for (; i < n && V_BSTR(&v)[i] && (V_BSTR(&v)[i] == L'+' || iswspace(V_BSTR(&v)[i])); i++); + for (; i < n && V_BSTR(&v)[i] && (V_BSTR(&v)[i] == L'+' || _iswspace_l(V_BSTR(&v)[i], m_locale)); i++); } return true; } -bool ZRCola::DBSource::GetUnicodeCharacter(const CComPtr& f, wchar_t& chr) const +bool ZRCola::DBSource::GetKeySequence(const ATL::CComPtr& f, std::vector& seq) const { wxASSERT_MSG(f, wxT("field is empty")); CComVariant v; wxVERIFY(SUCCEEDED(f->get_Value(&v))); - // Parse the field. Must be exactly one Unicode code. wxVERIFY(SUCCEEDED(v.ChangeType(VT_BSTR))); - UINT i = 0, n = ::SysStringLen(V_BSTR(&v)); - chr = 0; - for (; i < n && V_BSTR(&v)[i]; i++) { - if (L'0' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'9') chr = chr*0x10 + (V_BSTR(&v)[i] - L'0'); - else if (L'A' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'F') chr = chr*0x10 + (V_BSTR(&v)[i] - L'A' + 10); - else if (L'a' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'f') chr = chr*0x10 + (V_BSTR(&v)[i] - L'a' + 10); - else break; - } - if (i <= 0 && 4 < i) { - CComBSTR fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); - _ftprintf(stderr, wxT("%s: error ZCC0030: Syntax error in \"%.*ls\" field (\"%.*ls\"). Unicode code must be one to four hexadecimal characters long.\n"), filename.c_str(), fieldname.Length(), (BSTR)fieldname, n, V_BSTR(&v)); - return false; - } else if (i != n) { - CComBSTR fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); - _ftprintf(stderr, wxT("%s: error ZCC0031: Syntax error in \"%.*ls\" field (\"%.*ls\"). Extra trailing characters.\n"), filename.c_str(), fieldname.Length(), (BSTR)fieldname, n, V_BSTR(&v)); - return false; + + // Convert to uppercase. + _wcsupr_l(V_BSTR(&v), m_locale); + + // Parse the field. Must be comma delimited sequence of key codes. + seq.clear(); + for (UINT i = 0, n = ::SysStringLen(V_BSTR(&v)); i < n && V_BSTR(&v)[i];) { + keyseq::keycode kc = {}; + + while (i < n && V_BSTR(&v)[i]) { + // Parse key code. + static const wchar_t str_shift[] = L"SHIFT+", str_ctrl[] = L"CTRL+", str_alt[] = L"ALT+"; + if (i + _countof(str_shift) <= n && wmemcmp(V_BSTR(&v) + i, str_shift, _countof(str_shift) - 1) == 0) { + kc.shift = true; + i += _countof(str_shift) - 1; + } else if (i + _countof(str_ctrl) <= n && wmemcmp(V_BSTR(&v) + i, str_ctrl, _countof(str_ctrl) - 1) == 0) { + kc.ctrl = true; + i += _countof(str_ctrl) - 1; + } else if (i + _countof(str_alt) <= n && wmemcmp(V_BSTR(&v) + i, str_alt, _countof(str_alt) - 1) == 0) { + kc.alt = true; + i += _countof(str_alt) - 1; + } else { + kc.key = V_BSTR(&v)[i]; + i++; + break; + } + } + if (i < n && V_BSTR(&v)[i] && V_BSTR(&v)[i] != L',' && !_iswspace_l(V_BSTR(&v)[i], m_locale)) { + CComBSTR fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); + _ftprintf(stderr, wxT("%s: error ZCC0060: Syntax error in \"%.*ls\" field (\"%.*ls\"). Key sequences must be \"Ctrl+Alt+\" formatted, delimited by commas and/or space.\n"), m_filename.c_str(), fieldname.Length(), (BSTR)fieldname, n, V_BSTR(&v)); + return false; + } + seq.push_back(kc); + + // Skip delimiter(s) and whitespace. + for (; i < n && V_BSTR(&v)[i] && (V_BSTR(&v)[i] == L',' || _iswspace_l(V_BSTR(&v)[i], m_locale)); i++); } return true; @@ -169,7 +223,7 @@ bool ZRCola::DBSource::SelectTranslations(ATL::CComPtr &rs) const // Open it. if (FAILED(rs->Open(ATL::CComVariant(L"SELECT [komb], [znak] FROM [VRS_ReplChar] WHERE [rang_komb]=1"), ATL::CComVariant(m_db), adOpenStatic, adLockReadOnly, adCmdText))) { - _ftprintf(stderr, wxT("%s: error ZCC0040: Error loading compositions from database. Please make sure the file is ZRCola.zrc compatible.\n"), filename.c_str()); + _ftprintf(stderr, wxT("%s: error ZCC0040: Error loading compositions from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); LogErrors(); return false; } @@ -182,20 +236,60 @@ bool ZRCola::DBSource::GetTranslation(const ATL::CComPtr& rs, ZRCo { wxASSERT_MSG(rs, wxT("recordset is empty")); - CComPtr flds; + ATL::CComPtr flds; wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); { - CComPtr f; + ATL::CComPtr f; wxVERIFY(SUCCEEDED(flds->get_Item(CComVariant(L"komb"), &f))); wxCHECK(GetUnicodeString(f, t.str), false); } { - CComPtr f; + ATL::CComPtr f; wxVERIFY(SUCCEEDED(flds->get_Item(CComVariant(L"znak"), &f))); wxCHECK(GetUnicodeCharacter(f, t.chr), false); } return true; } + + +bool ZRCola::DBSource::SelectKeySequences(ATL::CComPtr &rs) const +{ + // Create a new recordset. + if (rs) rs.Release(); + wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); + + // Open it. + if (FAILED(rs->Open(ATL::CComVariant(L"SELECT [Znak], [tipka] FROM [wrd_KeyCodes]"), ATL::CComVariant(m_db), adOpenStatic, adLockReadOnly, adCmdText))) { + _ftprintf(stderr, wxT("%s: error ZCC0050: Error loading key sequences from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetKeySequence(const ATL::CComPtr& rs, ZRCola::DBSource::keyseq& ks) const +{ + wxASSERT_MSG(rs, wxT("recordset is empty")); + + ATL::CComPtr flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + + { + ATL::CComPtr f; + wxVERIFY(SUCCEEDED(flds->get_Item(CComVariant(L"Znak"), &f))); + wxCHECK(GetUnicodeCharacter(f, ks.chr), false); + } + + { + ATL::CComPtr f; + wxVERIFY(SUCCEEDED(flds->get_Item(CComVariant(L"tipka"), &f))); + wxCHECK(GetKeySequence(f, ks.seq), false); + } + + return true; +} diff --git a/ZRColaCompile/dbsource.h b/ZRColaCompile/dbsource.h index 711d97f..69c330a 100644 --- a/ZRColaCompile/dbsource.h +++ b/ZRColaCompile/dbsource.h @@ -22,6 +22,7 @@ #include #include #include +#include namespace ZRCola { @@ -40,6 +41,27 @@ namespace ZRCola { std::wstring str; ///< Decomposed string }; + + /// + /// Key sequence + /// + class keyseq { + public: + /// + /// Key code + /// + struct keycode { + wchar_t key; ///< Key + bool shift; ///< Shift modifier + bool ctrl; ///< Ctrl modifier + bool alt; ///< Alt modifier + }; + + public: + wchar_t chr; ///< Character + std::vector seq; ///< Key sequence + }; + public: DBSource(); virtual ~DBSource(); @@ -93,19 +115,6 @@ namespace ZRCola { } - /// - /// Gets encoded Unicode string from ZRCola.zrc database - /// - /// \param[in] f Data field - /// \param[out] str Output string - /// - /// \returns - /// - true when successful - /// - false otherwise - /// - bool GetUnicodeString(const CComPtr& f, std::wstring& str) const; - - /// /// Gets encoded Unicode character from ZRCola.zrc database /// @@ -116,7 +125,33 @@ namespace ZRCola { /// - true when successful /// - false otherwise /// - bool GetUnicodeCharacter(const CComPtr& f, wchar_t& chr) const; + bool GetUnicodeCharacter(const ATL::CComPtr& f, wchar_t& chr) const; + + + /// + /// Gets encoded Unicode string from ZRCola.zrc database + /// + /// \param[in] f Data field + /// \param[out] str Output string + /// + /// \returns + /// - true when successful + /// - false otherwise + /// + bool GetUnicodeString(const ATL::CComPtr& f, std::wstring& str) const; + + + /// + /// Gets encoded key sequence from ZRCola.zrc database + /// + /// \param[in] f Data field + /// \param[out] seq Output sequence + /// + /// \returns + /// - true when successful + /// - false otherwise + /// + bool GetKeySequence(const ATL::CComPtr& f, std::vector& seq) const; /// @@ -143,8 +178,34 @@ namespace ZRCola { /// bool GetTranslation(const ATL::CComPtr& rs, translation& t) const; + + /// + /// Returns key sequences + /// + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectKeySequences(ATL::CComPtr& rs) const; + + + /// + /// Returns key sequence data + /// + /// \param[in] rs Recordset with results + /// \param[out] ks Key sequence + /// + /// \returns + /// - true when succeeded + /// - false otherwise + /// + bool GetKeySequence(const ATL::CComPtr& rs, keyseq& ks) const; + protected: - std::basic_string filename; ///< the database filename - ATL::CComPtr m_db; ///< the database + std::basic_string m_filename; ///< Database filename + ATL::CComPtr m_db; ///< Database + _locale_t m_locale; ///< Database locale }; }; diff --git a/ZRColaCompile/main.cpp b/ZRColaCompile/main.cpp index ee8cc74..068f68c 100644 --- a/ZRColaCompile/main.cpp +++ b/ZRColaCompile/main.cpp @@ -24,18 +24,18 @@ /// Writes translation database to a stream /// /// \param[in] stream Output stream -/// \param[in] t_db Translation database +/// \param[in] db Translation database /// /// \returns The stream \p stream /// -inline std::ostream& operator <<(std::ostream& stream, const ZRCola::translation_db &t_db) +inline std::ostream& operator <<(std::ostream& stream, const ZRCola::translation_db &db) { - assert(t_db.idxComp.size() == t_db.idxDecomp.size()); + assert(db.idxComp.size() == db.idxDecomp.size()); unsigned __int32 count; // Write index count. - std::vector::size_type trans_count = t_db.idxComp.size(); + std::vector::size_type trans_count = db.idxComp.size(); #if defined(_WIN64) || defined(__x86_64__) || defined(__ppc64__) // 4G check if (trans_count > 0xffffffff) { @@ -49,14 +49,14 @@ inline std::ostream& operator <<(std::ostream& stream, const ZRCola::translation // Write composition index. if (stream.fail()) return stream; - stream.write((const char*)t_db.idxComp.data(), sizeof(unsigned __int32)*count); + stream.write((const char*)db.idxComp.data(), sizeof(unsigned __int32)*count); // Write decomposition index. if (stream.fail()) return stream; - stream.write((const char*)t_db.idxDecomp.data(), sizeof(unsigned __int32)*count); + stream.write((const char*)db.idxDecomp.data(), sizeof(unsigned __int32)*count); // Write data count. - std::vector::size_type data_count = t_db.data.size(); + std::vector::size_type data_count = db.data.size(); #if defined(_WIN64) || defined(__x86_64__) || defined(__ppc64__) // 4G check if (data_count > 0xffffffff) { @@ -70,7 +70,63 @@ inline std::ostream& operator <<(std::ostream& stream, const ZRCola::translation // Write data. if (stream.fail()) return stream; - stream.write((const char*)t_db.data.data(), sizeof(wchar_t)*count); + stream.write((const char*)db.data.data(), sizeof(unsigned __int16)*count); + + return stream; +} + + +/// +/// Writes key sequence database to a stream +/// +/// \param[in] stream Output stream +/// \param[in] db Key sequence database +/// +/// \returns The stream \p stream +/// +inline std::ostream& operator <<(std::ostream& stream, const ZRCola::keyseq_db &db) +{ + assert(db.idxChr.size() == db.idxKey.size()); + + unsigned __int32 count; + + // Write index count. + std::vector::size_type ks_count = db.idxChr.size(); +#if defined(_WIN64) || defined(__x86_64__) || defined(__ppc64__) + // 4G check + if (ks_count > 0xffffffff) { + stream.setstate(std::ios_base::failbit); + return stream; + } +#endif + if (stream.fail()) return stream; + count = (unsigned __int32)ks_count; + stream.write((const char*)&count, sizeof(count)); + + // Write character index. + if (stream.fail()) return stream; + stream.write((const char*)db.idxChr.data(), sizeof(unsigned __int32)*count); + + // Write key index. + if (stream.fail()) return stream; + stream.write((const char*)db.idxKey.data(), sizeof(unsigned __int32)*count); + + // Write data count. + std::vector::size_type data_count = db.data.size(); +#if defined(_WIN64) || defined(__x86_64__) || defined(__ppc64__) + // 4G check + if (data_count > 0xffffffff) { + stream.setstate(std::ios_base::failbit); + return stream; + } +#endif + if (stream.fail()) return stream; + count = (unsigned __int32)data_count; + stream.write((const char*)&count, sizeof(count)); + + // Write data. + if (stream.fail()) return stream; + stream.write((const char*)db.data.data(), sizeof(unsigned __int16)*count); return stream; } @@ -93,17 +149,42 @@ inline std::ostream& operator <<(std::ostream& stream, const ZRCola::translation /// The function does not treat \\0 characters as terminators for performance reasons. /// Therefore \p count_a and \p count_b must represent exact string lengths. /// -static inline int CompareBinary(const wchar_t *str_a, size_t count_a, const wchar_t *str_b, size_t count_b) +static inline int CompareSequence(const wchar_t *str_a, size_t count_a, const wchar_t *str_b, size_t count_b) { for (size_t i = 0; ; i++) { - if (i >= count_a && i >= count_b) break; + if (i >= count_a && i >= count_b) return 0; else if (i >= count_a && i < count_b) return -1; else if (i < count_a && i >= count_b) return +1; else if (str_a[i] < str_b[i]) return -1; else if (str_a[i] > str_b[i]) return +1; } +} - return 0; + +/// +/// Compares two key sequences +/// +/// \param[in] seq_a First key sequence +/// \param[in] count_a Number of keys in sequence \p seq_a +/// \param[in] seq_b Second key sequence +/// \param[in] count_b Number of keys in sequence \p seq_b +/// +/// \returns +/// - <0 when seq_a < seq_b +/// - =0 when seq_a == seq_b +/// - >0 when seq_a > seq_b +/// +static inline int CompareSequence(const ZRCola::keyseq_db::keyseq::key_t *seq_a, size_t count_a, const ZRCola::keyseq_db::keyseq::key_t *seq_b, size_t count_b) +{ + for (size_t i = 0; ; i++) { + if (i >= count_a && i >= count_b) return 0; + else if (i >= count_a && i < count_b) return -1; + else if (i < count_a && i >= count_b) return +1; + else if (seq_a[i].key < seq_b[i].key ) return -1; + else if (seq_a[i].key > seq_b[i].key ) return +1; + else if (seq_a[i].modifiers < seq_b[i].modifiers) return -1; + else if (seq_a[i].modifiers > seq_b[i].modifiers) return +1; + } } @@ -122,10 +203,10 @@ static inline int CompareBinary(const wchar_t *str_a, size_t count_a, const wcha static int __cdecl CompareCompositionIndex(void *data, const void *a, const void *b) { const ZRCola::translation_db::translation - &trans_a = (const ZRCola::translation_db::translation&)((const wchar_t*)data)[*(const unsigned __int32*)a], - &trans_b = (const ZRCola::translation_db::translation&)((const wchar_t*)data)[*(const unsigned __int32*)b]; + &trans_a = (const ZRCola::translation_db::translation&)((const unsigned __int16*)data)[*(const unsigned __int32*)a], + &trans_b = (const ZRCola::translation_db::translation&)((const unsigned __int16*)data)[*(const unsigned __int32*)b]; - int r = CompareBinary(trans_a.str, trans_a.str_len, trans_b.str, trans_b.str_len); + int r = CompareSequence(trans_a.str, trans_a.str_len, trans_b.str, trans_b.str_len); if (r != 0) return r; if (trans_a.chr < trans_b.chr) return -1; @@ -150,13 +231,66 @@ static int __cdecl CompareCompositionIndex(void *data, const void *a, const void static int __cdecl CompareDecompositionIndex(void *data, const void *a, const void *b) { const ZRCola::translation_db::translation - &trans_a = (const ZRCola::translation_db::translation&)((const wchar_t*)data)[*(const unsigned __int32*)a], - &trans_b = (const ZRCola::translation_db::translation&)((const wchar_t*)data)[*(const unsigned __int32*)b]; + &trans_a = (const ZRCola::translation_db::translation&)((const unsigned __int16*)data)[*(const unsigned __int32*)a], + &trans_b = (const ZRCola::translation_db::translation&)((const unsigned __int16*)data)[*(const unsigned __int32*)b]; if (trans_a.chr < trans_b.chr) return -1; else if (trans_a.chr > trans_b.chr) return +1; - return CompareBinary(trans_a.str, trans_a.str_len, trans_b.str, trans_b.str_len); + return CompareSequence(trans_a.str, trans_a.str_len, trans_b.str, trans_b.str_len); +} + + +/// +/// Function to use in \c qsort_s for key sequence index sorting +/// +/// \param[in] data Pointer to key sequence data +/// \param[in] a Pointer to first key sequence index element +/// \param[in] b Pointer to second key sequence index element +/// +/// \returns +/// - <0 when a < b +/// - =0 when a == b +/// - >0 when a > b +/// +static int __cdecl CompareKeySequenceChar(void *data, const void *a, const void *b) +{ + const ZRCola::keyseq_db::keyseq + &ks_a = (const ZRCola::keyseq_db::keyseq&)((const unsigned __int16*)data)[*(const unsigned __int32*)a], + &ks_b = (const ZRCola::keyseq_db::keyseq&)((const unsigned __int16*)data)[*(const unsigned __int32*)b]; + + if (ks_a.chr < ks_b.chr) return -1; + else if (ks_a.chr > ks_b.chr) return +1; + + return CompareSequence(ks_a.seq, ks_a.seq_len, ks_b.seq, ks_b.seq_len); +} + + +/// +/// Function to use in \c qsort_s for key sequence index sorting +/// +/// \param[in] data Pointer to key sequence data +/// \param[in] a Pointer to first key sequence index element +/// \param[in] b Pointer to second key sequence index element +/// +/// \returns +/// - <0 when a < b +/// - =0 when a == b +/// - >0 when a > b +/// +static int __cdecl CompareKeySequenceKey(void *data, const void *a, const void *b) +{ + const ZRCola::keyseq_db::keyseq + &ks_a = (const ZRCola::keyseq_db::keyseq&)((const unsigned __int16*)data)[*(const unsigned __int32*)a], + &ks_b = (const ZRCola::keyseq_db::keyseq&)((const unsigned __int16*)data)[*(const unsigned __int32*)b]; + + int r = CompareSequence(ks_a.seq, ks_a.seq_len, ks_b.seq, ks_b.seq_len); + if (r != 0) return r; + + if (ks_a.chr < ks_b.chr) return -1; + else if (ks_a.chr > ks_b.chr) return +1; + + return 0; } @@ -239,31 +373,30 @@ int _tmain(int argc, _TCHAR *argv[]) // Get translations. ATL::CComPtr rs; if (src.SelectTranslations(rs)) { - size_t trans_count = src.GetRecordsetCount(rs); - if (trans_count < 0xffffffff) { // 4G check (-1 is reserved for error condition) + size_t count = src.GetRecordsetCount(rs); + if (count < 0xffffffff) { // 4G check (-1 is reserved for error condition) ZRCola::DBSource::translation trans; - ZRCola::translation_db t_db; + ZRCola::translation_db db; // Preallocate memory. - t_db.idxComp .reserve(trans_count); - t_db.idxDecomp.reserve(trans_count); - t_db.data .reserve(trans_count*4); + db.idxComp .reserve(count); + db.idxDecomp.reserve(count); + db.data .reserve(count*4); // Parse translations and build index and data. while (!ZRCola::DBSource::IsEOF(rs)) { // Read translation from the database. if (src.GetTranslation(rs, trans)) { // Add translation to index and data. - unsigned __int32 ti; - ti = t_db.data.size(); - t_db.data.push_back(trans.chr); + unsigned __int32 idx = db.data.size(); + db.data.push_back(trans.chr); std::wstring::size_type n = trans.str.length(); wxASSERT_MSG(n <= 0xffff, wxT("transformation string too long")); - t_db.data.push_back((wchar_t)n); + db.data.push_back((unsigned __int16)n); for (std::wstring::size_type i = 0; i < n; i++) - t_db.data.push_back(trans.str[i]); - t_db.idxComp .push_back(ti); - t_db.idxDecomp.push_back(ti); + db.data.push_back(trans.str[i]); + db.idxComp .push_back(idx); + db.idxDecomp.push_back(idx); } else has_errors = true; @@ -271,11 +404,11 @@ int _tmain(int argc, _TCHAR *argv[]) } // Sort indices. - qsort_s(t_db.idxComp .data(), trans_count, sizeof(unsigned __int32), CompareCompositionIndex , t_db.data.data()); - qsort_s(t_db.idxDecomp.data(), trans_count, sizeof(unsigned __int32), CompareDecompositionIndex, t_db.data.data()); + qsort_s(db.idxComp .data(), count, sizeof(unsigned __int32), CompareCompositionIndex , db.data.data()); + qsort_s(db.idxDecomp.data(), count, sizeof(unsigned __int32), CompareDecompositionIndex, db.data.data()); // Write translations to file. - dst << ZRCola::translation_rec(t_db); + dst << ZRCola::translation_rec(db); } else { _ftprintf(stderr, wxT("%s: error ZCC0004: Error getting translation count from database or too many translations.\n"), (LPCTSTR)filenameIn.c_str()); has_errors = true; @@ -286,10 +419,67 @@ int _tmain(int argc, _TCHAR *argv[]) } } + + { + // Get key sequences. + ATL::CComPtr rs; + if (src.SelectKeySequences(rs)) { + size_t count = src.GetRecordsetCount(rs); + if (count < 0xffffffff) { // 4G check (-1 is reserved for error condition) + ZRCola::DBSource::keyseq ks; + ZRCola::keyseq_db db; + + // Preallocate memory. + db.idxChr.reserve(count); + db.idxKey.reserve(count); + db.data .reserve(count*4); + + // Parse translations and build index and data. + while (!ZRCola::DBSource::IsEOF(rs)) { + // Read translation from the database. + if (src.GetKeySequence(rs, ks)) { + // Add translation to index and data. + unsigned __int32 idx = db.data.size(); + db.data.push_back(ks.chr); + std::vector::size_type n = ks.seq.size(); + wxASSERT_MSG(n <= 0xffff, wxT("key sequence too long")); + db.data.push_back((unsigned __int16)n); + for (std::vector::size_type i = 0; i < n; i++) { + const ZRCola::DBSource::keyseq::keycode &kc = ks.seq[i]; + db.data.push_back(kc.key); + db.data.push_back( + (kc.shift ? ZRCola::keyseq_db::keyseq::SHIFT : 0) | + (kc.ctrl ? ZRCola::keyseq_db::keyseq::CTRL : 0) | + (kc.alt ? ZRCola::keyseq_db::keyseq::ALT : 0)); + } + db.idxChr.push_back(idx); + db.idxKey.push_back(idx); + } else + has_errors = true; + + wxVERIFY(SUCCEEDED(rs->MoveNext())); + } + + // Sort indices. + qsort_s(db.idxChr.data(), count, sizeof(unsigned __int32), CompareKeySequenceChar, db.data.data()); + qsort_s(db.idxKey.data(), count, sizeof(unsigned __int32), CompareKeySequenceKey , db.data.data()); + + // Write translations to file. + dst << ZRCola::keyseq_rec(db); + } else { + _ftprintf(stderr, wxT("%s: error ZCC0006: Error getting key sequence count from database or too many key sequences.\n"), (LPCTSTR)filenameIn.c_str()); + has_errors = true; + } + } else { + _ftprintf(stderr, wxT("%s: error ZCC0005: Error getting key sequences from database. Please make sure the file is ZRCola.zrc compatible.\n"), (LPCTSTR)filenameIn.c_str()); + has_errors = true; + } + } + stdex::idrec::close(dst, dst_start); if (dst.fail()) { - _ftprintf(stderr, wxT("%s: error ZCC0005: Writing to output file failed.\n"), (LPCTSTR)filenameOut.c_str()); + _ftprintf(stderr, wxT("%s: error ZCC0007: Writing to output file failed.\n"), (LPCTSTR)filenameOut.c_str()); has_errors = true; } diff --git a/ZRColaCompile/stdafx.h b/ZRColaCompile/stdafx.h index 404e319..13563e7 100644 --- a/ZRColaCompile/stdafx.h +++ b/ZRColaCompile/stdafx.h @@ -23,6 +23,7 @@ #include "dbsource.h" #include +#include #include #include diff --git a/lib/libZRColaUI/build/libZRColaUI.props b/lib/libZRColaUI/build/libZRColaUI.props index dd8e6f9..6d6fcf5 100644 --- a/lib/libZRColaUI/build/libZRColaUI.props +++ b/lib/libZRColaUI/build/libZRColaUI.props @@ -10,6 +10,7 @@ LIBZRCOLAUI;%(PreprocessorDefinitions) + ..\..\stdex\include;..\..\libZRCola\include;%(AdditionalIncludeDirectories) diff --git a/lib/libZRColaUI/build/libZRColaUI.vcxproj b/lib/libZRColaUI/build/libZRColaUI.vcxproj index 4d2b2a0..5b4e48a 100644 --- a/lib/libZRColaUI/build/libZRColaUI.vcxproj +++ b/lib/libZRColaUI/build/libZRColaUI.vcxproj @@ -19,6 +19,7 @@ + Create Create @@ -28,6 +29,7 @@ + diff --git a/lib/libZRColaUI/build/libZRColaUI.vcxproj.filters b/lib/libZRColaUI/build/libZRColaUI.vcxproj.filters index e0a2fd7..605e170 100644 --- a/lib/libZRColaUI/build/libZRColaUI.vcxproj.filters +++ b/lib/libZRColaUI/build/libZRColaUI.vcxproj.filters @@ -18,6 +18,9 @@ Source Files + + Source Files + @@ -26,6 +29,9 @@ Header Files + + Header Files + diff --git a/lib/libZRColaUI/include/zrcolaui/common.h b/lib/libZRColaUI/include/zrcolaui/common.h index 18605eb..16df1fd 100644 --- a/lib/libZRColaUI/include/zrcolaui/common.h +++ b/lib/libZRColaUI/include/zrcolaui/common.h @@ -24,16 +24,8 @@ /// Public function calling convention /// #ifdef LIBZRCOLAUI -#define ZRCOLAUI_API __declspec(dllexport) +#define ZRCOLAUI_API __declspec(dllexport) #else -#define ZRCOLAUI_API __declspec(dllimport) +#define ZRCOLAUI_API __declspec(dllimport) #endif #define ZRCOLA_NOVTABLE __declspec(novtable) -#pragma warning(push) -#pragma warning(disable: 4251) - - -namespace ZRCola { -}; - -#pragma warning(pop) diff --git a/lib/libZRColaUI/include/zrcolaui/keyboard.h b/lib/libZRColaUI/include/zrcolaui/keyboard.h new file mode 100644 index 0000000..13a24f4 --- /dev/null +++ b/lib/libZRColaUI/include/zrcolaui/keyboard.h @@ -0,0 +1,113 @@ +/* + Copyright 2015-2016 Amebis + + This file is part of ZRCola. + + ZRCola is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ZRCola is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ZRCola. If not, see . +*/ + +#pragma once + +#include "common.h" +#include + +#include +#include +#include + +#pragma warning(push) +#pragma warning(disable: 4251) + + +namespace ZRCola { + /// + /// Key sequence database + /// + class ZRCOLAUI_API keyseq_db { + public: +#pragma pack(push) +#pragma pack(2) +#pragma warning(push) +#pragma warning(disable: 4200) + /// + /// Key sequence data + /// + struct keyseq { + enum modifiers_t { + SHIFT = 1<<0, ///< SHIFT key was pressed + CTRL = 1<<1, ///< CTRL key was pressed + ALT = 1<<2, ///< ALT key was pressed + }; + + wchar_t chr; ///< Character + unsigned __int16 seq_len; ///< \c seq length + struct key_t { + wchar_t key; ///< Key + unsigned __int16 modifiers; ///< Modifiers (bitwise combination of SHIFT, CTRL and ALT) + } seq[]; ///< Key sequence + }; +#pragma warning(pop) +#pragma pack(pop) + + std::vector idxChr; ///< Character index + std::vector idxKey; ///< Key index + std::vector data; ///< Key sequences data + }; + + + typedef ZRCOLAUI_API stdex::idrec::record keyseq_rec; +}; + + +const ZRCola::recordid_t stdex::idrec::record::id = *(ZRCola::recordid_t*)"KEY"; + + +/// +/// Reads key sequence database from a stream +/// +/// \param[in] stream Input stream +/// \param[out] db Key sequence database +/// +/// \returns The stream \p stream +/// +inline std::istream& operator >>(_In_ std::istream& stream, _Out_ ZRCola::keyseq_db &db) +{ + unsigned __int32 count; + + // Read index count. + stream.read((char*)&count, sizeof(count)); + if (!stream.good()) return stream; + + // Read character index. + db.idxChr.resize(count); + stream.read((char*)db.idxChr.data(), sizeof(unsigned __int32)*count); + if (!stream.good()) return stream; + + // Read key index. + db.idxKey.resize(count); + stream.read((char*)db.idxKey.data(), sizeof(unsigned __int32)*count); + if (!stream.good()) return stream; + + // Read data count. + stream.read((char*)&count, sizeof(count)); + if (!stream.good()) return stream; + + // Read data. + db.data.resize(count); + stream.read((char*)db.data.data(), sizeof(unsigned __int16)*count); + + return stream; +} + +#pragma warning(pop) diff --git a/lib/libZRColaUI/src/keyboard.cpp b/lib/libZRColaUI/src/keyboard.cpp new file mode 100644 index 0000000..ac111f1 --- /dev/null +++ b/lib/libZRColaUI/src/keyboard.cpp @@ -0,0 +1,20 @@ +/* + Copyright 2015-2016 Amebis + + This file is part of ZRCola. + + ZRCola is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ZRCola is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ZRCola. If not, see . +*/ + +#include "stdafx.h" diff --git a/lib/libZRColaUI/src/stdafx.h b/lib/libZRColaUI/src/stdafx.h index 6a78f14..d4f84ec 100644 --- a/lib/libZRColaUI/src/stdafx.h +++ b/lib/libZRColaUI/src/stdafx.h @@ -20,3 +20,4 @@ #pragma once #include "../../../include/zrcola.h" +#include "../include/zrcolaui/keyboard.h" diff --git a/output/data/ZRCola.zrcdb b/output/data/ZRCola.zrcdb index eb9bbd4f3d587d99316bc3fe3c3b7f3dadb7f8ef..14c5484cf58c57fb8f48605b8c70643d9e359634 100644 GIT binary patch delta 9825 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 delta 20 ccmZ2>nr+#CR<@`hXNGz8jci-l7#Gw709E}5mH+?%