]> git.saurik.com Git - wxWidgets.git/commitdiff
implemented multiple selection
authorVadim Zeitlin <vadim@wxwidgets.org>
Wed, 11 Jun 2003 11:40:37 +0000 (11:40 +0000)
committerVadim Zeitlin <vadim@wxwidgets.org>
Wed, 11 Jun 2003 11:40:37 +0000 (11:40 +0000)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@21040 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

include/wx/vlbox.h
src/generic/vlbox.cpp

index b1588627ba9c3424ba7072228da12b1af8d03877..ff1dc10de91b41bb77d127b141a1687a8c4e41a0 100644 (file)
@@ -14,6 +14,8 @@
 
 #include "wx/vscroll.h"         // base class
 
+class WXDLLEXPORT wxSelectionStore;
+
 #define wxVListBoxNameStr _T("wxVListBox")
 
 // ----------------------------------------------------------------------------
@@ -43,29 +45,81 @@ public:
                wxWindowID id = wxID_ANY,
                const wxPoint& pos = wxDefaultPosition,
                const wxSize& size = wxDefaultSize,
-               size_t countItems = 0,
                long style = 0,
                const wxString& name = wxVListBoxNameStr)
     {
         Init();
 
-        (void)Create(parent, id, pos, size, countItems, style, name);
+        (void)Create(parent, id, pos, size, style, name);
     }
 
     // really creates the control and sets the initial number of items in it
     // (which may be changed later with SetItemCount())
     //
-    // there are no special styles defined for wxVListBox
+    // the only special style which may be specified here is wxLB_MULTIPLE
     //
     // returns true on success or false if the control couldn't be created
     bool Create(wxWindow *parent,
                 wxWindowID id = wxID_ANY,
                 const wxPoint& pos = wxDefaultPosition,
                 const wxSize& size = wxDefaultSize,
-                size_t countItems = 0,
                 long style = 0,
                 const wxString& name = wxVListBoxNameStr);
 
+    // dtor does some internal cleanup (deletes m_selStore if any)
+    virtual ~wxVListBox();
+
+
+    // accessors
+    // ---------
+
+    // get the number of items in the control
+    size_t GetItemCount() const { return GetLineCount(); }
+
+    // does this control use multiple selection?
+    bool HasMultipleSelection() const { return m_selStore != NULL; }
+
+    // get the currently selected item or wxNOT_FOUND if there is no selection
+    //
+    // this method is only valid for the single selection listboxes
+    int GetSelection() const
+    {
+        wxASSERT_MSG( !HasMultipleSelection(),
+                        _T("GetSelection() can't be used with wxLB_MULTIPLE") );
+
+        return m_current;
+    }
+
+    // is this item the current one?
+    bool IsCurrent(size_t item) const { return item == (size_t)m_current; }
+
+    // is this item selected?
+    bool IsSelected(size_t item) const;
+
+    // get the number of the selected items (maybe 0)
+    //
+    // this method is valid for both single and multi selection listboxes
+    size_t GetSelectedCount() const;
+
+    // get the first selected item, returns wxNOT_FOUND if none
+    //
+    // cookie is an opaque parameter which should be passed to
+    // GetNextSelected() later
+    //
+    // this method is only valid for the multi selection listboxes
+    int GetFirstSelected(unsigned long& cookie) const;
+
+    // get next selection item, return wxNOT_FOUND if no more
+    //
+    // cookie must be the same parameter that was passed to GetFirstSelected()
+    // before
+    //
+    // this method is only valid for the multi selection listboxes
+    int GetNextSelected(unsigned long& cookie) const;
+
+    // get the margins around each item
+    wxPoint GetMargins() const { return m_ptMargins; }
+
 
     // operations
     // ----------
@@ -73,14 +127,49 @@ public:
     // set the number of items to be shown in the control
     //
     // this is just a synonym for wxVScrolledWindow::SetLineCount()
-    void SetItemCount(size_t count) { SetLineCount(count); }
+    void SetItemCount(size_t count);
 
     // delete all items from the control
     void Clear() { SetItemCount(0); }
 
     // set the selection to the specified item, if it is -1 the selection is
     // unset
-    void SetSelection(int selection) { DoSetSelection(selection, false); }
+    //
+    // this function is only valid for the single selection listboxes
+    void SetSelection(int selection);
+
+    // selects or deselects the specified item which must be valid (i.e. not
+    // equal to -1)
+    //
+    // return true if the items selection status has changed or false
+    // otherwise
+    //
+    // this function is only valid for the multiple selection listboxes
+    bool Select(size_t item, bool select = true);
+
+    // selects the items in the specified range whose end points may be given
+    // in any order
+    //
+    // return true if any items selection status has changed, false otherwise
+    //
+    // this function is only valid for the single selection listboxes
+    bool SelectRange(size_t from, size_t to);
+
+    // toggle the selection of the specified item (must be valid)
+    //
+    // this function is only valid for the multiple selection listboxes
+    void Toggle(size_t item) { Select(item, !IsSelected(item)); }
+
+    // select all items in the listbox
+    //
+    // the return code indicates if any items were affected by this operation
+    // (true) or if nothing has changed (false)
+    bool SelectAll() { return DoSelectAll(true); }
+
+    // unselect all items in the listbox
+    //
+    // the return code has the same meaning as for SelectAll()
+    bool DeselectAll() { return DoSelectAll(false); }
 
     // set the margins: horizontal margin is the distance between the window
     // border and the item contents while vertical margin is half of the
@@ -91,22 +180,6 @@ public:
     void SetMargins(wxCoord x, wxCoord y) { SetMargins(wxPoint(x, y)); }
 
 
-    // accessors
-    // ---------
-
-    // get the number of items in the control
-    size_t GetItemCount() const { return GetLineCount(); }
-
-    // get the currently selected item or -1 if there is no selection
-    int GetSelection() const { return m_selection; }
-
-    // is this item selected?
-    bool IsSelected(size_t line) const { return (int)line == m_selection; }
-
-    // get the margins around each item
-    wxPoint GetMargins() const { return m_ptMargins; }
-
-
 protected:
     // the derived class must implement this function to actually draw the item
     // with the given index on the provided DC
@@ -141,18 +214,35 @@ protected:
     // common part of all ctors
     void Init();
 
-    // SetSelection() with additional parameter telling it whether to send a
-    // notification event or not
-    void DoSetSelection(int selection, bool sendEvent = true);
+    // send the wxEVT_COMMAND_LISTBOX_SELECTED event
+    void SendSelectedEvent();
+
+    // common implementation of SelectAll() and DeselectAll()
+    bool DoSelectAll(bool select);
+
+    // change the current item (in single selection listbox it also implicitly
+    // changes the selection); current may be -1 in which case there will be
+    // no current item any more
+    //
+    // return true if the current item changed, false otherwise
+    bool DoSetCurrent(int current);
+
+    // common part of keyboard and mouse handling processing code
+    void DoHandleItemClick(int item, bool shiftDown, bool ctrlDown);
 
 private:
-    // the current selection or -1
-    int m_selection;
+    // the current item or -1
+    //
+    // if m_selStore == NULL this is also the selected item, otherwise the
+    // selections are managed by m_selStore
+    int m_current;
+
+    // the object managing our selected items if not NULL
+    wxSelectionStore *m_selStore;
 
     // margins
     wxPoint m_ptMargins;
 
-
     DECLARE_EVENT_TABLE()
 };
 
index d5b824c1e1feb590540fdabec5605499d647392d..e15de81c6a40a8b7284bc25b12ee8987ff711f92 100644 (file)
@@ -30,6 +30,7 @@
 #endif //WX_PRECOMP
 
 #include "wx/vlbox.h"
+#include "wx/selstore.h"
 
 // ----------------------------------------------------------------------------
 // event tables
@@ -53,14 +54,14 @@ END_EVENT_TABLE()
 
 void wxVListBox::Init()
 {
-    m_selection = -1;
+    m_current = wxNOT_FOUND;
+    m_selStore = NULL;
 }
 
 bool wxVListBox::Create(wxWindow *parent,
                         wxWindowID id,
                         const wxPoint& pos,
                         const wxSize& size,
-                        size_t countItems,
                         long style,
                         const wxString& name)
 {
@@ -69,60 +70,206 @@ bool wxVListBox::Create(wxWindow *parent,
 
     SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX));
 
-    SetItemCount(countItems);
+    if ( style & wxLB_MULTIPLE )
+        m_selStore = new wxSelectionStore;
 
     return true;
 }
 
+wxVListBox::~wxVListBox()
+{
+    delete m_selStore;
+}
+
+void wxVListBox::SetItemCount(size_t count)
+{
+    if ( m_selStore )
+    {
+        // tell the selection store that our number of items has changed
+        m_selStore->SetItemCount(count);
+    }
+
+    SetLineCount(count);
+}
+
 // ----------------------------------------------------------------------------
 // selection handling
 // ----------------------------------------------------------------------------
 
-void wxVListBox::DoSetSelection(int selection, bool sendEvent)
+bool wxVListBox::IsSelected(size_t line) const
+{
+    return m_selStore ? m_selStore->IsSelected(line) : (int)line == m_current;
+}
+
+bool wxVListBox::Select(size_t item, bool select)
+{
+    wxCHECK_MSG( m_selStore, false,
+                 _T("Select() may only be used with multiselection listbox") );
+
+    wxCHECK_MSG( item < GetItemCount(), false,
+                 _T("Select(): invalid item index") );
+
+    bool changed = m_selStore->SelectItem(item, select);
+    if ( changed )
+    {
+        // selection really changed
+        RefreshLine(item);
+    }
+
+    DoSetCurrent(item);
+
+    return changed;
+}
+
+bool wxVListBox::SelectRange(size_t from, size_t to)
 {
-    if ( selection == m_selection )
+    wxCHECK_MSG( m_selStore, false,
+                 _T("SelectRange() may only be used with multiselection listbox") );
+
+    // make sure items are in correct order
+    if ( from > to )
+    {
+        size_t tmp = from;
+        from = to;
+        to = tmp;
+    }
+
+    wxCHECK_MSG( to < GetItemCount(), false,
+                    _T("SelectRange(): invalid item index") );
+
+    wxArrayInt changed;
+    if ( !m_selStore->SelectRange(from, to, true, &changed) )
+    {
+        // too many items have changed, we didn't record them in changed array
+        // so we have no choice but to refresh everything between from and to
+        RefreshLines(from, to);
+    }
+    else // we've got the indices of the changed items
+    {
+        const size_t count = changed.GetCount();
+        if ( !count )
+        {
+            // nothing changed
+            return false;
+        }
+
+        // refresh just the lines which have really changed
+        for ( size_t n = 0; n < count; n++ )
+        {
+            RefreshLine(changed[n]);
+        }
+    }
+
+    // something changed
+    return true;
+}
+
+bool wxVListBox::DoSelectAll(bool select)
+{
+    wxCHECK_MSG( m_selStore, false,
+                 _T("SelectAll may only be used with multiselection listbox") );
+
+    size_t count = GetItemCount();
+    if ( count )
+    {
+        wxArrayInt changed;
+        if ( !m_selStore->SelectRange(0, count - 1, select) ||
+                !changed.IsEmpty() )
+        {
+            Refresh();
+
+            // something changed
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool wxVListBox::DoSetCurrent(int current)
+{
+    if ( current == m_current )
     {
         // nothing to do
-        return;
+        return false;
     }
 
-    if ( m_selection != -1 )
-        RefreshLine(m_selection);
+    if ( m_current != wxNOT_FOUND )
+        RefreshLine(m_current);
 
-    m_selection = selection;
+    m_current = current;
 
-    if ( m_selection != -1 )
+    if ( m_current != wxNOT_FOUND )
     {
         // if the line is not visible at all, we scroll it into view but we
         // don't need to refresh it -- it will be redrawn anyhow
-        if ( !IsVisible(m_selection) )
+        if ( !IsVisible(m_current) )
         {
-            ScrollToLine(m_selection);
+            ScrollToLine(m_current);
         }
         else // line is at least partly visible
         {
             // it is, indeed, only partly visible, so scroll it into view to
             // make it entirely visible
-            if ( (size_t)m_selection == GetLastVisibleLine() )
+            if ( (size_t)m_current == GetLastVisibleLine() )
             {
-                ScrollToLine(m_selection);
+                ScrollToLine(m_current);
             }
 
             // but in any case refresh it as even if it was only partly visible
             // before we need to redraw it entirely as its background changed
-            RefreshLine(m_selection);
+            RefreshLine(m_current);
         }
+    }
 
-        // send a notification event if we were not called directly by user
-        if ( sendEvent )
-        {
-            wxCommandEvent event(wxEVT_COMMAND_LISTBOX_SELECTED, GetId());
-            event.SetEventObject(this);
-            event.m_commandInt = selection;
+    return true;
+}
 
-            (void)GetEventHandler()->ProcessEvent(event);
-        }
+void wxVListBox::SendSelectedEvent()
+{
+    wxASSERT_MSG( m_current != wxNOT_FOUND,
+                    _T("SendSelectedEvent() shouldn't be called") );
+
+    wxCommandEvent event(wxEVT_COMMAND_LISTBOX_SELECTED, GetId());
+    event.SetEventObject(this);
+    event.m_commandInt = m_current;
+
+    (void)GetEventHandler()->ProcessEvent(event);
+}
+
+void wxVListBox::SetSelection(int selection)
+{
+    wxASSERT_MSG( !HasMultipleSelection(),
+                  _T("SetSelection() is invalid with multiselection listbox") );
+
+    DoSetCurrent(selection);
+}
+
+size_t wxVListBox::GetSelectedCount() const
+{
+    return m_selStore ? m_selStore->GetSelectedCount()
+                      : m_current == wxNOT_FOUND ? 0 : 1;
+}
+
+int wxVListBox::GetFirstSelected(unsigned long& cookie) const
+{
+    cookie = 0;
+
+    return GetNextSelected(cookie);
+}
+
+int wxVListBox::GetNextSelected(unsigned long& cookie) const
+{
+    wxCHECK_MSG( m_selStore, wxNOT_FOUND,
+                  _T("GetFirst/NextSelected() may only be used with multiselection listboxes") );
+
+    while ( cookie < GetItemCount() )
+    {
+        if ( IsSelected(cookie++) )
+            return cookie - 1;
     }
+
+    return wxNOT_FOUND;
 }
 
 // ----------------------------------------------------------------------------
@@ -175,13 +322,24 @@ void wxVListBox::OnPaint(wxPaintEvent& event)
             // don't allow drawing outside of the lines rectangle
             wxDCClipper clip(dc, rectLine);
 
-            if ( IsSelected(line) )
+            // we need to render selected and current items differently
+            const bool isSelected = IsSelected(line);
+            if ( isSelected || IsCurrent(line) )
             {
-                wxBrush brush(wxSystemSettings::
-                                GetColour(wxSYS_COLOUR_HIGHLIGHT),
-                                wxSOLID);
-                dc.SetBrush(brush);
-                dc.SetPen(*wxTRANSPARENT_PEN);
+                if ( isSelected )
+                {
+                    wxBrush brush(wxSystemSettings::
+                                    GetColour(wxSYS_COLOUR_HIGHLIGHT),
+                                    wxSOLID);
+                    dc.SetBrush(brush);
+                }
+                else // !selected
+                {
+                    dc.SetBrush(*wxTRANSPARENT_BRUSH);
+                }
+
+                dc.SetPen(*(IsCurrent(line) ? wxBLACK_PEN : wxTRANSPARENT_PEN));
+
                 dc.DrawRectangle(rectLine);
             }
 
@@ -206,53 +364,126 @@ void wxVListBox::OnPaint(wxPaintEvent& event)
     }
 }
 
+// ============================================================================
+// wxVListBox keyboard/mouse handling
+// ============================================================================
+
+void wxVListBox::DoHandleItemClick(int item, bool shiftDown, bool ctrlDown)
+{
+    // has anything worth telling the client code about happened?
+    bool notify = false;
+
+    if ( HasMultipleSelection() )
+    {
+        // select the iteem clicked?
+        bool select = true;
+
+        // NB: the keyboard interface we implement here corresponds to
+        //     wxLB_EXTENDED rather than wxLB_MULTIPLE but this one makes more
+        //     sense IMHO
+        if ( shiftDown )
+        {
+            if ( m_current != wxNOT_FOUND )
+            {
+                select = false;
+
+                // only the range from old m_current to new m_current must be
+                // selected
+                if ( DeselectAll() )
+                    notify = true;
+
+                if ( SelectRange(m_current, item) )
+                    notify = true;
+            }
+            //else: treat it as ordinary click/keypress
+        }
+        else if ( ctrlDown )
+        {
+            select = false;
+
+            Toggle(item);
+
+            // the status of the item has definitely changed
+            notify = true;
+        }
+        //else: behave as in single selection case
+
+        if ( select )
+        {
+            // make the clicked item the only selection
+            if ( DeselectAll() )
+                notify = true;
+
+            if ( Select(item) )
+                notify = true;
+        }
+    }
+
+    // in any case the item should become the current one
+    if ( DoSetCurrent(item) )
+    {
+        if ( !HasMultipleSelection() )
+        {
+            // this has also changed the selection for single selection case
+            notify = true;
+        }
+    }
+
+    if ( notify )
+    {
+        // notify the user about the selection change
+        SendSelectedEvent();
+    }
+    //else: nothing changed at all
+}
+
 // ----------------------------------------------------------------------------
-// wxVListBox keyboard handling
+// keyboard handling
 // ----------------------------------------------------------------------------
 
 void wxVListBox::OnKeyDown(wxKeyEvent& event)
 {
-    int selection = 0; // just to silent the stupid compiler warnings
+    int current = 0; // just to silent the stupid compiler warnings
     switch ( event.GetKeyCode() )
     {
         case WXK_HOME:
-            selection = 0;
+            current = 0;
             break;
 
         case WXK_END:
-            selection = GetLineCount() - 1;
+            current = GetLineCount() - 1;
             break;
 
         case WXK_DOWN:
-            if ( m_selection == (int)GetLineCount() - 1 )
+            if ( m_current == (int)GetLineCount() - 1 )
                 return;
 
-            selection = m_selection + 1;
+            current = m_current + 1;
             break;
 
         case WXK_UP:
-            if ( m_selection == -1 )
-                selection = GetLineCount() - 1;
-            else if ( m_selection != 0 )
-                selection = m_selection - 1;
-            else // m_selection == 0
+            if ( m_current == wxNOT_FOUND )
+                current = GetLineCount() - 1;
+            else if ( m_current != 0 )
+                current = m_current - 1;
+            else // m_current == 0
                 return;
             break;
 
         case WXK_PAGEDOWN:
         case WXK_NEXT:
             PageDown();
-            selection = GetFirstVisibleLine();
+            current = GetFirstVisibleLine();
             break;
 
         case WXK_PAGEUP:
         case WXK_PRIOR:
-            if ( m_selection == (int)GetFirstVisibleLine() )
+            if ( m_current == (int)GetFirstVisibleLine() )
             {
                 PageUp();
             }
 
-            selection = GetFirstVisibleLine();
+            current = GetFirstVisibleLine();
             break;
 
         default:
@@ -260,7 +491,7 @@ void wxVListBox::OnKeyDown(wxKeyEvent& event)
             return;
     }
 
-    DoSetSelection(selection);
+    DoHandleItemClick(current, event.ShiftDown(), event.ControlDown());
 }
 
 // ----------------------------------------------------------------------------
@@ -271,13 +502,19 @@ void wxVListBox::OnLeftDown(wxMouseEvent& event)
 {
     int item = HitTest(event.GetPosition());
 
-    DoSetSelection(item);
+    DoHandleItemClick(item, event.ShiftDown(),
+#ifdef __WXMAC__
+                            event.MetaDown()
+#else
+                            event.ControlDown()
+#endif
+                     );
 }
 
 void wxVListBox::OnLeftDClick(wxMouseEvent& event)
 {
     int item = HitTest(event.GetPosition());
-    if ( item != -1 )
+    if ( item != wxNOT_FOUND )
     {
         wxCommandEvent event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, GetId());
         event.SetEventObject(this);