From d34d31f6d62dadc35ade1fc4308add7552b2877e Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 7 Oct 2012 22:42:27 +0000 Subject: [PATCH 1/1] Implement incremental search in wxGenericListCtrl. Mostly copy wxGenericTreeCtrl incremental search implementation to wxGenericListCtrl (unfortunately there is no simple way to reuse this code currently), including the recently added EnableBellOnNoMatch() method. Update the sample to test it, the key event handling in it had to be modified to allow it. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@72639 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- docs/changes.txt | 1 + include/wx/generic/listctrl.h | 2 + include/wx/generic/private/listctrl.h | 38 ++++++ include/wx/listbase.h | 4 + interface/wx/listctrl.h | 12 ++ samples/listctrl/listtest.cpp | 39 +++---- samples/listctrl/listtest.h | 2 + src/generic/listctrl.cpp | 161 +++++++++++++++++++++++++- 8 files changed, 237 insertions(+), 22 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index 39af7ad563..af95c7bb2f 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -566,6 +566,7 @@ All (GUI): - Add "inherit" to XRC tag (Steffen Olszewski, Gero Meßsysteme GmbH). - Add support for wxALWAYS_SHOW_SB style to wxScrolled<> (Catalin Raceanu). - Add wxTreeCtrl::EnableBellOnNoMatch() (Jonathan Dagresta). +- Implement incremental search in wxGenericListCtrl (Jonathan Dagresta). wxGTK: diff --git a/include/wx/generic/listctrl.h b/include/wx/generic/listctrl.h index b34315b4f1..629373b706 100644 --- a/include/wx/generic/listctrl.h +++ b/include/wx/generic/listctrl.h @@ -144,6 +144,8 @@ public: void RefreshItem(long item); void RefreshItems(long itemFrom, long itemTo); + virtual void EnableBellOnNoMatch(bool on); + #if WXWIN_COMPATIBILITY_2_6 // obsolete, don't use wxDEPRECATED( int GetItemSpacing( bool isSmall ) const ); diff --git a/include/wx/generic/private/listctrl.h b/include/wx/generic/private/listctrl.h index 48a1f016b8..ee3a7c7be3 100644 --- a/include/wx/generic/private/listctrl.h +++ b/include/wx/generic/private/listctrl.h @@ -390,6 +390,27 @@ public: void Notify(); }; +//----------------------------------------------------------------------------- +// wxListFindTimer (internal) +//----------------------------------------------------------------------------- + +class wxListFindTimer: public wxTimer +{ +public: + // reset the current prefix after half a second of inactivity + enum { DELAY = 500 }; + + wxListFindTimer( wxListMainWindow *owner ) + : m_owner(owner) + { + } + + virtual void Notify(); + +private: + wxListMainWindow *m_owner; +}; + //----------------------------------------------------------------------------- // wxListTextCtrlWrapper: wraps a wxTextCtrl to make it work for inline editing //----------------------------------------------------------------------------- @@ -556,6 +577,11 @@ public: bool OnRenameAccept(size_t itemEdit, const wxString& value); void OnRenameCancelled(size_t itemEdit); + void OnFindTimer(); + // set whether or not to ring the find bell + // (does nothing on MSW - bell is always rung) + void EnableBellOnNoMatch( bool on ); + void OnMouse( wxMouseEvent &event ); // called to switch the selection from the current item to newCurrent, @@ -729,6 +755,15 @@ protected: bool m_lastOnSame; wxTimer *m_renameTimer; + + // incremental search data + wxString m_findPrefix; + wxTimer *m_findTimer; + // This flag is set to 0 if the bell is disabled, 1 if it is enabled and -1 + // if it is globally enabled but has been temporarily disabled because we + // had already beeped for this particular search. + int m_findBell; + bool m_isCreated; int m_dragCount; wxPoint m_dragStart; @@ -779,6 +814,9 @@ protected: // force us to recalculate the range of visible lines void ResetVisibleLinesRange() { m_lineFrom = (size_t)-1; } + // find the first item starting with the given prefix after the given item + size_t PrefixFindItem(size_t item, const wxString& prefix) const; + // get the colour to be used for drawing the rules wxColour GetRuleColour() const { diff --git a/include/wx/listbase.h b/include/wx/listbase.h index e559493b9f..4aa0d5f046 100644 --- a/include/wx/listbase.h +++ b/include/wx/listbase.h @@ -454,6 +454,10 @@ public: bool InReportView() const { return HasFlag(wxLC_REPORT); } bool IsVirtual() const { return HasFlag(wxLC_VIRTUAL); } + // Enable or disable beep when incremental match doesn't find any item. + // Only implemented in the generic version currently. + virtual void EnableBellOnNoMatch(bool WXUNUSED(on) = true) { } + protected: // Real implementations methods to which our public forwards. virtual long DoInsertColumn(long col, const wxListItem& info) = 0; diff --git a/interface/wx/listctrl.h b/interface/wx/listctrl.h index 0e746bde7a..41347abe1f 100644 --- a/interface/wx/listctrl.h +++ b/interface/wx/listctrl.h @@ -407,6 +407,18 @@ public: wxTextCtrl* EditLabel(long item, wxClassInfo* textControlClass = wxCLASSINFO(wxTextCtrl)); + /** + Enable or disable a beep if there is no match for the currently + entered text when searching for the item from keyboard. + + The default is to not beep in this case except in wxMSW where the + beep is always generated by the native control and cannot be disabled, + i.e. calls to this function do nothing there. + + @since 2.9.5 + */ + void EnableBellOnNoMatch(bool on); + /** Finish editing the label. diff --git a/samples/listctrl/listtest.cpp b/samples/listctrl/listtest.cpp index c7fbde5c45..f10280b690 100644 --- a/samples/listctrl/listtest.cpp +++ b/samples/listctrl/listtest.cpp @@ -149,6 +149,7 @@ BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(LIST_THAW, MyFrame::OnThaw) EVT_MENU(LIST_TOGGLE_LINES, MyFrame::OnToggleLines) EVT_MENU(LIST_TOGGLE_HEADER, MyFrame::OnToggleHeader) + EVT_MENU(LIST_TOGGLE_BELL, MyFrame::OnToggleBell) #ifdef __WXOSX__ EVT_MENU(LIST_MAC_USE_GENERIC, MyFrame::OnToggleMacUseGeneric) #endif // __WXOSX__ @@ -258,6 +259,7 @@ MyFrame::MyFrame(const wxChar *title) menuList->Check(LIST_TOGGLE_MULTI_SEL, true); menuList->AppendCheckItem(LIST_TOGGLE_HEADER, "Toggle &header\tCtrl-H"); menuList->Check(LIST_TOGGLE_HEADER, true); + menuList->AppendCheckItem(LIST_TOGGLE_BELL, "Toggle &bell on no match"); wxMenu *menuCol = new wxMenu; menuCol->Append(LIST_SET_FG_COL, wxT("&Foreground colour...")); @@ -366,6 +368,11 @@ void MyFrame::OnToggleHeader(wxCommandEvent& event) m_listCtrl->ToggleWindowStyle(wxLC_NO_HEADER); } +void MyFrame::OnToggleBell(wxCommandEvent& event) +{ + m_listCtrl->EnableBellOnNoMatch(event.IsChecked()); +} + #ifdef __WXOSX__ void MyFrame::OnToggleMacUseGeneric(wxCommandEvent& event) @@ -468,6 +475,10 @@ void MyFrame::RecreateList(long flags, bool withText) default: wxFAIL_MSG( wxT("unknown listctrl mode") ); } + + wxMenuBar* const mb = GetMenuBar(); + if ( mb ) + m_listCtrl->EnableBellOnNoMatch(mb->IsChecked(LIST_TOGGLE_BELL)); } DoSize(); @@ -1096,6 +1107,13 @@ void MyListCtrl::OnListKeyDown(wxListEvent& event) { long item; + if ( !wxGetKeyState(WXK_SHIFT) ) + { + LogEvent(event, wxT("OnListKeyDown")); + event.Skip(); + return; + } + switch ( event.GetKeyCode() ) { case 'C': // colorize @@ -1237,26 +1255,7 @@ void MyListCtrl::OnChar(wxKeyEvent& event) { wxLogMessage(wxT("Got char event.")); - switch ( event.GetKeyCode() ) - { - case 'n': - case 'N': - case 'c': - case 'C': - case 'r': - case 'R': - case 'u': - case 'U': - case 'd': - case 'D': - case 'i': - case 'I': - // these are the keys we process ourselves - break; - - default: - event.Skip(); - } + event.Skip(); } void MyListCtrl::OnRightClick(wxMouseEvent& event) diff --git a/samples/listctrl/listtest.h b/samples/listctrl/listtest.h index b93aabe825..dafb92384b 100644 --- a/samples/listctrl/listtest.h +++ b/samples/listctrl/listtest.h @@ -147,6 +147,7 @@ protected: void OnThaw(wxCommandEvent& event); void OnToggleLines(wxCommandEvent& event); void OnToggleHeader(wxCommandEvent& event); + void OnToggleBell(wxCommandEvent& event); #ifdef __WXOSX__ void OnToggleMacUseGeneric(wxCommandEvent& event); #endif // __WXOSX__ @@ -219,6 +220,7 @@ enum LIST_SET_BG_COL, LIST_TOGGLE_MULTI_SEL, LIST_TOGGLE_HEADER, + LIST_TOGGLE_BELL, LIST_TOGGLE_FIRST, LIST_SHOW_COL_INFO, LIST_SHOW_SEL_INFO, diff --git a/src/generic/listctrl.cpp b/src/generic/listctrl.cpp index 26a34afdad..2b74874690 100644 --- a/src/generic/listctrl.cpp +++ b/src/generic/listctrl.cpp @@ -1371,6 +1371,15 @@ void wxListRenameTimer::Notify() m_owner->OnRenameTimer(); } +//----------------------------------------------------------------------------- +// wxListFindTimer (internal) +//----------------------------------------------------------------------------- + +void wxListFindTimer::Notify() +{ + m_owner->OnFindTimer(); +} + //----------------------------------------------------------------------------- // wxListTextCtrlWrapper (internal) //----------------------------------------------------------------------------- @@ -1563,6 +1572,8 @@ void wxListMainWindow::Init() m_lastOnSame = false; m_renameTimer = new wxListRenameTimer( this ); + m_findTimer = NULL; + m_findBell = 0; // default is to not ring bell at all m_textctrlWrapper = NULL; m_current = @@ -1625,6 +1636,7 @@ wxListMainWindow::~wxListMainWindow() delete m_highlightBrush; delete m_highlightUnfocusedBrush; delete m_renameTimer; + delete m_findTimer; } void wxListMainWindow::SetReportView(bool inReportView) @@ -2288,6 +2300,18 @@ void wxListMainWindow::OnRenameCancelled(size_t itemEdit) GetEventHandler()->ProcessEvent( le ); } +void wxListMainWindow::OnFindTimer() +{ + m_findPrefix.clear(); + if ( m_findBell ) + m_findBell = 1; +} + +void wxListMainWindow::EnableBellOnNoMatch( bool on ) +{ + m_findBell = on; +} + void wxListMainWindow::OnMouse( wxMouseEvent &event ) { #ifdef __WXMAC__ @@ -2801,7 +2825,8 @@ void wxListMainWindow::OnChar( wxKeyEvent &event ) event.m_keyCode = WXK_RIGHT; } - switch ( event.GetKeyCode() ) + int keyCode = event.GetKeyCode(); + switch ( keyCode ) { case WXK_UP: if ( m_current > 0 ) @@ -2899,7 +2924,79 @@ void wxListMainWindow::OnChar( wxKeyEvent &event ) break; default: - event.Skip(); + if ( !event.HasModifiers() && + ((keyCode >= '0' && keyCode <= '9') || + (keyCode >= 'a' && keyCode <= 'z') || + (keyCode >= 'A' && keyCode <= 'Z') || + (keyCode == '_') || + (keyCode == '+') || + (keyCode == '*') || + (keyCode == '-'))) + { + // find the next item starting with the given prefix + wxChar ch = (wxChar)keyCode; + size_t item; + + // if the same character is typed multiple times then go to the + // next entry starting with that character instead of searching + // for an item starting with multiple copies of this character, + // this is more useful and is how it works under Windows. + if ( m_findPrefix.length() == 1 && m_findPrefix[0] == ch ) + { + item = PrefixFindItem(m_current, ch); + } + else + { + const wxString newPrefix(m_findPrefix + ch); + item = PrefixFindItem(m_current, newPrefix); + if ( item != (size_t)-1 ) + m_findPrefix = newPrefix; + } + + // also start the timer to reset the current prefix if the user + // doesn't press any more alnum keys soon -- we wouldn't want + // to use this prefix for a new item search + if ( !m_findTimer ) + { + m_findTimer = new wxListFindTimer( this ); + } + + // Notice that we should start the timer even if we didn't find + // anything to make sure we reset the search state later. + m_findTimer->Start(wxListFindTimer::DELAY, wxTIMER_ONE_SHOT); + + // restart timer even when there's no match so bell get's reset + if ( item != (size_t)-1 ) + { + // Select the found item and go to it. + HighlightAll(false); + SetItemState(item, + wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED, + wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED); + + // Reset the bell flag if it had been temporarily disabled + // before. + if ( m_findBell ) + m_findBell = 1; + } + else // No such item + { + // Signal it with a bell if enabled. + if ( m_findBell == 1 ) + { + ::wxBell(); + + // Disable it for the next unsuccessful match, we only + // beep once, this is usually enough and continuing to + // do it would be annoying. + m_findBell = -1; + } + } + } + else + { + event.Skip(); + } } } @@ -4321,6 +4418,61 @@ void wxListMainWindow::GetVisibleLinesRange(size_t *from, size_t *to) *to = m_lineTo; } +size_t +wxListMainWindow::PrefixFindItem(size_t idParent, + const wxString& prefixOrig) const +{ + // if no items then just return + if ( idParent == (size_t)-1 ) + return idParent; + + // match is case insensitive as this is more convenient to the user: having + // to press Shift-letter to go to the item starting with a capital letter + // would be too bothersome + wxString prefix = prefixOrig.Lower(); + + // determine the starting point: we shouldn't take the current item (this + // allows to switch between two items starting with the same letter just by + // pressing it) but we shouldn't jump to the next one if the user is + // continuing to type as otherwise he might easily skip the item he wanted + size_t itemid = idParent; + if ( prefix.length() == 1 ) + { + itemid += 1; + } + + // look for the item starting with the given prefix after it + while ( ( itemid < (size_t)GetItemCount() ) && + !GetLine(itemid)->GetText(0).Lower().StartsWith(prefix) ) + { + itemid += 1; + } + + // if we haven't found anything... + if ( !( itemid < (size_t)GetItemCount() ) ) + { + // ... wrap to the beginning + itemid = 0; + + // and try all the items (stop when we get to the one we started from) + while ( ( itemid < (size_t)GetItemCount() ) && itemid != idParent && + !GetLine(itemid)->GetText(0).Lower().StartsWith(prefix) ) + { + itemid += 1; + } + // If we haven't found the item, id will be (size_t)-1, as per + // documentation + if ( !( itemid < (size_t)GetItemCount() ) || + ( ( itemid == idParent ) && + !GetLine(itemid)->GetText(0).Lower().StartsWith(prefix) ) ) + { + itemid = (size_t)-1; + } + } + + return itemid; +} + // ------------------------------------------------------------------------------------- // wxGenericListCtrl // ------------------------------------------------------------------------------------- @@ -5280,6 +5432,11 @@ void wxGenericListCtrl::RefreshItems(long itemFrom, long itemTo) m_mainWin->RefreshLines(itemFrom, itemTo); } +void wxGenericListCtrl::EnableBellOnNoMatch( bool on ) +{ + m_mainWin->EnableBellOnNoMatch(on); +} + // Generic wxListCtrl is more or less a container for two other // windows which drawings are done upon. These are namely // 'm_headerWin' and 'm_mainWin'. -- 2.45.2