]> git.saurik.com Git - wxWidgets.git/commitdiff
Implement incremental search in wxGenericListCtrl.
authorVadim Zeitlin <vadim@wxwidgets.org>
Sun, 7 Oct 2012 22:42:27 +0000 (22:42 +0000)
committerVadim Zeitlin <vadim@wxwidgets.org>
Sun, 7 Oct 2012 22:42:27 +0000 (22:42 +0000)
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
include/wx/generic/listctrl.h
include/wx/generic/private/listctrl.h
include/wx/listbase.h
interface/wx/listctrl.h
samples/listctrl/listtest.cpp
samples/listctrl/listtest.h
src/generic/listctrl.cpp

index 39af7ad56310310c27c90dcaa6897dd27ac30858..af95c7bb2f6fc38f49999b13775cdd173b03fffe 100644 (file)
@@ -566,6 +566,7 @@ All (GUI):
 - Add "inherit" to <font> 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:
 
index b34315b4f139a3f2443dfe6b414e51c4216fdaa6..629373b70693f3601d8eba7bcb5833e236be5c1c 100644 (file)
@@ -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 );
index 48a1f016b849342a9fdd59a54af4de859a636b38..ee3a7c7be39b8e78f84ea44fc824f909058123fd 100644 (file)
@@ -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
     {
index e559493b9fdcabb0538985e1afdc8bbc504a1011..4aa0d5f0465c6ce44ccab9626d93ff993d6fa0d0 100644 (file)
@@ -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;
index 0e746bde7a369db539803a1464c98036f18e5b4b..41347abe1f1b3701fb7191380ea7193d5e9cac83 100644 (file)
@@ -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.
 
index c7fbde5c45bb6c414cd3e173518b562dfc9dc1d3..f10280b690d9ffd7ac475a3e761c6bc823ad1d9e 100644 (file)
@@ -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)
index b93aabe82519ae27bb50b7119357add90b8aa039..dafb92384bb902e3b2745be7ff021bb9a2eb338a 100644 (file)
@@ -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,
index 26a34afdad5f0d0a6d5bf567e9560c311b4451dc..2b7487469042e1d382c130d25badc9bc95d358ec 100644 (file)
@@ -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'.