882 lines
30 KiB
C++
882 lines
30 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// wxZRColaUTF16CharValidator
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
wxIMPLEMENT_DYNAMIC_CLASS(wxZRColaUTF16CharValidator, wxValidator);
|
|
|
|
|
|
wxZRColaUTF16CharValidator::wxZRColaUTF16CharValidator(wchar_t *val) :
|
|
m_val(val),
|
|
wxValidator()
|
|
{
|
|
}
|
|
|
|
|
|
wxObject* wxZRColaUTF16CharValidator::Clone() const
|
|
{
|
|
return new wxZRColaUTF16CharValidator(*this);
|
|
}
|
|
|
|
|
|
bool wxZRColaUTF16CharValidator::Validate(wxWindow *parent)
|
|
{
|
|
wxASSERT(GetWindow()->IsKindOf(CLASSINFO(wxTextCtrl)));
|
|
wxTextCtrl *ctrl = (wxTextCtrl*)GetWindow();
|
|
if (!ctrl->IsEnabled()) return true;
|
|
|
|
wxString val(ctrl->GetValue());
|
|
return Parse(val, 0, val.Length(), ctrl, parent);
|
|
}
|
|
|
|
|
|
bool wxZRColaUTF16CharValidator::TransferToWindow()
|
|
{
|
|
wxASSERT(GetWindow()->IsKindOf(CLASSINFO(wxTextCtrl)));
|
|
|
|
if (m_val)
|
|
((wxTextCtrl*)GetWindow())->SetValue(wxString::Format(wxT("%04X"), *m_val));
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool wxZRColaUTF16CharValidator::TransferFromWindow()
|
|
{
|
|
wxASSERT(GetWindow()->IsKindOf(CLASSINFO(wxTextCtrl)));
|
|
wxTextCtrl *ctrl = (wxTextCtrl*)GetWindow();
|
|
|
|
wxString val(ctrl->GetValue());
|
|
return Parse(val, 0, val.Length(), ctrl, NULL, m_val);
|
|
}
|
|
|
|
|
|
bool wxZRColaUTF16CharValidator::Parse(const wxString &val_in, size_t i_start, size_t i_end, wxTextCtrl *ctrl, wxWindow *parent, wchar_t *val_out)
|
|
{
|
|
const wxStringCharType *buf = val_in;
|
|
|
|
wchar_t chr = 0;
|
|
for (size_t i = i_start;;) {
|
|
if (i >= i_end) {
|
|
// End of Unicode found.
|
|
if (val_out) *val_out = chr;
|
|
return true;
|
|
} else if (i >= i_start + 4) {
|
|
// Maximum characters exceeded.
|
|
ctrl->SetFocus();
|
|
ctrl->SetSelection(i, i_end);
|
|
wxMessageBox(_("Too many digits in Unicode."), _("Validation conflict"), wxOK | wxICON_EXCLAMATION, parent);
|
|
return false;
|
|
} else if (_T('0') <= buf[i] && buf[i] <= _T('9')) {
|
|
// Digit found.
|
|
chr = (chr << 4) | (buf[i] - _T('0'));
|
|
i++;
|
|
} else if (_T('A') <= buf[i] && buf[i] <= _T('F')) {
|
|
// Capital letter found.
|
|
chr = (chr << 4) | (buf[i] - _T('A') + 10);
|
|
i++;
|
|
} else if (_T('a') <= buf[i] && buf[i] <= _T('f')) {
|
|
// Lower letter found.
|
|
chr = (chr << 4) | (buf[i] - _T('a') + 10);
|
|
i++;
|
|
} else {
|
|
// Invalid character found.
|
|
ctrl->SetFocus();
|
|
ctrl->SetSelection(i, i + 1);
|
|
wxMessageBox(wxString::Format(_("Invalid character in Unicode found: %c"), buf[i]), _("Validation conflict"), wxOK | wxICON_EXCLAMATION, parent);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// wxZRColaUnicodeDumpValidator
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
wxIMPLEMENT_DYNAMIC_CLASS(wxZRColaUnicodeDumpValidator, wxValidator);
|
|
|
|
|
|
wxZRColaUnicodeDumpValidator::wxZRColaUnicodeDumpValidator(wxString *val) :
|
|
m_val(val),
|
|
wxValidator()
|
|
{
|
|
}
|
|
|
|
|
|
wxObject* wxZRColaUnicodeDumpValidator::Clone() const
|
|
{
|
|
return new wxZRColaUnicodeDumpValidator(*this);
|
|
}
|
|
|
|
|
|
bool wxZRColaUnicodeDumpValidator::Validate(wxWindow *parent)
|
|
{
|
|
wxASSERT(GetWindow()->IsKindOf(CLASSINFO(wxTextCtrl)));
|
|
wxTextCtrl *ctrl = (wxTextCtrl*)GetWindow();
|
|
if (!ctrl->IsEnabled()) return true;
|
|
|
|
wxString val(ctrl->GetValue());
|
|
return Parse(val, 0, val.Length(), ctrl, parent);
|
|
}
|
|
|
|
|
|
bool wxZRColaUnicodeDumpValidator::TransferToWindow()
|
|
{
|
|
wxASSERT(GetWindow()->IsKindOf(CLASSINFO(wxTextCtrl)));
|
|
|
|
if (m_val)
|
|
((wxTextCtrl*)GetWindow())->SetValue(ZRCola::GetUnicodeDumpW(m_val->c_str(), m_val->length(), L"+"));
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool wxZRColaUnicodeDumpValidator::TransferFromWindow()
|
|
{
|
|
wxASSERT(GetWindow()->IsKindOf(CLASSINFO(wxTextCtrl)));
|
|
wxTextCtrl *ctrl = (wxTextCtrl*)GetWindow();
|
|
|
|
wxString val(ctrl->GetValue());
|
|
return Parse(val, 0, val.Length(), ctrl, NULL, m_val);
|
|
}
|
|
|
|
|
|
bool wxZRColaUnicodeDumpValidator::Parse(const wxString &val_in, size_t i_start, size_t i_end, wxTextCtrl *ctrl, wxWindow *parent, wxString *val_out)
|
|
{
|
|
const wxStringCharType *buf = val_in;
|
|
|
|
wxString str;
|
|
for (size_t i = i_start;;) {
|
|
const wxStringCharType *buf_next;
|
|
wchar_t chr;
|
|
if ((buf_next = wmemchr(buf + i, L'+', i_end - i)) != NULL) {
|
|
// Unicode dump separator found.
|
|
if (!wxZRColaUTF16CharValidator::Parse(val_in, i, buf_next - buf, ctrl, parent, &chr))
|
|
return false;
|
|
str += chr;
|
|
i = buf_next - buf + 1;
|
|
} else if (wxZRColaUTF16CharValidator::Parse(val_in, i, i_end, ctrl, parent, &chr)) {
|
|
// The rest of the FQDN parsed succesfully.
|
|
if (chr) str += chr;
|
|
if (val_out) *val_out = str;
|
|
return true;
|
|
} else
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// wxZRColaCharSelect
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
wxDEFINE_EVENT(wxEVT_SEARCH_COMPLETE, wxThreadEvent);
|
|
|
|
|
|
wxZRColaCharSelect::wxZRColaCharSelect(wxWindow* parent) :
|
|
m_searchChanged(false),
|
|
m_unicodeChanged(false),
|
|
m_searchThread(NULL),
|
|
wxZRColaCharSelectBase(parent)
|
|
{
|
|
// Set tag lookup locale.
|
|
wxLanguage language = dynamic_cast<ZRColaApp*>(wxTheApp)->m_lang_ui;
|
|
if (wxLANGUAGE_DEFAULT == language ||
|
|
wxLANGUAGE_ENGLISH <= language && language <= wxLANGUAGE_ENGLISH_ZIMBABWE) m_locale = MAKELCID(MAKELANGID(LANG_ENGLISH , SUBLANG_DEFAULT), SORT_DEFAULT);
|
|
else if (wxLANGUAGE_RUSSIAN <= language && language <= wxLANGUAGE_RUSSIAN_UKRAINE ) m_locale = MAKELCID(MAKELANGID(LANG_RUSSIAN , SUBLANG_DEFAULT), SORT_DEFAULT);
|
|
else if (wxLANGUAGE_SLOVENIAN == language ) m_locale = MAKELCID(MAKELANGID(LANG_SLOVENIAN, SUBLANG_DEFAULT), SORT_DEFAULT);
|
|
else m_locale = MAKELCID(MAKELANGID(LANG_ENGLISH , SUBLANG_DEFAULT), SORT_DEFAULT);
|
|
|
|
Connect(wxID_ANY, wxEVT_SEARCH_COMPLETE, wxThreadEventHandler(wxZRColaCharSelect::OnSearchComplete), NULL, this);
|
|
|
|
m_search_more->SetLabel(_(L"▸ Search Options"));
|
|
|
|
m_unicode->SetValidator(wxZRColaUnicodeDumpValidator(&m_char));
|
|
|
|
// Fill categories.
|
|
auto app = dynamic_cast<ZRColaApp*>(wxTheApp);
|
|
for (size_t i = 0, n = app->m_cc_db.idxRank.size(); i < n; i++) {
|
|
const auto &cc = app->m_cc_db.idxRank[i];
|
|
int idx = m_categories->Insert(wxGetTranslation(wxString(cc.name(), cc.name_len()), wxT("ZRCola-zrcdb")), i);
|
|
m_categories->Check(idx);
|
|
m_ccOrder.insert(std::make_pair(cc.cat, idx));
|
|
}
|
|
|
|
ResetResults();
|
|
|
|
NavigationState state;
|
|
state.m_char = m_char;
|
|
state.m_related.m_selected.SetCol(m_gridRelated->GetGridCursorCol());
|
|
state.m_related.m_selected.SetRow(m_gridRelated->GetGridCursorRow());
|
|
m_historyCursor = m_history.insert(m_history.end(), state);
|
|
}
|
|
|
|
|
|
wxZRColaCharSelect::~wxZRColaCharSelect()
|
|
{
|
|
if (m_searchThread)
|
|
m_searchThread->Delete();
|
|
|
|
Disconnect(wxID_ANY, wxEVT_SEARCH_COMPLETE, wxThreadEventHandler(wxZRColaCharSelect::OnSearchComplete), NULL, this);
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnIdle(wxIdleEvent& event)
|
|
{
|
|
event.Skip();
|
|
|
|
if (m_unicodeChanged) {
|
|
if (m_unicode->GetValidator()->TransferFromWindow()) {
|
|
auto app = dynamic_cast<ZRColaApp*>(wxTheApp);
|
|
|
|
m_gridPreview->SetCellValue(m_char, 0, 0);
|
|
|
|
std::unique_ptr<ZRCola::character_db::character> chr((ZRCola::character_db::character*)new char[sizeof(ZRCola::character_db::character) + sizeof(wchar_t)*m_char.length()]);
|
|
chr->ZRCola::character_db::character::character(m_char.data(), m_char.length());
|
|
size_t start;
|
|
if (app->m_chr_db.idxChr.find(*chr, start)) {
|
|
const auto &chr = app->m_chr_db.idxChr[start];
|
|
// Update character description.
|
|
m_description->SetValue(wxString(chr.desc(), chr.desc_len()));
|
|
{
|
|
// See if this character has a key sequence registered.
|
|
std::unique_ptr<ZRCola::keyseq_db::keyseq> ks((ZRCola::keyseq_db::keyseq*)new char[sizeof(ZRCola::keyseq_db::keyseq) + sizeof(wchar_t)*m_char.length()]);
|
|
ks->ZRCola::keyseq_db::keyseq::keyseq(NULL, 0, m_char.data(), m_char.length());
|
|
ZRCola::keyseq_db::indexKey::size_type start;
|
|
if (app->m_ks_db.idxChr.find(*ks, start)) {
|
|
ZRCola::keyseq_db::keyseq &seq = app->m_ks_db.idxChr[start];
|
|
wxString ks_str;
|
|
if (ZRCola::keyseq_db::GetSequenceAsText(seq.seq(), seq.seq_len(), ks_str))
|
|
m_shortcut->SetValue(ks_str);
|
|
else
|
|
m_shortcut->SetValue(wxEmptyString);
|
|
} else
|
|
m_shortcut->SetValue(wxEmptyString);
|
|
}
|
|
{
|
|
char cc[sizeof(ZRCola::chrcat_db::chrcat)] = {};
|
|
((ZRCola::chrcat_db::chrcat*)cc)->cat= chr.cat;
|
|
size_t start;
|
|
// Update character category.
|
|
if (app->m_cc_db.idxChrCat.find(*((ZRCola::chrcat_db::chrcat*)cc), start)) {
|
|
const auto &cat = app->m_cc_db.idxChrCat[start];
|
|
m_category->SetValue(wxGetTranslation(wxString(cat.name(), cat.name_len()), wxT("ZRCola-zrcdb")));
|
|
} else
|
|
m_category->SetValue(wxEmptyString);
|
|
}
|
|
// Update related characters.
|
|
m_gridRelated->SetCharacters(wxString(chr.rel(), chr.rel_end()));
|
|
} else {
|
|
m_description->SetValue(wxEmptyString);
|
|
m_shortcut->SetValue(wxEmptyString);
|
|
m_category->SetValue(wxEmptyString);
|
|
m_gridRelated->ClearGrid();
|
|
}
|
|
|
|
// Find character tags.
|
|
std::list<std::wstring> tag_names;
|
|
std::unique_ptr<ZRCola::chrtag_db::chrtag> ct((ZRCola::chrtag_db::chrtag*)new char[sizeof(ZRCola::chrtag_db::chrtag) + sizeof(wchar_t)*m_char.length()]);
|
|
ct->ZRCola::chrtag_db::chrtag::chrtag(m_char.data(), m_char.length());
|
|
size_t end;
|
|
if (app->m_ct_db.idxChr.find(*ct, start, end)) {
|
|
for (size_t i = start; i < end; i++) {
|
|
const ZRCola::chrtag_db::chrtag &ct = app->m_ct_db.idxChr[i];
|
|
|
|
// Find tag names.
|
|
char tn[sizeof(ZRCola::tagname_db::tagname)] = {};
|
|
((ZRCola::tagname_db::tagname*)tn)->locale = m_locale;
|
|
((ZRCola::tagname_db::tagname*)tn)->tag = ct.tag;
|
|
size_t start, end;
|
|
if (app->m_tn_db.idxTag.find(*((ZRCola::tagname_db::tagname*)tn), start, end)) {
|
|
for (size_t i = start; i < end; i++) {
|
|
const ZRCola::tagname_db::tagname &tn = app->m_tn_db.idxTag[i];
|
|
|
|
// Add tag name to the list (prevent duplicates).
|
|
for (auto name = tag_names.cbegin(), name_end = tag_names.cend();; ++name) {
|
|
if (name == name_end) {
|
|
// Add name to the list.
|
|
tag_names.push_back(std::wstring(tn.name(), tn.name_end()));
|
|
break;
|
|
} else if (ZRCola::tagname_db::tagname::CompareName(m_locale, name->data(), (unsigned __int16)name->length(), tn.name(), tn.name_len()) == 0)
|
|
// Name is already on the list.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
wxString tags;
|
|
for (auto name = tag_names.cbegin(), name_end = tag_names.cend(); name != name_end; ++name) {
|
|
if (!tags.empty())
|
|
tags += _(", ");
|
|
tags += *name;
|
|
}
|
|
m_tags->SetValue(tags);
|
|
|
|
m_gridRelated->GoToCell(m_historyCursor->m_related.m_selected);
|
|
|
|
wxGridCellCoords coord(m_gridResults->GetCharacterCoords(m_char));
|
|
if (coord.GetRow() != -1 && coord.GetCol() != -1) {
|
|
m_gridResults->GoToCell(coord);
|
|
m_gridResults->SetGridCursor(coord);
|
|
} else
|
|
m_gridResults->ClearSelection();
|
|
}
|
|
|
|
m_unicodeChanged = false;
|
|
} else if (m_searchChanged) {
|
|
if (m_searchThread)
|
|
m_searchThread->Delete();
|
|
|
|
wxString val(m_search->GetValue());
|
|
if (!val.IsEmpty()) {
|
|
auto app = dynamic_cast<ZRColaApp*>(wxTheApp);
|
|
|
|
m_searchThread = new SearchThread(this);
|
|
|
|
m_searchThread->m_search.assign(val.c_str(), val.Length());
|
|
|
|
// Select categories.
|
|
for (size_t i = 0, n = app->m_cc_db.idxRank.size(); i < n; i++) {
|
|
const auto &cc = app->m_cc_db.idxRank[i];
|
|
if (m_categories->IsChecked(i))
|
|
m_searchThread->m_cats.insert(cc.cat);
|
|
}
|
|
|
|
if (m_searchThread->Run() != wxTHREAD_NO_ERROR) {
|
|
wxFAIL_MSG("Can't create the thread!");
|
|
delete m_searchThread;
|
|
m_searchThread = NULL;
|
|
}
|
|
} else
|
|
ResetResults();
|
|
|
|
m_searchChanged = false;
|
|
}
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnSearchText(wxCommandEvent& event)
|
|
{
|
|
event.Skip();
|
|
|
|
m_searchChanged = true;
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnSearchMore(wxHyperlinkEvent& event)
|
|
{
|
|
event.StopPropagation();
|
|
|
|
if (m_search_panel->IsShown()) {
|
|
m_search_panel->Show(false);
|
|
m_search_more->SetLabel(_(L"▸ Search Options"));
|
|
} else {
|
|
m_search_panel->Show(true);
|
|
m_search_more->SetLabel(_(L"▾ Search Options"));
|
|
}
|
|
|
|
this->Layout();
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnCategoriesAll(wxHyperlinkEvent& event)
|
|
{
|
|
event.StopPropagation();
|
|
|
|
auto app = dynamic_cast<ZRColaApp*>(wxTheApp);
|
|
for (size_t i = 0, n = app->m_cc_db.idxRank.size(); i < n; i++)
|
|
m_categories->Check(i, true);
|
|
|
|
m_searchChanged = true;
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnCategoriesNone(wxHyperlinkEvent& event)
|
|
{
|
|
event.StopPropagation();
|
|
|
|
auto app = dynamic_cast<ZRColaApp*>(wxTheApp);
|
|
for (size_t i = 0, n = app->m_cc_db.idxRank.size(); i < n; i++)
|
|
m_categories->Check(i, false);
|
|
|
|
m_searchChanged = true;
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnCategoriesInvert(wxHyperlinkEvent& event)
|
|
{
|
|
event.StopPropagation();
|
|
|
|
auto app = dynamic_cast<ZRColaApp*>(wxTheApp);
|
|
for (size_t i = 0, n = app->m_cc_db.idxRank.size(); i < n; i++)
|
|
m_categories->Check(i, !m_categories->IsChecked(i));
|
|
|
|
m_searchChanged = true;
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnCategoriesToggle(wxCommandEvent& event)
|
|
{
|
|
event.Skip();
|
|
|
|
m_searchChanged = true;
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnSearchComplete(wxThreadEvent& event)
|
|
{
|
|
event.Skip();
|
|
|
|
if (m_searchThread) {
|
|
// Display results.
|
|
wxArrayString chars;
|
|
chars.reserve(m_searchThread->m_hits.size());
|
|
for (auto i = m_searchThread->m_hits.cbegin(), i_end = m_searchThread->m_hits.cend(); i != i_end; ++i)
|
|
chars.Add(i->second);
|
|
m_gridResults->SetCharacters(chars);
|
|
|
|
m_searchThread->Delete();
|
|
m_searchThread = NULL;
|
|
|
|
m_gridResults->Scroll(0, 0);
|
|
}
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnResultSelectCell(wxGridEvent& event)
|
|
{
|
|
if (m_unicodeChanged) return;
|
|
|
|
wxString val(m_gridResults->GetCellValue(event.GetRow(), event.GetCol()));
|
|
if (!val.IsEmpty())
|
|
NavigateTo(val);
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnResultCellDClick(wxGridEvent& event)
|
|
{
|
|
event.Skip();
|
|
|
|
wxString val(m_gridResults->GetCellValue(event.GetRow(), event.GetCol()));
|
|
if (!val.IsEmpty()) {
|
|
NavigateTo(val);
|
|
wxCommandEvent e(wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK);
|
|
m_sdbSizerButtonsOK->GetEventHandler()->ProcessEvent(e);
|
|
}
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnResultsKeyDown(wxKeyEvent& event)
|
|
{
|
|
switch (event.GetKeyCode()) {
|
|
case WXK_RETURN:
|
|
case WXK_NUMPAD_ENTER:
|
|
wxString val(m_gridResults->GetCellValue(m_gridResults->GetCursorRow(), m_gridResults->GetCursorColumn()));
|
|
if (!val.IsEmpty()) {
|
|
NavigateTo(val);
|
|
wxCommandEvent e(wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK);
|
|
m_sdbSizerButtonsOK->GetEventHandler()->ProcessEvent(e);
|
|
|
|
event.StopPropagation();
|
|
return;
|
|
}
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnRecentSelectCell(wxGridEvent& event)
|
|
{
|
|
if (m_unicodeChanged) return;
|
|
|
|
wxString val(m_gridRecent->GetCellValue(event.GetRow(), event.GetCol()));
|
|
if (!val.IsEmpty())
|
|
NavigateTo(val);
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnRecentCellDClick(wxGridEvent& event)
|
|
{
|
|
event.Skip();
|
|
|
|
wxString val(m_gridRecent->GetCellValue(event.GetRow(), event.GetCol()));
|
|
if (!val.IsEmpty()) {
|
|
NavigateTo(val);
|
|
wxCommandEvent e(wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK);
|
|
m_sdbSizerButtonsOK->GetEventHandler()->ProcessEvent(e);
|
|
}
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnRecentKeyDown(wxKeyEvent& event)
|
|
{
|
|
switch (event.GetKeyCode()) {
|
|
case WXK_RETURN:
|
|
case WXK_NUMPAD_ENTER:
|
|
wxString val(m_gridRecent->GetCellValue(m_gridRecent->GetCursorRow(), m_gridRecent->GetCursorColumn()));
|
|
if (!val.IsEmpty()) {
|
|
NavigateTo(val);
|
|
wxCommandEvent e(wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK);
|
|
m_sdbSizerButtonsOK->GetEventHandler()->ProcessEvent(e);
|
|
|
|
event.StopPropagation();
|
|
return;
|
|
}
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnUnicodeText(wxCommandEvent& event)
|
|
{
|
|
event.Skip();
|
|
|
|
m_unicodeChanged = true;
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnPreviewKeyDown(wxKeyEvent& event)
|
|
{
|
|
int key_code = event.GetKeyCode();
|
|
if (key_code == WXK_TAB != NULL) {
|
|
wxNavigationKeyEvent eventNav;
|
|
eventNav.SetDirection(!event.ShiftDown());
|
|
eventNav.SetWindowChange(event.ControlDown());
|
|
eventNav.SetEventObject(this);
|
|
|
|
if (HandleWindowEvent(eventNav))
|
|
return;
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnNavigateBack(wxHyperlinkEvent& event)
|
|
{
|
|
event.StopPropagation();
|
|
|
|
NavigateBy(-1);
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnNavigateForward(wxHyperlinkEvent& event)
|
|
{
|
|
event.StopPropagation();
|
|
|
|
NavigateBy(+1);
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnRelatedSelectCell(wxGridEvent& event)
|
|
{
|
|
if (m_unicodeChanged) return;
|
|
|
|
wxString val(m_gridRelated->GetCellValue(event.GetRow(), event.GetCol()));
|
|
if (!val.IsEmpty())
|
|
NavigateTo(val);
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::OnOKButtonClick(wxCommandEvent& event)
|
|
{
|
|
event.Skip();
|
|
|
|
const wxArrayString &recent = m_gridRecent->GetCharacters();
|
|
wxArrayString val;
|
|
val.reserve(recent.GetCount() + 1);
|
|
val.Add(m_char);
|
|
for (size_t i = 0, n = recent.GetCount(); i < n; i++) {
|
|
const wxString &c = recent[i];
|
|
if (c != m_char)
|
|
val.Add(c);
|
|
}
|
|
m_gridRecent->SetCharacters(val);
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::ResetResults()
|
|
{
|
|
// Fill the results.
|
|
auto app = dynamic_cast<ZRColaApp*>(wxTheApp);
|
|
size_t i, n = app->m_chr_db.idxChr.size();
|
|
wxArrayString val;
|
|
val.reserve(n);
|
|
for (i = 0; i < n; i++) {
|
|
const auto &chr = app->m_chr_db.idxChr[i];
|
|
auto idx = m_ccOrder.find(chr.cat);
|
|
if (idx == m_ccOrder.end() || m_categories->IsChecked(idx->second))
|
|
val.Add(wxString(chr.chr(), chr.chr_len()));
|
|
}
|
|
m_gridResults->SetCharacters(val);
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::NavigateBy(int offset)
|
|
{
|
|
if (offset != 0) {
|
|
// Update history state
|
|
m_historyCursor->m_related.m_selected.SetCol(m_gridRelated->GetGridCursorCol());
|
|
m_historyCursor->m_related.m_selected.SetRow(m_gridRelated->GetGridCursorRow());
|
|
|
|
if (offset < 0) {
|
|
while (m_historyCursor != m_history.begin() && offset) {
|
|
--m_historyCursor; offset++;
|
|
m_char = m_historyCursor->m_char;
|
|
m_unicodeChanged = true;
|
|
}
|
|
} else {
|
|
while (offset) {
|
|
++m_historyCursor;
|
|
if (m_historyCursor == m_history.end()) {
|
|
// We're past the last history entry.
|
|
--m_historyCursor;
|
|
break;
|
|
}
|
|
offset--;
|
|
m_char = m_historyCursor->m_char;
|
|
m_unicodeChanged = true;
|
|
}
|
|
}
|
|
|
|
m_navigateBack->Enable(m_historyCursor != m_history.begin());
|
|
auto cursor_next = m_historyCursor;
|
|
++cursor_next;
|
|
m_navigateForward->Enable(cursor_next != m_history.end());
|
|
|
|
if (m_unicodeChanged)
|
|
m_unicode->GetValidator()->TransferToWindow();
|
|
}
|
|
}
|
|
|
|
|
|
void wxZRColaCharSelect::NavigateTo(const wxString &c)
|
|
{
|
|
if (m_char != c) {
|
|
// Update history state
|
|
m_historyCursor->m_related.m_selected.SetCol(m_gridRelated->GetGridCursorCol());
|
|
m_historyCursor->m_related.m_selected.SetRow(m_gridRelated->GetGridCursorRow());
|
|
|
|
++m_historyCursor;
|
|
|
|
// Create new state.
|
|
NavigationState state;
|
|
state.m_char = m_char = c;
|
|
state.m_related.m_selected.SetCol(0);
|
|
state.m_related.m_selected.SetRow(0);
|
|
m_historyCursor = m_history.insert(m_historyCursor, state);
|
|
|
|
// Purge the history's tail.
|
|
auto cursor_next = m_historyCursor;
|
|
++cursor_next;
|
|
m_history.erase(cursor_next, m_history.end());
|
|
|
|
m_unicode->GetValidator()->TransferToWindow();
|
|
|
|
m_navigateBack->Enable(true);
|
|
m_navigateForward->Enable(false);
|
|
}
|
|
}
|
|
|
|
|
|
wxZRColaCharSelect::SearchThread::SearchThread(wxZRColaCharSelect *parent) :
|
|
m_parent(parent),
|
|
wxThread(wxTHREAD_JOINABLE)
|
|
{
|
|
//// This is a worker thread. Set priority between minimal and normal.
|
|
//SetPriority((wxPRIORITY_MIN + wxPRIORITY_DEFAULT) / 2);
|
|
}
|
|
|
|
|
|
wxThread::ExitCode wxZRColaCharSelect::SearchThread::Entry()
|
|
{
|
|
auto app = dynamic_cast<ZRColaApp*>(wxTheApp);
|
|
std::map<std::wstring, ZRCola::charrank_t> hits;
|
|
|
|
if (TestDestroy()) return (wxThread::ExitCode)1;
|
|
|
|
{
|
|
// Search by tags: Get tags with given names. Then, get characters of found tags.
|
|
std::map<ZRCola::tagid_t, unsigned __int16> hits_tag;
|
|
if (!app->m_tn_db.Search(m_search.c_str(), m_parent->m_locale, hits_tag, TestDestroyS, this)) return (wxThread::ExitCode)1;
|
|
if (!app->m_ct_db.Search(hits_tag, app->m_chr_db, m_cats, hits, TestDestroyS, this)) return (wxThread::ExitCode)1;
|
|
}
|
|
|
|
{
|
|
// Search by description and merge results.
|
|
std::map<std::wstring, ZRCola::charrank_t> hits_sub;
|
|
if (!app->m_chr_db.Search(m_search.c_str(), m_cats, hits, hits_sub, TestDestroyS, this)) return (wxThread::ExitCode)1;
|
|
for (auto i = hits_sub.cbegin(), i_end = hits_sub.cend(); i != i_end; ++i) {
|
|
if (TestDestroy()) return (wxThread::ExitCode)1;
|
|
auto idx = hits.find(i->first);
|
|
if (idx == hits.end())
|
|
hits.insert(std::make_pair(i->first, i->second / 4));
|
|
else
|
|
idx->second += i->second / 4;
|
|
}
|
|
}
|
|
|
|
// Get best rank.
|
|
ZRCola::charrank_t rank_ref = 0;
|
|
for (auto i = hits.cbegin(), i_end = hits.cend(); i != i_end; ++i) {
|
|
if (TestDestroy()) return (wxThread::ExitCode)1;
|
|
if (i->second > rank_ref)
|
|
rank_ref = i->second;
|
|
}
|
|
|
|
// Now sort the characters by rank (taking only top 3/4 by rank).
|
|
ZRCola::charrank_t rank_threshold = rank_ref*3/4;
|
|
m_hits.reserve(hits.size());
|
|
for (auto i = hits.cbegin(), i_end = hits.cend(); i != i_end; ++i) {
|
|
if (TestDestroy()) return (wxThread::ExitCode)1;
|
|
if (i->second > rank_threshold)
|
|
m_hits.push_back(std::make_pair(i->second, i->first));
|
|
}
|
|
std::qsort(m_hits.data(), m_hits.size(), sizeof(std::pair<ZRCola::charrank_t, std::wstring>), CompareHits);
|
|
|
|
// Signal the event handler that this thread is going to be destroyed.
|
|
// NOTE: here we assume that using the m_parent pointer is safe,
|
|
// (in this case this is assured by the wxZRColaCharSelect destructor)
|
|
wxQueueEvent(m_parent, new wxThreadEvent(wxEVT_SEARCH_COMPLETE));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int __cdecl wxZRColaCharSelect::SearchThread::CompareHits(const void *a, const void *b)
|
|
{
|
|
const std::pair<ZRCola::charrank_t, wchar_t> *_a = (const std::pair<ZRCola::charrank_t, wchar_t>*)a;
|
|
const std::pair<ZRCola::charrank_t, wchar_t> *_b = (const std::pair<ZRCola::charrank_t, wchar_t>*)b;
|
|
|
|
if (_a->first > _b->first) return -1;
|
|
else if (_a->first < _b->first) return 1;
|
|
|
|
if (_a->second < _b->second) return -1;
|
|
else if (_a->second > _b->second) return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool __cdecl wxZRColaCharSelect::SearchThread::TestDestroyS(void *cookie)
|
|
{
|
|
return static_cast<wxZRColaCharSelect::SearchThread*>(cookie)->TestDestroy();
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// wxPersistentZRColaCharSelect
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
wxPersistentZRColaCharSelect::wxPersistentZRColaCharSelect(wxZRColaCharSelect *wnd) : wxPersistentDialog(wnd)
|
|
{
|
|
}
|
|
|
|
|
|
void wxPersistentZRColaCharSelect::Save() const
|
|
{
|
|
wxPersistentDialog::Save();
|
|
|
|
auto wnd = static_cast<const wxZRColaCharSelect*>(GetWindow()); // dynamic_cast is not reliable as we are typically called late in the wxTopLevelWindowMSW destructor.
|
|
auto app = dynamic_cast<ZRColaApp*>(wxTheApp);
|
|
|
|
wxString str, str2;
|
|
auto &recent = wnd->m_gridRecent->GetCharacters();
|
|
for (size_t i = 0, n = recent.GetCount(); i < n; i++) {
|
|
if (i) str2 += wxT('|');
|
|
auto &chr = recent[i];
|
|
for (size_t j = 0, m = chr.Length(); j < m; j++) {
|
|
if (j) str2 += wxT('+');
|
|
str2 += wxString::Format(wxT("%04X"), chr[j]);
|
|
}
|
|
if (chr.Length() == 1)
|
|
str += chr[0];
|
|
}
|
|
SaveValue(wxT("recentChars" ), str ); // Save in legacy format for backward compatibility.
|
|
SaveValue(wxT("recentChars2"), str2); // Save in native format
|
|
|
|
for (size_t i = 0, n = app->m_cc_db.idxRank.size(); i < n; i++) {
|
|
const auto &cc = app->m_cc_db.idxRank[i];
|
|
wxString name(wxT("category"));
|
|
name.Append(cc.cat.data, _countof(cc.cat.data));
|
|
SaveValue(name, wnd->m_categories->IsChecked(i));
|
|
}
|
|
|
|
SaveValue(wxT("searchPanel"), wnd->m_search_panel->IsShown());
|
|
}
|
|
|
|
|
|
bool wxPersistentZRColaCharSelect::Restore()
|
|
{
|
|
auto wnd = dynamic_cast<wxZRColaCharSelect*>(GetWindow());
|
|
auto app = dynamic_cast<ZRColaApp*>(wxTheApp);
|
|
|
|
wxString str;
|
|
if (RestoreValue(wxT("recentChars2"), &str)) {
|
|
// Native format found.
|
|
wxArrayString val;
|
|
for (wxStringTokenizer tok(str, wxT("|")); tok.HasMoreTokens(); ) {
|
|
wxString chr;
|
|
for (wxStringTokenizer tok_chr(tok.GetNextToken(), wxT("+")); tok_chr.HasMoreTokens(); )
|
|
chr += (wchar_t)_tcstoul(tok_chr.GetNextToken().c_str(), NULL, 16);
|
|
val.Add(chr);
|
|
}
|
|
wnd->m_gridRecent->SetCharacters(val);
|
|
} else if (RestoreValue(wxT("recentChars"), &str)) {
|
|
// Legacy value found.
|
|
wxArrayString val;
|
|
for (size_t i = 0, n = str.Length(); i < n; i++)
|
|
val.Add(wxString(1, str[i]));
|
|
wnd->m_gridRecent->SetCharacters(val);
|
|
}
|
|
|
|
for (size_t i = 0, n = app->m_cc_db.idxRank.size(); i < n; i++) {
|
|
const auto &cc = app->m_cc_db.idxRank[i];
|
|
wxString name(wxT("category"));
|
|
name.Append(cc.cat.data, _countof(cc.cat.data));
|
|
bool val;
|
|
if (RestoreValue(name, &val))
|
|
wnd->m_categories->Check(i, val);
|
|
}
|
|
|
|
bool search_panel;
|
|
if (RestoreValue(wxT("searchPanel"), &search_panel)) {
|
|
if (search_panel) {
|
|
wnd->m_search_panel->Show(true);
|
|
wnd->m_search_more->SetLabel(_(L"▾ Search Options"));
|
|
} else {
|
|
wnd->m_search_panel->Show(false);
|
|
wnd->m_search_more->SetLabel(_(L"▸ Search Options"));
|
|
}
|
|
}
|
|
|
|
wnd->ResetResults();
|
|
|
|
return wxPersistentDialog::Restore();
|
|
}
|