From be465555381c323bdf854da04ca943e15cbb2d2d Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 11 Jun 2003 11:40:37 +0000 Subject: [PATCH] implemented multiple selection git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@21040 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/vlbox.h | 146 +++++++++++++++---- src/generic/vlbox.cpp | 329 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 401 insertions(+), 74 deletions(-) diff --git a/include/wx/vlbox.h b/include/wx/vlbox.h index b1588627ba..ff1dc10de9 100644 --- a/include/wx/vlbox.h +++ b/include/wx/vlbox.h @@ -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() }; diff --git a/src/generic/vlbox.cpp b/src/generic/vlbox.cpp index d5b824c1e1..e15de81c6a 100644 --- a/src/generic/vlbox.cpp +++ b/src/generic/vlbox.cpp @@ -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); -- 2.45.2