// Purpose: generic implementation of wxListCtrl
// Author: Robert Roebling
// Vadim Zeitlin (virtual list control support)
-// Id: $Id$
// Copyright: (c) 1998 Robert Roebling
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
BEGIN_EVENT_TABLE(wxListHeaderWindow,wxWindow)
EVT_PAINT (wxListHeaderWindow::OnPaint)
EVT_MOUSE_EVENTS (wxListHeaderWindow::OnMouse)
- EVT_SET_FOCUS (wxListHeaderWindow::OnSetFocus)
END_EVENT_TABLE()
void wxListHeaderWindow::Init()
m_resizeCursor = NULL;
}
-wxListHeaderWindow::wxListHeaderWindow( wxWindow *win,
- wxWindowID id,
- wxListMainWindow *owner,
- const wxPoint& pos,
- const wxSize& size,
- long style,
- const wxString &name )
- : wxWindow( win, id, pos, size, style, name )
+bool wxListHeaderWindow::Create( wxWindow *win,
+ wxWindowID id,
+ wxListMainWindow *owner,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxString &name )
{
+ if ( !wxWindow::Create(win, id, pos, size, style, name) )
+ return false;
+
Init();
m_owner = owner;
if (!m_hasFont)
SetOwnFont( wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT ));
#endif
+
+ return true;
}
wxListHeaderWindow::~wxListHeaderWindow()
if (m_isDragging)
{
- SendListEvent(wxEVT_COMMAND_LIST_COL_DRAGGING, event.GetPosition());
+ SendListEvent(wxEVT_LIST_COL_DRAGGING, event.GetPosition());
// we don't draw the line beyond our window, but we allow dragging it
// there
m_isDragging = false;
m_dirty = true;
m_owner->SetColumnWidth( m_column, m_currentX - m_minX );
- SendListEvent(wxEVT_COMMAND_LIST_COL_END_DRAG, event.GetPosition());
+ SendListEvent(wxEVT_LIST_COL_END_DRAG, event.GetPosition());
}
else
{
{
if (hit_border && event.LeftDown())
{
- if ( SendListEvent(wxEVT_COMMAND_LIST_COL_BEGIN_DRAG,
+ if ( SendListEvent(wxEVT_LIST_COL_BEGIN_DRAG,
event.GetPosition()) )
{
m_isDragging = true;
}
SendListEvent( event.LeftDown()
- ? wxEVT_COMMAND_LIST_COL_CLICK
- : wxEVT_COMMAND_LIST_COL_RIGHT_CLICK,
+ ? wxEVT_LIST_COL_CLICK
+ : wxEVT_LIST_COL_RIGHT_CLICK,
event.GetPosition());
}
}
}
}
-void wxListHeaderWindow::OnSetFocus( wxFocusEvent &WXUNUSED(event) )
-{
- m_owner->SetFocus();
- m_owner->Update();
-}
-
bool wxListHeaderWindow::SendListEvent(wxEventType type, const wxPoint& pos)
{
wxWindow *parent = GetParent();
m_owner->OnRenameTimer();
}
+//-----------------------------------------------------------------------------
+// wxListFindTimer (internal)
+//-----------------------------------------------------------------------------
+
+void wxListFindTimer::Notify()
+{
+ m_owner->OnFindTimer();
+}
+
//-----------------------------------------------------------------------------
// wxListTextCtrlWrapper (internal)
//-----------------------------------------------------------------------------
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 =
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();
delete m_highlightBrush;
delete m_highlightUnfocusedBrush;
delete m_renameTimer;
+ delete m_findTimer;
}
void wxListMainWindow::SetReportView(bool inReportView)
if ( changed )
{
- SendNotify( line, highlight ? wxEVT_COMMAND_LIST_ITEM_SELECTED
- : wxEVT_COMMAND_LIST_ITEM_DESELECTED );
+ SendNotify( line, highlight ? wxEVT_LIST_ITEM_SELECTED
+ : wxEVT_LIST_ITEM_DESELECTED );
}
return changed;
// tell the caller cache to cache the data
if ( IsVirtual() )
{
- wxListEvent evCache(wxEVT_COMMAND_LIST_CACHE_HINT,
+ wxListEvent evCache(wxEVT_LIST_CACHE_HINT,
GetParent()->GetId());
evCache.SetEventObject( GetParent() );
evCache.m_oldItemIndex = visibleFrom;
{
GetLine(line)->GetItem( 0, le.m_item );
}
- //else: this happens for wxEVT_COMMAND_LIST_ITEM_FOCUSED event
+ //else: this happens for wxEVT_LIST_ITEM_FOCUSED event
}
//else: there may be no more such item
if ( m_renameTimer->IsRunning() )
m_renameTimer->Stop();
- SendNotify(current, wxEVT_COMMAND_LIST_ITEM_FOCUSED);
+ SendNotify(current, wxEVT_LIST_ITEM_FOCUSED);
}
wxTextCtrl *wxListMainWindow::EditLabel(long item, wxClassInfo* textControlClass)
wxCHECK_MSG( (item >= 0) && ((size_t)item < GetItemCount()), NULL,
wxT("wrong index in wxGenericListCtrl::EditLabel()") );
- wxASSERT_MSG( textControlClass->IsKindOf(CLASSINFO(wxTextCtrl)),
+ wxASSERT_MSG( textControlClass->IsKindOf(wxCLASSINFO(wxTextCtrl)),
wxT("EditLabel() needs a text control") );
size_t itemEdit = (size_t)item;
- wxListEvent le( wxEVT_COMMAND_LIST_BEGIN_LABEL_EDIT, GetParent()->GetId() );
+ wxListEvent le( wxEVT_LIST_BEGIN_LABEL_EDIT, GetParent()->GetId() );
le.SetEventObject( GetParent() );
le.m_item.m_itemId =
le.m_itemIndex = item;
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();
bool wxListMainWindow::OnRenameAccept(size_t itemEdit, const wxString& value)
{
- wxListEvent le( wxEVT_COMMAND_LIST_END_LABEL_EDIT, GetParent()->GetId() );
+ wxListEvent le( wxEVT_LIST_END_LABEL_EDIT, GetParent()->GetId() );
le.SetEventObject( GetParent() );
le.m_item.m_itemId =
le.m_itemIndex = itemEdit;
void wxListMainWindow::OnRenameCancelled(size_t itemEdit)
{
// let owner know that the edit was cancelled
- wxListEvent le( wxEVT_COMMAND_LIST_END_LABEL_EDIT, GetParent()->GetId() );
+ wxListEvent le( wxEVT_LIST_END_LABEL_EDIT, GetParent()->GetId() );
le.SetEditCanceled(true);
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__
#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)
{
if (event.RightDown())
{
- SendNotify( (size_t)-1, wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK, event.GetPosition() );
+ SendNotify( (size_t)-1, wxEVT_LIST_ITEM_RIGHT_CLICK, event.GetPosition() );
wxContextMenuEvent evtCtx(wxEVT_CONTEXT_MENU,
GetParent()->GetId(),
m_dragCount = 0;
// The only mouse event that can be generated without any valid item is
- // wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK as it can be useful to have a global
+ // wxEVT_LIST_ITEM_RIGHT_CLICK as it can be useful to have a global
// popup menu for the list control itself which should be shown even when
// the user clicks outside of any item.
if ( !hitResult )
// outside of any item
if (event.RightDown())
{
- SendNotify( (size_t) -1, wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK, event.GetPosition() );
+ SendNotify( (size_t) -1, wxEVT_LIST_ITEM_RIGHT_CLICK, event.GetPosition() );
wxContextMenuEvent evtCtx(
wxEVT_CONTEXT_MENU,
if (m_dragCount != 3)
return;
- int command = event.RightIsDown() ? wxEVT_COMMAND_LIST_BEGIN_RDRAG
- : wxEVT_COMMAND_LIST_BEGIN_DRAG;
+ int command = event.RightIsDown() ? wxEVT_LIST_BEGIN_RDRAG
+ : wxEVT_LIST_BEGIN_DRAG;
SendNotify( m_lineLastClicked, command, m_dragStart );
if ( current == m_lineLastClicked )
{
- SendNotify( current, wxEVT_COMMAND_LIST_ITEM_ACTIVATED );
+ SendNotify( current, wxEVT_LIST_ITEM_ACTIVATED );
return;
}
m_renameTimer->Start(dclick > 0 ? dclick : 250, true);
}
}
+
+ m_lastOnSame = false;
}
- m_lastOnSame = false;
m_lineSelectSingleOnUp = (size_t)-1;
}
else
ReverseHighlight(m_current);
}
- SendNotify( current, wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK, event.GetPosition() );
+ SendNotify( current, wxEVT_LIST_ITEM_RIGHT_CLICK, event.GetPosition() );
// Allow generation of context menu event
event.Skip();
}
else if (event.MiddleDown())
{
- SendNotify( current, wxEVT_COMMAND_LIST_ITEM_MIDDLE_CLICK );
+ SendNotify( current, wxEVT_LIST_ITEM_MIDDLE_CLICK );
}
else if ( event.LeftDown() || forceClick )
{
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();
}
}
// propagate the key event upwards
wxKeyEvent ke(event);
ke.SetEventObject( parent );
+ ke.SetId(GetParent()->GetId());
if (parent->GetEventHandler()->ProcessEvent( ke ))
return;
// send a list event
- wxListEvent le( wxEVT_COMMAND_LIST_KEY_DOWN, parent->GetId() );
+ wxListEvent le( wxEVT_LIST_KEY_DOWN, parent->GetId() );
le.m_item.m_itemId =
le.m_itemIndex = m_current;
if (HasCurrent())
// propagate the key event upwards
wxKeyEvent ke(event);
+ ke.SetEventObject( parent );
+ ke.SetId(GetParent()->GetId());
if (parent->GetEventHandler()->ProcessEvent( ke ))
return;
// propagate the char event upwards
wxKeyEvent ke(event);
ke.SetEventObject( parent );
+ ke.SetId(GetParent()->GetId());
if (parent->GetEventHandler()->ProcessEvent( ke ))
return;
event.m_keyCode = WXK_RIGHT;
}
- switch ( event.GetKeyCode() )
+ int keyCode = event.GetKeyCode();
+ switch ( keyCode )
{
case WXK_UP:
if ( m_current > 0 )
}
else // normal space press
{
- SendNotify( m_current, wxEVT_COMMAND_LIST_ITEM_ACTIVATED );
+ SendNotify( m_current, wxEVT_LIST_ITEM_ACTIVATED );
}
}
else // multiple selection
case WXK_RETURN:
case WXK_EXECUTE:
- SendNotify( m_current, wxEVT_COMMAND_LIST_ITEM_ACTIVATED );
+ SendNotify( m_current, wxEVT_LIST_ITEM_ACTIVATED );
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();
+ }
}
}
ResetVisibleLinesRange();
}
- SendNotify( index, wxEVT_COMMAND_LIST_DELETE_ITEM, wxDefaultPosition );
+ SendNotify( index, wxEVT_LIST_DELETE_ITEM, wxDefaultPosition );
if ( IsVirtual() )
{
// for all of them: this is compatible with wxMSW and documented in
// DeleteAllItems() description
- wxListEvent event( wxEVT_COMMAND_LIST_DELETE_ALL_ITEMS, GetParent()->GetId() );
+ wxListEvent event( wxEVT_LIST_DELETE_ALL_ITEMS, GetParent()->GetId() );
event.SetEventObject( GetParent() );
GetParent()->GetEventHandler()->ProcessEvent( event );
if ( HasCurrent() && m_current >= id )
m_current++;
- SendNotify(id, wxEVT_COMMAND_LIST_INSERT_ITEM);
+ SendNotify(id, wxEVT_LIST_INSERT_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() )
{
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 );
}
// invalidate it as it has to be recalculated
m_headerWidth = 0;
}
+ return idx;
}
int wxListMainWindow::GetItemWidthWithImage(wxListItem * item)
*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
// -------------------------------------------------------------------------------------
if (needs_header)
{
- m_headerWin = new wxListHeaderWindow
+ // Notice that we must initialize m_headerWin first, and create the
+ // real window only later, so that the test in the beginning of the
+ // function blocks repeated creation of the header as it could happen
+ // before via wxNavigationEnabled::AddChild() -> ToggleWindowStyle() ->
+ // SetWindowStyleFlag().
+ m_headerWin = new wxListHeaderWindow();
+ m_headerWin->Create
(
this, wxID_ANY, m_mainWin,
wxPoint(0,0),
),
wxTAB_TRAVERSAL
);
-
+
#if defined( __WXMAC__ )
static wxFont font( wxOSX_SYSTEM_FONT_SMALL );
m_headerWin->SetFont( font );
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 );
// 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 )
{
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 )
#endif
}
-void wxGenericListCtrl::DoClientToScreen( int *x, int *y ) const
-{
- // It's not clear whether this can be called before m_mainWin is created
- // but it seems better to be on the safe side and check.
- if ( m_mainWin )
- m_mainWin->DoClientToScreen(x, y);
- else
- wxListCtrlBase::DoClientToScreen(x, y);
-}
-
-void wxGenericListCtrl::DoScreenToClient( int *x, int *y ) const
-{
- // At least in wxGTK/Univ build this method can be called before m_mainWin
- // is created so avoid crashes in this case.
- if ( m_mainWin )
- m_mainWin->DoScreenToClient(x, y);
- else
- wxListCtrlBase::DoScreenToClient(x, y);
-}
-
-void wxGenericListCtrl::SetFocus()
-{
- // The test in window.cpp fails as we are a composite
- // window, so it checks against "this", but not m_mainWin.
- if ( DoFindFocus() != this )
- m_mainWin->SetFocus();
-}
-
wxSize wxGenericListCtrl::DoGetBestClientSize() const
{
- // Something is better than nothing even if this is completely arbitrary.
- wxSize sizeBest(100, 80);
+ // The base class version can compute the best size in report view only.
+ wxSize sizeBest = wxListCtrlBase::DoGetBestClientSize();
if ( !InReportView() )
{
return -1;
}
-wxListItemAttr *
-wxGenericListCtrl::OnGetItemAttr(long WXUNUSED_UNLESS_DEBUG(item)) const
-{
- wxASSERT_MSG( item >= 0 && item < GetItemCount(),
- wxT("invalid item index in OnGetItemAttr()") );
-
- // no attributes by default
- return NULL;
-}
-
void wxGenericListCtrl::SetItemCount(long count)
{
wxASSERT_MSG( IsVirtual(), wxT("this is for virtual controls only") );
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'.