]> git.saurik.com Git - wxWidgets.git/blobdiff - src/generic/listctrl.cpp
Handle Shift-TAB correctly in wxOSX/Carbon wxComboBox.
[wxWidgets.git] / src / generic / listctrl.cpp
index 11fa7ffcfbc75391a7846fe4fa79fe6b4d17bb01..03e299d46aa144595b4401ff261997a74fe03467 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 =
@@ -1582,10 +1593,9 @@ wxListMainWindow::wxListMainWindow()
 wxListMainWindow::wxListMainWindow( wxWindow *parent,
                                     wxWindowID id,
                                     const wxPoint& pos,
-                                    const wxSize& size,
-                                    long style,
-                                    const wxString &name )
-                : wxWindow( parent, id, pos, size, style, name )
+                                    const wxSize& size )
+                : wxWindow( parent, id, pos, size,
+                            wxWANTS_CHARS | wxBORDER_NONE )
 {
     Init();
 
@@ -1626,6 +1636,7 @@ wxListMainWindow::~wxListMainWindow()
     delete m_highlightBrush;
     delete m_highlightUnfocusedBrush;
     delete m_renameTimer;
+    delete m_findTimer;
 }
 
 void wxListMainWindow::SetReportView(bool inReportView)
@@ -2227,19 +2238,10 @@ wxTextCtrl *wxListMainWindow::EditLabel(long item, wxClassInfo* textControlClass
         return NULL;
     }
 
-    // We have to call this here because the label in question might just have
-    // been added and no screen update taken place.
     if ( m_dirty )
     {
-        // TODO: use wxTheApp->SafeYieldFor(NULL, wxEVT_CATEGORY_UI) instead
-        //       so that no pending events may change the item count (see below)
-        //       IMPORTANT: needs to be tested!
-        wxSafeYield();
-
-        // Pending events dispatched by wxSafeYield might have changed the item
-        // count
-        if ( (size_t)item >= GetItemCount() )
-            return NULL;
+        // Ensure the display is updated before we start editing.
+        Update();
     }
 
     wxTextCtrl * const text = (wxTextCtrl *)textControlClass->CreateObject();
@@ -2289,6 +2291,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__
@@ -2301,10 +2315,16 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event )
 #endif // __WXMAC__
 
     if ( event.LeftDown() )
-        SetFocus();
+    {
+        // Ensure we skip the event to let the system set focus to this window.
+        event.Skip();
+    }
 
-    event.SetEventObject( GetParent() );
-    if ( GetParent()->GetEventHandler()->ProcessEvent( event) )
+    // Pretend that the event happened in wxListCtrl itself.
+    wxMouseEvent me(event);
+    me.SetEventObject( GetParent() );
+    me.SetId(GetParent()->GetId());
+    if ( GetParent()->GetEventHandler()->ProcessEvent( me ))
         return;
 
     if (event.GetEventType() == wxEVT_MOUSEWHEEL)
@@ -2465,9 +2485,10 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event )
                     m_renameTimer->Start(dclick > 0 ? dclick : 250, true);
                 }
             }
+
+            m_lastOnSame = false;
         }
 
-        m_lastOnSame = false;
         m_lineSelectSingleOnUp = (size_t)-1;
     }
     else
@@ -2559,8 +2580,16 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event )
         if (m_current != oldCurrent)
             RefreshLine( oldCurrent );
 
-        // forceClick is only set if the previous click was on another item
-        m_lastOnSame = !forceClick && (m_current == oldCurrent) && oldWasSelected;
+        // Set the flag telling us whether the next click on this item should
+        // start editing its label. This should happen if we clicked on the
+        // current item and it was already selected, i.e. if this click was not
+        // done to select it.
+        //
+        // It should not happen if this was a double click (forceClick is true)
+        // nor if we hadn't had the focus before as then this click was used to
+        // give focus to the control.
+        m_lastOnSame = (m_current == oldCurrent) && oldWasSelected &&
+                            !forceClick && HasFocus();
     }
 }
 
@@ -2706,6 +2735,7 @@ void wxListMainWindow::OnKeyDown( wxKeyEvent &event )
     // propagate the key event upwards
     wxKeyEvent ke(event);
     ke.SetEventObject( parent );
+    ke.SetId(GetParent()->GetId());
     if (parent->GetEventHandler()->ProcessEvent( ke ))
         return;
 
@@ -2729,6 +2759,8 @@ void wxListMainWindow::OnKeyUp( wxKeyEvent &event )
 
     // propagate the key event upwards
     wxKeyEvent ke(event);
+    ke.SetEventObject( parent );
+    ke.SetId(GetParent()->GetId());
     if (parent->GetEventHandler()->ProcessEvent( ke ))
         return;
 
@@ -2758,6 +2790,7 @@ void wxListMainWindow::OnChar( wxKeyEvent &event )
     // propagate the char event upwards
     wxKeyEvent ke(event);
     ke.SetEventObject( parent );
+    ke.SetId(GetParent()->GetId());
     if (parent->GetEventHandler()->ProcessEvent( ke ))
         return;
 
@@ -2783,7 +2816,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 )
@@ -2881,7 +2915,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();
+            }
     }
 }
 
@@ -4126,8 +4232,10 @@ void wxListMainWindow::InsertItem( wxListItem &item )
     RefreshLines(id, GetItemCount() - 1);
 }
 
-void wxListMainWindow::InsertColumn( long col, const wxListItem &item )
+long wxListMainWindow::InsertColumn( long col, const wxListItem &item )
 {
+    long idx = -1;
+
     m_dirty = true;
     if ( InReportView() )
     {
@@ -4144,9 +4252,11 @@ void wxListMainWindow::InsertColumn( long col, const wxListItem &item )
                 node = m_columns.Item( col );
             m_columns.Insert( node, column );
             m_aColWidths.Insert( colWidthInfo, col );
+            idx = col;
         }
         else
         {
+            idx = m_aColWidths.GetCount();
             m_columns.Append( column );
             m_aColWidths.Add( colWidthInfo );
         }
@@ -4168,6 +4278,7 @@ void wxListMainWindow::InsertColumn( long col, const wxListItem &item )
         // invalidate it as it has to be recalculated
         m_headerWidth = 0;
     }
+    return idx;
 }
 
 int wxListMainWindow::GetItemWidthWithImage(wxListItem * item)
@@ -4298,6 +4409,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
 // -------------------------------------------------------------------------------------
@@ -4389,12 +4555,7 @@ bool wxGenericListCtrl::Create(wxWindow *parent,
                                   validator, name ) )
         return false;
 
-#ifdef __WXGTK__
-    style &= ~wxBORDER_MASK;
-    style |= wxBORDER_THEME;
-#endif
-
-    m_mainWin = new wxListMainWindow( this, wxID_ANY, wxPoint(0, 0), size, style );
+    m_mainWin = new wxListMainWindow(this, wxID_ANY, wxPoint(0, 0), size);
 
     SetTargetWindow( m_mainWin );
 
@@ -4451,13 +4612,14 @@ void wxGenericListCtrl::OnScroll(wxScrollWinEvent& event)
     // the window the next time
     m_mainWin->ResetVisibleLinesRange();
 
-    HandleOnScroll( event );
-
     if ( event.GetOrientation() == wxHORIZONTAL && HasHeader() )
     {
         m_headerWin->Refresh();
         m_headerWin->Update();
     }
+
+    // Let the window be scrolled as usual by the default handler.
+    event.Skip();
 }
 
 void wxGenericListCtrl::SetSingleStyle( long style, bool add )
@@ -4953,14 +5115,14 @@ long wxGenericListCtrl::DoInsertColumn( long col, const wxListItem &item )
 {
     wxCHECK_MSG( InReportView(), -1, wxT("can't add column in non report mode") );
 
-    m_mainWin->InsertColumn( col, item );
+    long idx = m_mainWin->InsertColumn( col, item );
 
     // NOTE: if wxLC_NO_HEADER was given, then we are in report view mode but
     //       still have m_headerWin==NULL
     if (m_headerWin)
         m_headerWin->Refresh();
 
-    return 0;
+    return idx;
 }
 
 bool wxGenericListCtrl::ScrollList( int dx, int dy )
@@ -5262,6 +5424,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'.