From 155fb03c5a0ab77d79231cd4383ecbde338eb7fa Mon Sep 17 00:00:00 2001 From: Simon Rozman Date: Fri, 13 May 2016 12:11:38 +0200 Subject: [PATCH] Character search moved to separate thread for smoother experience --- ZRCola/ZRCola.fbp | 13 +- ZRCola/zrcolachrslct.cpp | 211 ++++++++++++++--------- ZRCola/zrcolachrslct.h | 36 +++- ZRCola/zrcolagui.cpp | 7 +- ZRCola/zrcolagui.h | 8 - lib/libZRCola/include/zrcola/character.h | 4 +- lib/libZRCola/src/character.cpp | 14 +- 7 files changed, 182 insertions(+), 111 deletions(-) diff --git a/ZRCola/ZRCola.fbp b/ZRCola/ZRCola.fbp index dc4db79..f4f46ef 100644 --- a/ZRCola/ZRCola.fbp +++ b/ZRCola/ZRCola.fbp @@ -2432,7 +2432,7 @@ 1 1 - wxTE_PROCESS_ENTER + 0 @@ -2469,7 +2469,7 @@ OnSearchText - OnSearchEnter + @@ -3506,15 +3506,6 @@ - - 0 - wxID_TIMER_SEARCH - m_timerSearch - 1 - 1000 - protected - OnSearchTimer - diff --git a/ZRCola/zrcolachrslct.cpp b/ZRCola/zrcolachrslct.cpp index cdc09c5..0aa3741 100644 --- a/ZRCola/zrcolachrslct.cpp +++ b/ZRCola/zrcolachrslct.cpp @@ -20,32 +20,22 @@ #include "stdafx.h" -static int __cdecl compare_hits(const void *a, const void *b) -{ - const std::pair *_a = (const std::pair*)a; - const std::pair *_b = (const std::pair*)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; -} - - - ////////////////////////////////////////////////////////////////////////// // wxZRColaCharSelect ////////////////////////////////////////////////////////////////////////// +wxDEFINE_EVENT(wxEVT_SEARCH_COMPLETE, wxThreadEvent); + + wxZRColaCharSelect::wxZRColaCharSelect(wxWindow* parent) : m_searchChanged(false), m_unicodeChanged(false), m_char(0), + m_searchThread(NULL), wxZRColaCharSelectBase(parent) { + Connect(wxID_ANY, wxEVT_SEARCH_COMPLETE, wxThreadEventHandler(wxZRColaCharSelect::OnSearchComplete), NULL, this); + m_unicode->SetValidator(wxHexValidator(&m_char)); // Fill categories. @@ -61,6 +51,15 @@ wxZRColaCharSelect::wxZRColaCharSelect(wxWindow* parent) : } +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(); @@ -90,7 +89,31 @@ void wxZRColaCharSelect::OnIdle(wxIdleEvent& event) m_unicodeChanged = false; } else if (m_searchChanged) { - m_timerSearch.Start(1000, true); + if (m_searchThread) + m_searchThread->Delete(); + + wxString val(m_search->GetValue()); + if (!val.IsEmpty()) { + ZRColaApp *app = (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.idxRnk.size(); i < n; i++) { + const ZRCola::chrcat_db::chrcat &cc = app->m_cc_db.idxRnk[i]; + if (m_categories->IsChecked(i)) + m_searchThread->m_cats.insert(cc.id); + } + + 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; } @@ -101,75 +124,35 @@ void wxZRColaCharSelect::OnSearchText(wxCommandEvent& event) { event.Skip(); - m_timerSearch.Stop(); m_searchChanged = true; } -void wxZRColaCharSelect::OnSearchEnter(wxCommandEvent& event) -{ - event.Skip(); - - m_timerSearch.Stop(); - wxTimerEvent e(m_timerSearch); - GetEventHandler()->ProcessEvent(e); - - m_searchChanged = false; -} - - -void wxZRColaCharSelect::OnSearchTimer(wxTimerEvent& event) -{ - wxString val(m_search->GetValue()); - if (!val.IsEmpty()) { - ZRColaApp *app = (ZRColaApp*)wxTheApp; - std::map hits; - std::set cats; - - // Select categories. - for (size_t i = 0, n = app->m_cc_db.idxRnk.size(); i < n; i++) { - const ZRCola::chrcat_db::chrcat &cc = app->m_cc_db.idxRnk[i]; - if (m_categories->IsChecked(i)) - cats.insert(cc.id); - } - - { - // Search by indexes and merge results. - std::map hits_sub; - app->m_chr_db.Search(val.c_str(), cats, hits, hits_sub); - for (std::map::const_iterator i = hits_sub.cbegin(), i_end = hits_sub.cend(); i != i_end; ++i) { - std::map::iterator 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; - } - } - - // Now sort the characters by rank. - std::vector< std::pair > hits2; - hits2.reserve(hits.size()); - for (std::map::const_iterator i = hits.cbegin(), i_end = hits.cend(); i != i_end; ++i) - hits2.push_back(std::make_pair(i->second, i->first)); - std::qsort(hits2.data(), hits2.size(), sizeof(std::pair), compare_hits); - - // Display results. - wxString chars; - chars.reserve(hits2.size()); - for (std::vector< std::pair >::const_iterator i = hits2.cbegin(), i_end = hits2.cend(); i != i_end; ++i) - chars += i->second; - m_gridResults->SetCharacters(chars); - } else - ResetResults(); - - m_gridResults->Scroll(0, 0); -} - - void wxZRColaCharSelect::OnCategoriesToggle(wxCommandEvent& event) { - m_timerSearch.Stop(); - m_timerSearch.Start(500, true); + event.Skip(); + + m_searchChanged = true; +} + + +void wxZRColaCharSelect::OnSearchComplete(wxThreadEvent& event) +{ + event.Skip(); + + if (m_searchThread) { + // Display results. + wxString chars; + chars.reserve(m_searchThread->m_hits.size()); + for (std::vector< std::pair >::const_iterator i = m_searchThread->m_hits.cbegin(), i_end = m_searchThread->m_hits.cend(); i != i_end; ++i) + chars += i->second; + m_gridResults->SetCharacters(chars); + + m_searchThread->Delete(); + m_searchThread = NULL; + + m_gridResults->Scroll(0, 0); + } } @@ -305,6 +288,74 @@ void wxZRColaCharSelect::ResetResults() } +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() +{ + ZRColaApp *app = (ZRColaApp*)wxTheApp; + std::map hits; + + if (TestDestroy()) return (wxThread::ExitCode)1; + + { + // Search by indexes and merge results. + std::map hits_sub; + if (!app->m_chr_db.Search(m_search.c_str(), m_cats, hits, hits_sub, TestDestroyS, this)) return (wxThread::ExitCode)1; + for (std::map::const_iterator i = hits_sub.cbegin(), i_end = hits_sub.cend(); i != i_end; ++i) { + if (TestDestroy()) return (wxThread::ExitCode)1; + std::map::iterator 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; + } + } + + // Now sort the characters by rank. + m_hits.reserve(hits.size()); + for (std::map::const_iterator i = hits.cbegin(), i_end = hits.cend(); i != i_end; ++i) { + if (TestDestroy()) return (wxThread::ExitCode)1; + m_hits.push_back(std::make_pair(i->second, i->first)); + } + std::qsort(m_hits.data(), m_hits.size(), sizeof(std::pair), 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 *_a = (const std::pair*)a; + const std::pair *_b = (const std::pair*)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(cookie)->TestDestroy(); +} + + ////////////////////////////////////////////////////////////////////////// // wxPersistentZRColaCharSelect ////////////////////////////////////////////////////////////////////////// diff --git a/ZRCola/zrcolachrslct.h b/ZRCola/zrcolachrslct.h index 6b2ef91..2646ad6 100644 --- a/ZRCola/zrcolachrslct.h +++ b/ZRCola/zrcolachrslct.h @@ -30,9 +30,14 @@ class wxZRColaCharSelect; #include #include #include +#include +#include #include +wxDECLARE_EVENT(wxEVT_SEARCH_COMPLETE, wxThreadEvent); + + /// /// ZRCola character select dialog /// @@ -40,15 +45,16 @@ class wxZRColaCharSelect : public wxZRColaCharSelectBase { public: wxZRColaCharSelect(wxWindow* parent); + virtual ~wxZRColaCharSelect(); friend class wxPersistentZRColaCharSelect; // Allow saving/restoring window state. + friend class SearchThread; // For search thread back-notifications protected: virtual void OnIdle(wxIdleEvent& event); virtual void OnSearchText(wxCommandEvent& event); - virtual void OnSearchEnter(wxCommandEvent& event); - virtual void OnSearchTimer(wxTimerEvent& event); virtual void OnCategoriesToggle(wxCommandEvent& event); + void OnSearchComplete(wxThreadEvent& event); virtual void OnResultSelectCell(wxGridEvent& event); virtual void OnResultCellDClick(wxGridEvent& event); virtual void OnResultsKeyDown(wxKeyEvent& event); @@ -65,12 +71,36 @@ public: wchar_t m_char; ///< Currently selected character (0 when none) protected: - bool m_searchChanged; ///< Did Search field change? + bool m_searchChanged; ///< Did Search field or category selection change? std::map m_ccOrder; ///< Character category order bool m_unicodeChanged; ///< Did Unicode field change? + + + /// + /// Search worker thread + /// + class SearchThread : public wxThread + { + public: + SearchThread(wxZRColaCharSelect *parent); + + protected: + virtual ExitCode Entry(); + static int __cdecl CompareHits(const void *a, const void *b); + static bool __cdecl TestDestroyS(void *cookie); + + public: + std::wstring m_search; ///< Search phrase + std::set m_cats; ///< Search categories + std::vector< std::pair > m_hits; ///< Search results + + protected: + wxZRColaCharSelect *m_parent; ///< Thread owner + } *m_searchThread; ///< Search thread }; + /// /// Supports saving/restoring wxZRColaCharSelect state /// diff --git a/ZRCola/zrcolagui.cpp b/ZRCola/zrcolagui.cpp index 39fa103..69f1488 100644 --- a/ZRCola/zrcolagui.cpp +++ b/ZRCola/zrcolagui.cpp @@ -416,7 +416,7 @@ wxZRColaCharSelectBase::wxZRColaCharSelectBase( wxWindow* parent, wxWindowID id, wxStaticBoxSizer* sbSizerBrowse; sbSizerBrowse = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, _("&Browse") ), wxVERTICAL ); - m_search = new wxSearchCtrl( sbSizerBrowse->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER ); + m_search = new wxSearchCtrl( sbSizerBrowse->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); #ifndef __WXMAC__ m_search->ShowSearchButton( true ); #endif @@ -610,12 +610,10 @@ wxZRColaCharSelectBase::wxZRColaCharSelectBase( wxWindow* parent, wxWindowID id, this->SetSizer( bSizerContent ); this->Layout(); bSizerContent->Fit( this ); - m_timerSearch.SetOwner( this, wxID_TIMER_SEARCH ); // Connect Events this->Connect( wxEVT_IDLE, wxIdleEventHandler( wxZRColaCharSelectBase::OnIdle ) ); m_search->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( wxZRColaCharSelectBase::OnSearchText ), NULL, this ); - m_search->Connect( wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( wxZRColaCharSelectBase::OnSearchEnter ), NULL, this ); m_categories->Connect( wxEVT_COMMAND_CHECKLISTBOX_TOGGLED, wxCommandEventHandler( wxZRColaCharSelectBase::OnCategoriesToggle ), NULL, this ); m_gridResults->Connect( wxEVT_GRID_CELL_LEFT_DCLICK, wxGridEventHandler( wxZRColaCharSelectBase::OnResultCellDClick ), NULL, this ); m_gridResults->Connect( wxEVT_GRID_SELECT_CELL, wxGridEventHandler( wxZRColaCharSelectBase::OnResultSelectCell ), NULL, this ); @@ -626,7 +624,6 @@ wxZRColaCharSelectBase::wxZRColaCharSelectBase( wxWindow* parent, wxWindowID id, m_unicode->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( wxZRColaCharSelectBase::OnUnicodeText ), NULL, this ); m_gridRelated->Connect( wxEVT_GRID_SELECT_CELL, wxGridEventHandler( wxZRColaCharSelectBase::OnRelatedSelectCell ), NULL, this ); m_sdbSizerButtonsOK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( wxZRColaCharSelectBase::OnOKButtonClick ), NULL, this ); - this->Connect( wxID_TIMER_SEARCH, wxEVT_TIMER, wxTimerEventHandler( wxZRColaCharSelectBase::OnSearchTimer ) ); } wxZRColaCharSelectBase::~wxZRColaCharSelectBase() @@ -634,7 +631,6 @@ wxZRColaCharSelectBase::~wxZRColaCharSelectBase() // Disconnect Events this->Disconnect( wxEVT_IDLE, wxIdleEventHandler( wxZRColaCharSelectBase::OnIdle ) ); m_search->Disconnect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( wxZRColaCharSelectBase::OnSearchText ), NULL, this ); - m_search->Disconnect( wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( wxZRColaCharSelectBase::OnSearchEnter ), NULL, this ); m_categories->Disconnect( wxEVT_COMMAND_CHECKLISTBOX_TOGGLED, wxCommandEventHandler( wxZRColaCharSelectBase::OnCategoriesToggle ), NULL, this ); m_gridResults->Disconnect( wxEVT_GRID_CELL_LEFT_DCLICK, wxGridEventHandler( wxZRColaCharSelectBase::OnResultCellDClick ), NULL, this ); m_gridResults->Disconnect( wxEVT_GRID_SELECT_CELL, wxGridEventHandler( wxZRColaCharSelectBase::OnResultSelectCell ), NULL, this ); @@ -645,6 +641,5 @@ wxZRColaCharSelectBase::~wxZRColaCharSelectBase() m_unicode->Disconnect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( wxZRColaCharSelectBase::OnUnicodeText ), NULL, this ); m_gridRelated->Disconnect( wxEVT_GRID_SELECT_CELL, wxGridEventHandler( wxZRColaCharSelectBase::OnRelatedSelectCell ), NULL, this ); m_sdbSizerButtonsOK->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( wxZRColaCharSelectBase::OnOKButtonClick ), NULL, this ); - this->Disconnect( wxID_TIMER_SEARCH, wxEVT_TIMER, wxTimerEventHandler( wxZRColaCharSelectBase::OnSearchTimer ) ); } diff --git a/ZRCola/zrcolagui.h b/ZRCola/zrcolagui.h index e0c6251..9729d94 100644 --- a/ZRCola/zrcolagui.h +++ b/ZRCola/zrcolagui.h @@ -186,11 +186,6 @@ class wxZRColaCharSelectBase : public wxDialog private: protected: - enum - { - wxID_TIMER_SEARCH = 1000 - }; - wxSearchCtrl* m_search; wxCheckListBox* m_categories; wxZRColaCharGrid* m_gridResults; @@ -203,12 +198,10 @@ class wxZRColaCharSelectBase : public wxDialog wxStdDialogButtonSizer* m_sdbSizerButtons; wxButton* m_sdbSizerButtonsOK; wxButton* m_sdbSizerButtonsCancel; - wxTimer m_timerSearch; // Virtual event handlers, overide them in your derived class virtual void OnIdle( wxIdleEvent& event ) { event.Skip(); } virtual void OnSearchText( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSearchEnter( wxCommandEvent& event ) { event.Skip(); } virtual void OnCategoriesToggle( wxCommandEvent& event ) { event.Skip(); } virtual void OnResultCellDClick( wxGridEvent& event ) { event.Skip(); } virtual void OnResultSelectCell( wxGridEvent& event ) { event.Skip(); } @@ -219,7 +212,6 @@ class wxZRColaCharSelectBase : public wxDialog virtual void OnUnicodeText( wxCommandEvent& event ) { event.Skip(); } virtual void OnRelatedSelectCell( wxGridEvent& event ) { event.Skip(); } virtual void OnOKButtonClick( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSearchTimer( wxTimerEvent& event ) { event.Skip(); } public: diff --git a/lib/libZRCola/include/zrcola/character.h b/lib/libZRCola/include/zrcola/character.h index 9afa7b3..662a519 100644 --- a/lib/libZRCola/include/zrcola/character.h +++ b/lib/libZRCola/include/zrcola/character.h @@ -234,8 +234,10 @@ namespace ZRCola { /// \param[in ] cats Set of categories, character must be a part of /// \param[inout] hits (character, count) map to append full-word hits to /// \param[inout] hits_sub (character, count) map to append partial-word hits to + /// \param[in] fn_abort Pointer to function to periodically test for search cancellation + /// \param[in] cookie Cookie for \p fn_abort call /// - void Search(_In_z_ const wchar_t *str, _In_ const std::set &cats, _Inout_ std::map &hits, _Inout_ std::map &hits_sub) const; + bool Search(_In_z_ const wchar_t *str, _In_ const std::set &cats, _Inout_ std::map &hits, _Inout_ std::map &hits_sub, _In_opt_ bool (__cdecl *fn_abort)(void *cookie) = NULL, _In_opt_ void *cookie = NULL) const; /// /// Get character category diff --git a/lib/libZRCola/src/character.cpp b/lib/libZRCola/src/character.cpp index f1ea0db..078b834 100644 --- a/lib/libZRCola/src/character.cpp +++ b/lib/libZRCola/src/character.cpp @@ -20,15 +20,17 @@ #include "stdafx.h" -void ZRCola::character_db::Search(_In_z_ const wchar_t *str, _In_ const std::set &cats, _Inout_ std::map &hits, _Inout_ std::map &hits_sub) const +bool ZRCola::character_db::Search(_In_z_ const wchar_t *str, _In_ const std::set &cats, _Inout_ std::map &hits, _Inout_ std::map &hits_sub, _In_opt_ bool (__cdecl *fn_abort)(void *cookie), _In_opt_ void *cookie) const { assert(str); while (*str) { + if (fn_abort && fn_abort(cookie)) return false; + // Skip white space. for (;;) { if (*str == 0) - return; + return true; else if (!iswspace(*str)) break; else @@ -59,15 +61,20 @@ void ZRCola::character_db::Search(_In_z_ const wchar_t *str, _In_ const std::set } if (!term.empty()) { + if (fn_abort && fn_abort(cookie)) return false; + // Find the term. std::transform(term.begin(), term.end(), term.begin(), std::towlower); + if (fn_abort && fn_abort(cookie)) return false; + const wchar_t *data; size_t len; if (idxDsc.find(term.c_str(), term.size(), &data, &len)) { // The term was found. for (size_t i = 0; i < len; i++) { + if (fn_abort && fn_abort(cookie)) return false; wchar_t c = data[i]; if (cats.find(GetCharCat(c)) != cats.end()) { std::map::iterator idx = hits.find(c); @@ -85,6 +92,7 @@ void ZRCola::character_db::Search(_In_z_ const wchar_t *str, _In_ const std::set if (idxDscSub.find(term.c_str(), term.size(), &data, &len)) { // The term was found in the sub-term index. for (size_t i = 0; i < len; i++) { + if (fn_abort && fn_abort(cookie)) return false; wchar_t c = data[i]; if (cats.find(GetCharCat(c)) != cats.end()) { std::map::iterator idx = hits_sub.find(c); @@ -100,4 +108,6 @@ void ZRCola::character_db::Search(_In_z_ const wchar_t *str, _In_ const std::set } } } + + return true; }