Fix treeview searching [Ashesh Vashi]
authordpage <dpage@a7884b65-44f6-0310-8a51-81a127f17b15>
Mon, 11 May 2009 13:46:42 +0000 (13:46 +0000)
committerdpage <dpage@a7884b65-44f6-0310-8a51-81a127f17b15>
Mon, 11 May 2009 13:46:42 +0000 (13:46 +0000)
git-svn-id: svn://svn.pgadmin.org/trunk/pgadmin3@7861 a7884b65-44f6-0310-8a51-81a127f17b15

pgadmin/ctl/ctlTree.cpp
pgadmin/include/ctl/ctlTree.h

index 33a622e9228d13cbc6aad88ab707721a256544fe..6ec7f8fea55e13d76b3ada21915b043267fd33b9 100644 (file)
@@ -24,43 +24,112 @@ BEGIN_EVENT_TABLE(ctlTree, wxTreeCtrl)
     EVT_CHAR(ctlTree::OnChar)
 END_EVENT_TABLE()
 
-int level = 0;
 
-wxTreeItemId ctlTree::FindItem(const wxTreeItemId& item, const wxString& str)
+wxTreeItemId ctlTree::FindItem(const wxTreeItemId& idParent, const wxString& prefixOrig)
 {
-    wxTreeItemId resultItem;
-    wxTreeItemId currItem = item;
-    if (!currItem.IsOk())
-        return currItem;
-    
-    wxString val = GetItemText(currItem);
+    // 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
+    wxTreeItemId id = idParent;
+
+    if ( prefix.length() == 1 )
+    {
+        wxCookieType cookie;
+        if (HasChildren(id))
+            id = GetFirstChild(id, cookie);
+        else
+        {
+             // Try a sibling of this or ancestor instead
+             wxTreeItemId p = id;
+             wxTreeItemId toFind;
+             do
+             {
+                  toFind = GetNextSibling(p);
+                  p = GetItemParent(p);
+             } while (p.IsOk() && !toFind.IsOk());
+             id = toFind;
+        }
+    }
 
-    // Ignore Dummy Nodes
-    if (!(val == wxT("Dummy") && GetItemData(item) == NULL) && val.Lower().StartsWith(str))
+    // look for the item starting with the given prefix after it
+    while ( id.IsOk() &&
+            ( ( GetItemText(id) == wxT("Dummy") && !GetItemData(id) ) ||
+             !GetItemText(id).Lower().StartsWith(prefix) ))
     {
-        return currItem;
+        wxCookieType cookie;
+        if ( HasChildren(id) )
+            id = GetFirstChild(id, cookie);
+        else
+        {
+             // Try a sibling of this or ancestor instead
+             wxTreeItemId p = id;
+             wxTreeItemId toFind;
+             do
+             {
+                  toFind = GetNextSibling(p);
+                  p = GetItemParent(p);
+             } while (p.IsOk() && !toFind.IsOk());
+             id = toFind;
+        }
     }
-    
-    if (HasChildren(currItem))
+
+    // if we haven't found anything...
+    if ( !id.IsOk() )
     {
-        wxTreeItemIdValue cookie;
-        wxTreeItemId childItem = GetFirstChild(currItem, cookie);
-        level++;
-        resultItem = FindItem(childItem, str);
-        if (resultItem.IsOk())
+        // ... wrap to the beginning
+        id = GetRootItem();
+        if ( HasFlag(wxTR_HIDE_ROOT) )
+        {
+            wxCookieType cookie;
+            // can't select virtual root
+            if ( HasChildren(id) )
+                id = GetFirstChild(id, cookie);
+            else
+            {
+                 // Try a sibling of this or ancestor instead
+                 wxTreeItemId p = id;
+                 wxTreeItemId toFind;
+                 do
+                 {
+                      toFind = GetNextSibling(p);
+                      p = GetItemParent(p);
+                 } while (p.IsOk() && !toFind.IsOk());
+                 id = toFind;
+            }
+        }
+
+        // and try all the items (stop when we get to the one we started from)
+        while ( id.IsOk() && id != idParent &&
+                (( GetItemText(id) == wxT("Dummy") && !GetItemData(id) ) ||
+                 !GetItemText(id).Lower().StartsWith(prefix) ))
         {
-            level--;
-            return resultItem;
+            wxCookieType cookie;
+            if ( HasChildren(id) )
+                id = GetFirstChild(id, cookie);
+            else
+            {
+                 // Try a sibling of this or ancestor instead
+                 wxTreeItemId p = id;
+                 wxTreeItemId toFind;
+                 do
+                 {
+                      toFind = GetNextSibling(p);
+                      p = GetItemParent(p);
+                 } while (p.IsOk() && !toFind.IsOk());
+                 id = toFind;
+            }
         }
-        level--;
+        // If we haven't found the item, id.IsOk() will be false, as per
+        // documentation
     }
-    
-    currItem = GetNextSibling(currItem);
-    
-    if (currItem.IsOk())
-        resultItem = FindItem(currItem, str);
 
-    return resultItem;
+    return id;
 }
 
 void ctlTree::OnChar(wxKeyEvent& event)
@@ -71,16 +140,30 @@ void ctlTree::OnChar(wxKeyEvent& event)
           (keyCode >= 'a' && keyCode <= 'z') ||
           (keyCode >= 'A' && keyCode <= 'Z')))
     {
-        wxChar ch = (wxChar)keyCode;
         wxTreeItemId currItem = GetSelection();
+
         if (!currItem.IsOk())
             return;
 
-        wxTreeItemId matchItem = FindItem(currItem, wxString(ch).Lower());
-        if (matchItem.IsOk())
+        wxTreeItemId matchItem = FindItem(currItem, m_findPrefix + (wxChar)keyCode);
+
+        if ( matchItem.IsOk() )
         {
             EnsureVisible(matchItem);
             SelectItem(matchItem);
+
+            m_findPrefix += (wxChar)keyCode;
+
+            // 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 ctlTreeFindTimer(this);
+            }
+
+            m_findTimer->Start(ctlTreeFindTimer::CTLTREE_DELAY, wxTIMER_ONE_SHOT);
+            return;
         }
     }
     else
@@ -90,8 +173,15 @@ void ctlTree::OnChar(wxKeyEvent& event)
 }
 
 ctlTree::ctlTree(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
-: wxTreeCtrl(parent, id, pos, size, style)
+: wxTreeCtrl(parent, id, pos, size, style), m_findTimer(NULL)
+{
+}
+
+ctlTree::~ctlTree()
 {
+    if ( m_findTimer )
+        delete m_findTimer;
+    m_findTimer = NULL;
 }
 
 
index 09ff208181b38f9cabe8c4075902a895505bbf7d..be1940b70ed62af05db13e05de3345e8877b9c0e 100644 (file)
 // wxWindows headers
 #include <wx/wx.h>
 #include <wx/treectrl.h>
+#include <wx/timer.h>
 
 class pgObject;
 class pgCollection;
 class pgaFactory;
-
-
+class ctlTreeFindTimer;
 
 class ctlTree : public wxTreeCtrl
 {
@@ -37,11 +37,35 @@ public:
     pgObject *FindObject(pgaFactory &factory, wxTreeItemId parent);
     pgCollection *FindCollection(pgaFactory &factory, wxTreeItemId parent);
     wxTreeItemId FindItem(const wxTreeItemId& item, const wxString& str);
+    virtual ~ctlTree();
 
     DECLARE_EVENT_TABLE()
 
 private:
     void OnChar(wxKeyEvent& event);
+    wxString m_findPrefix;
+    ctlTreeFindTimer * m_findTimer;
+
+friend class ctlTreeFindTimer;
+};
+
+
+// timer used to clear ctlTreeCtrl::m_findPrefix if no key was pressed
+// for a sufficiently long time
+class ctlTreeFindTimer : public wxTimer
+{
+public:
+    // reset the current prefix after half a second of inactivity
+    enum { CTLTREE_DELAY = 500 };
+
+    ctlTreeFindTimer( ctlTree *owner ) { m_owner = owner; }
+
+    virtual void Notify() { m_owner->m_findPrefix.clear(); }
+
+private:
+    ctlTree *m_owner;
+
+    DECLARE_NO_COPY_CLASS(ctlTreeFindTimer)
 };