#include "wx/vscroll.h" // base class
+class WXDLLEXPORT wxSelectionStore;
+
#define wxVListBoxNameStr _T("wxVListBox")
// ----------------------------------------------------------------------------
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
// ----------
// 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
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
// 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()
};
#endif //WX_PRECOMP
#include "wx/vlbox.h"
+#include "wx/selstore.h"
// ----------------------------------------------------------------------------
// event tables
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)
{
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;
}
// ----------------------------------------------------------------------------
// 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);
}
}
}
+// ============================================================================
+// 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:
return;
}
- DoSetSelection(selection);
+ DoHandleItemClick(current, event.ShiftDown(), event.ControlDown());
}
// ----------------------------------------------------------------------------
{
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);