From c8628ef4eb271e67a3516e2c045eec7bb004d072 Mon Sep 17 00:00:00 2001 From: Simon Rozman Date: Thu, 7 Apr 2016 12:53:19 +0200 Subject: [PATCH] Key sequence import fixed (resolves #7) --- ZRColaCompile/dbsource.cpp | 122 ++++++++++++++++++++++++++++++++++--- ZRColaCompile/dbsource.h | 39 ++++++++++++ output/data/ZRCola.zrcdb | Bin 107922 -> 107962 bytes 3 files changed, 152 insertions(+), 9 deletions(-) diff --git a/ZRColaCompile/dbsource.cpp b/ZRColaCompile/dbsource.cpp index ac86bca..3510b96 100644 --- a/ZRColaCompile/dbsource.cpp +++ b/ZRColaCompile/dbsource.cpp @@ -99,15 +99,43 @@ void ZRCola::DBSource::LogErrors() const } +bool ZRCola::DBSource::GetValue(const ATL::CComPtr& f, bool& val) const +{ + wxASSERT_MSG(f, wxT("field is empty")); + + ATL::CComVariant v; + wxVERIFY(SUCCEEDED(f->get_Value(&v))); + wxCHECK(SUCCEEDED(v.ChangeType(VT_BOOL)), false); + + val = V_BOOL(&v) ? true : false; + + return true; +} + + +bool ZRCola::DBSource::GetValue(const ATL::CComPtr& f, int& val) const +{ + wxASSERT_MSG(f, wxT("field is empty")); + + ATL::CComVariant v; + wxVERIFY(SUCCEEDED(f->get_Value(&v))); + wxCHECK(SUCCEEDED(v.ChangeType(VT_I4)), false); + + val = V_I4(&v); + + return true; +} + + bool ZRCola::DBSource::GetUnicodeCharacter(const ATL::CComPtr& f, wchar_t& chr) const { wxASSERT_MSG(f, wxT("field is empty")); ATL::CComVariant v; wxVERIFY(SUCCEEDED(f->get_Value(&v))); + wxCHECK(SUCCEEDED(v.ChangeType(VT_BSTR)), false); // 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++) { @@ -136,9 +164,9 @@ bool ZRCola::DBSource::GetUnicodeString(const ATL::CComPtr& f, std::ws ATL::CComVariant v; wxVERIFY(SUCCEEDED(f->get_Value(&v))); + wxCHECK(SUCCEEDED(v.ChangeType(VT_BSTR)), false); // Parse the field. Must be "xxxx+xxxx+xxxx..." sequence. - wxVERIFY(SUCCEEDED(v.ChangeType(VT_BSTR))); str.clear(); for (UINT i = 0, n = ::SysStringLen(V_BSTR(&v)); i < n && V_BSTR(&v)[i];) { // Parse Unicode code. @@ -165,13 +193,61 @@ bool ZRCola::DBSource::GetUnicodeString(const ATL::CComPtr& f, std::ws } +bool ZRCola::DBSource::GetKeyCode(const ATL::CComPtr& f, ZRCola::DBSource::keyseq::keycode& kc) const +{ + wxASSERT_MSG(f, wxT("field is empty")); + + ATL::CComVariant v; + wxVERIFY(SUCCEEDED(f->get_Value(&v))); + wxCHECK(SUCCEEDED(v.ChangeType(VT_BSTR)), false); + + // Convert to uppercase. + _wcsupr_l(V_BSTR(&v), m_locale); + + // Parse the field. + memset(&kc, 0, sizeof(kc)); + for (UINT i = 0, n = ::SysStringLen(V_BSTR(&v)); i < n && V_BSTR(&v)[i];) { + // Parse key code. + if (i) { + // Check for "+" separator. + if (V_BSTR(&v)[i] != L'+') { + ATL::CComBSTR fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); + _ftprintf(stderr, wxT("%s: error ZCC0070: Syntax error in \"%.*ls\" field (\"%.*ls\"). Key codes must be \"Ctrl+Alt+\" formatted.\n"), m_filename.c_str(), fieldname.Length(), (BSTR)fieldname, n, V_BSTR(&v)); + } + i++; + if (i >= n || !V_BSTR(&v)[i]) { + ATL::CComBSTR fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); + _ftprintf(stderr, wxT("%s: error ZCC0071: Syntax error in \"%.*ls\" field (\"%.*ls\"). Trailing separator \"+\" found.\n"), m_filename.c_str(), fieldname.Length(), (BSTR)fieldname, n, V_BSTR(&v)); + } + } + + static const wchar_t str_shift[] = L"SHIFT", str_ctrl[] = L"CTRL", str_alt[] = L"ALT"; + if (i + _countof(str_shift) - 1 <= 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) - 1 <= 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) - 1 <= 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++; + } + } + + return true; +} + + bool ZRCola::DBSource::GetKeySequence(const ATL::CComPtr& f, std::vector& seq) const { wxASSERT_MSG(f, wxT("field is empty")); ATL::CComVariant v; wxVERIFY(SUCCEEDED(f->get_Value(&v))); - wxVERIFY(SUCCEEDED(v.ChangeType(VT_BSTR))); + wxCHECK(SUCCEEDED(v.ChangeType(VT_BSTR)), false); // Convert to uppercase. _wcsupr_l(V_BSTR(&v), m_locale); @@ -184,13 +260,13 @@ bool ZRCola::DBSource::GetKeySequence(const ATL::CComPtr& f, std::vect 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) { + if (i + _countof(str_shift) - 1 <= 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) { + } else if (i + _countof(str_ctrl) - 1 <= 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) { + } else if (i + _countof(str_alt) - 1 <= n && wmemcmp(V_BSTR(&v) + i, str_alt, _countof(str_alt) - 1) == 0) { kc.alt = true; i += _countof(str_alt) - 1; } else { @@ -270,7 +346,7 @@ bool ZRCola::DBSource::SelectKeySequences(ATL::CComPtr &rs) const wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); // Open it. - if (FAILED(rs->Open(ATL::CComVariant(L"SELECT DISTINCT [Znak], [tipka] FROM [wrd_KeyCodes]"), ATL::CComVariant(m_db), adOpenStatic, adLockReadOnly, adCmdText))) { + if (FAILED(rs->Open(ATL::CComVariant(L"SELECT DISTINCT [VRS_KeyCodes].[Znak], [VRS_CharGroup].[Name] AS [CharGroup], [VRS_KeyCodes].[KeyCode], [VRS_KeyCodes].[Shift] FROM [VRS_KeyCodes] LEFT JOIN [VRS_CharGroup] ON [VRS_CharGroup].[CharGroup]=[VRS_KeyCodes].[CharGroup]"), 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; @@ -293,10 +369,38 @@ bool ZRCola::DBSource::GetKeySequence(const ATL::CComPtr& rs, ZRCo wxCHECK(GetUnicodeCharacter(f, ks.chr), false); } + keyseq::keycode kc1; { ATL::CComPtr f; - wxVERIFY(SUCCEEDED(flds->get_Item(ATL::CComVariant(L"tipka"), &f))); - wxCHECK(GetKeySequence(f, ks.seq), false); + wxVERIFY(SUCCEEDED(flds->get_Item(ATL::CComVariant(L"CharGroup"), &f))); + wxCHECK(GetKeyCode(f, kc1), false); + } + + int keycode; + { + ATL::CComPtr f; + wxVERIFY(SUCCEEDED(flds->get_Item(ATL::CComVariant(L"KeyCode"), &f))); + wxCHECK(GetValue(f, keycode), false); + } + + bool shift; + { + ATL::CComPtr f; + wxVERIFY(SUCCEEDED(flds->get_Item(ATL::CComVariant(L"Shift"), &f))); + wxCHECK(GetValue(f, shift), false); + } + + ks.seq.clear(); + if (kc1.key) { + // First key in the sequence is complete. + ks.seq.push_back(kc1); + keyseq::keycode kc2 = { keycode, shift }; + ks.seq.push_back(kc2); + } else { + // First key in the sequence is only modifier(s). + kc1.key = keycode; + if (shift) kc1.shift = true; + ks.seq.push_back(kc1); } return true; diff --git a/ZRColaCompile/dbsource.h b/ZRColaCompile/dbsource.h index 69c330a..5106aef 100644 --- a/ZRColaCompile/dbsource.h +++ b/ZRColaCompile/dbsource.h @@ -115,6 +115,32 @@ namespace ZRCola { } + /// + /// Gets boolean from ZRCola.zrc database + /// + /// \param[in] f Data field + /// \param[out] val Output boolean value + /// + /// \returns + /// - true when successful + /// - false otherwise + /// + bool GetValue(const ATL::CComPtr& f, bool& val) const; + + + /// + /// Gets integer from ZRCola.zrc database + /// + /// \param[in] f Data field + /// \param[out] val Output integer value + /// + /// \returns + /// - true when successful + /// - false otherwise + /// + bool GetValue(const ATL::CComPtr& f, int& val) const; + + /// /// Gets encoded Unicode character from ZRCola.zrc database /// @@ -141,6 +167,19 @@ namespace ZRCola { bool GetUnicodeString(const ATL::CComPtr& f, std::wstring& str) const; + /// + /// Gets encoded key from ZRCola.zrc database + /// + /// \param[in] f Data field + /// \param[out] kc Output key code + /// + /// \returns + /// - true when successful + /// - false otherwise + /// + bool GetKeyCode(const ATL::CComPtr& f, keyseq::keycode& kc) const; + + /// /// Gets encoded key sequence from ZRCola.zrc database /// diff --git a/output/data/ZRCola.zrcdb b/output/data/ZRCola.zrcdb index d39f33a98a52ffb14dce1168604c71c1e7519e5b..bc4848b2e4196e87e8be0d11e6a26bc1c03b94fa 100644 GIT binary patch delta 5404 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 U->OswZM(Dc*}MFUoh`!t2R~X=TmS$7 delta 5413 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 B7tH_w