X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/ec3f5752a02c56fa6cfa0ce593cc7439d547009c..36a0190ebd5bd9a7302f60f6dcd608b80574e21c:/src/generic/listctrl.cpp diff --git a/src/generic/listctrl.cpp b/src/generic/listctrl.cpp index 09354910b6..58a5c733eb 100644 --- a/src/generic/listctrl.cpp +++ b/src/generic/listctrl.cpp @@ -3,7 +3,6 @@ // Purpose: generic implementation of wxListCtrl // Author: Robert Roebling // Vadim Zeitlin (virtual list control support) -// Id: $Id$ // Copyright: (c) 1998 Robert Roebling // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// @@ -342,12 +341,23 @@ bool wxListHeaderData::IsHit( int x, int y ) const void wxListHeaderData::GetItem( wxListItem& item ) { - item.m_mask = m_mask; - item.m_text = m_text; - item.m_image = m_image; - item.m_format = m_format; - item.m_width = m_width; - item.m_state = m_state; + long mask = item.m_mask; + if ( !mask ) + { + // by default, get everything for backwards compatibility + mask = -1; + } + + if ( mask & wxLIST_MASK_STATE ) + item.m_state = m_state; + if ( mask & wxLIST_MASK_TEXT ) + item.m_text = m_text; + if ( mask & wxLIST_MASK_IMAGE ) + item.m_image = m_image; + if ( mask & wxLIST_MASK_WIDTH ) + item.m_width = m_width; + if ( mask & wxLIST_MASK_FORMAT ) + item.m_format = m_format; } int wxListHeaderData::GetImage() const @@ -923,7 +933,6 @@ void wxListLineData::ReverseHighlight( void ) 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() @@ -942,15 +951,17 @@ wxListHeaderWindow::wxListHeaderWindow() 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; @@ -968,6 +979,8 @@ wxListHeaderWindow::wxListHeaderWindow( wxWindow *win, if (!m_hasFont) SetOwnFont( wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT )); #endif + + return true; } wxListHeaderWindow::~wxListHeaderWindow() @@ -1204,7 +1217,7 @@ void wxListHeaderWindow::OnMouse( wxMouseEvent &event ) 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 @@ -1223,7 +1236,7 @@ void wxListHeaderWindow::OnMouse( wxMouseEvent &event ) 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 { @@ -1276,7 +1289,7 @@ void wxListHeaderWindow::OnMouse( wxMouseEvent &event ) { 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; @@ -1305,8 +1318,8 @@ void wxListHeaderWindow::OnMouse( wxMouseEvent &event ) } 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()); } } @@ -1330,12 +1343,6 @@ void wxListHeaderWindow::OnMouse( wxMouseEvent &event ) } } -void wxListHeaderWindow::OnSetFocus( wxFocusEvent &WXUNUSED(event) ) -{ - m_owner->SetFocus(); - m_owner->Update(); -} - bool wxListHeaderWindow::SendListEvent(wxEventType type, const wxPoint& pos) { wxWindow *parent = GetParent(); @@ -1367,6 +1374,15 @@ void wxListRenameTimer::Notify() m_owner->OnRenameTimer(); } +//----------------------------------------------------------------------------- +// wxListFindTimer (internal) +//----------------------------------------------------------------------------- + +void wxListFindTimer::Notify() +{ + m_owner->OnFindTimer(); +} + //----------------------------------------------------------------------------- // wxListTextCtrlWrapper (internal) //----------------------------------------------------------------------------- @@ -1461,6 +1477,12 @@ bool wxListTextCtrlWrapper::AcceptChanges() } void wxListTextCtrlWrapper::OnChar( wxKeyEvent &event ) +{ + if ( !CheckForEndEditKey(event) ) + event.Skip(); +} + +bool wxListTextCtrlWrapper::CheckForEndEditKey(const wxKeyEvent& event) { switch ( event.m_keyCode ) { @@ -1473,8 +1495,10 @@ void wxListTextCtrlWrapper::OnChar( wxKeyEvent &event ) break; default: - event.Skip(); + return false; } + + return true; } void wxListTextCtrlWrapper::OnKeyUp( wxKeyEvent &event ) @@ -1518,6 +1542,7 @@ void wxListTextCtrlWrapper::OnKillFocus( wxFocusEvent &event ) BEGIN_EVENT_TABLE(wxListMainWindow, wxWindow) EVT_PAINT (wxListMainWindow::OnPaint) EVT_MOUSE_EVENTS (wxListMainWindow::OnMouse) + EVT_CHAR_HOOK (wxListMainWindow::OnCharHook) EVT_CHAR (wxListMainWindow::OnChar) EVT_KEY_DOWN (wxListMainWindow::OnKeyDown) EVT_KEY_UP (wxListMainWindow::OnKeyUp) @@ -1550,6 +1575,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 = @@ -1569,10 +1596,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(); @@ -1613,6 +1639,7 @@ wxListMainWindow::~wxListMainWindow() delete m_highlightBrush; delete m_highlightUnfocusedBrush; delete m_renameTimer; + delete m_findTimer; } void wxListMainWindow::SetReportView(bool inReportView) @@ -1864,8 +1891,8 @@ bool wxListMainWindow::HighlightLine( size_t line, bool highlight ) 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; @@ -2019,10 +2046,11 @@ void wxListMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) // 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; + evCache.m_item.m_itemId = evCache.m_itemIndex = visibleTo; GetParent()->GetEventHandler()->ProcessEvent( evCache ); } @@ -2153,6 +2181,7 @@ void wxListMainWindow::SendNotify( size_t line, wxListEvent le( command, GetParent()->GetId() ); le.SetEventObject( GetParent() ); + le.m_item.m_itemId = le.m_itemIndex = line; // set only for events which have position @@ -2169,7 +2198,7 @@ void wxListMainWindow::SendNotify( size_t line, { 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 @@ -2185,7 +2214,7 @@ void wxListMainWindow::ChangeCurrent(size_t current) 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) @@ -2193,13 +2222,14 @@ 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; wxListLineData *data = GetLine(itemEdit); wxCHECK_MSG( data, NULL, wxT("invalid index in EditLabel()") ); @@ -2211,19 +2241,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(); @@ -2240,8 +2261,9 @@ void wxListMainWindow::OnRenameTimer() 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; wxListLineData *data = GetLine(itemEdit); @@ -2257,11 +2279,12 @@ bool wxListMainWindow::OnRenameAccept(size_t itemEdit, const wxString& value) 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); le.SetEventObject( GetParent() ); + le.m_item.m_itemId = le.m_itemIndex = itemEdit; wxListLineData *data = GetLine(itemEdit); @@ -2271,6 +2294,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__ @@ -2283,10 +2318,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) @@ -2300,7 +2341,7 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) { 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(), @@ -2346,43 +2387,25 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) } } + // Update drag events counter first as we must do it even if the mouse is + // not on any item right now as we must keep count in case we started + // dragging from the empty control area but continued to do it over a valid + // item -- in this situation we must not start dragging this item. if (event.Dragging()) - { - if (m_dragCount == 0) - { - // we have to report the raw, physical coords as we want to be - // able to call HitTest(event.m_pointDrag) from the user code to - // get the item being dragged - m_dragStart = event.GetPosition(); - } - m_dragCount++; - - if (m_dragCount != 3) - return; - - int command = event.RightIsDown() ? wxEVT_COMMAND_LIST_BEGIN_RDRAG - : wxEVT_COMMAND_LIST_BEGIN_DRAG; - - wxListEvent le( command, GetParent()->GetId() ); - le.SetEventObject( GetParent() ); - le.m_itemIndex = m_lineLastClicked; - le.m_pointDrag = m_dragStart; - GetParent()->GetEventHandler()->ProcessEvent( le ); - - return; - } else - { m_dragCount = 0; - } + // The only mouse event that can be generated without any valid item is + // 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, @@ -2400,6 +2423,27 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) return; } + if ( event.Dragging() ) + { + if (m_dragCount == 1) + { + // we have to report the raw, physical coords as we want to be + // able to call HitTest(event.m_pointDrag) from the user code to + // get the item being dragged + m_dragStart = event.GetPosition(); + } + + if (m_dragCount != 3) + return; + + int command = event.RightIsDown() ? wxEVT_LIST_BEGIN_RDRAG + : wxEVT_LIST_BEGIN_DRAG; + + SendNotify( m_lineLastClicked, command, m_dragStart ); + + return; + } + bool forceClick = false; if (event.ButtonDClick()) { @@ -2410,7 +2454,7 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) if ( current == m_lineLastClicked ) { - SendNotify( current, wxEVT_COMMAND_LIST_ITEM_ACTIVATED ); + SendNotify( current, wxEVT_LIST_ITEM_ACTIVATED ); return; } @@ -2444,9 +2488,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 @@ -2471,14 +2516,14 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) 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 ) { @@ -2538,8 +2583,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(); } } @@ -2685,11 +2738,13 @@ 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; // 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()) GetLine(m_current)->GetItem( 0, le.m_item ); @@ -2707,12 +2762,30 @@ 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; event.Skip(); } +void wxListMainWindow::OnCharHook( wxKeyEvent &event ) +{ + if ( m_textctrlWrapper ) + { + // When an in-place editor is active we should ensure that it always + // gets the key events that are special to it. + if ( m_textctrlWrapper->CheckForEndEditKey(event) ) + { + // Skip the call to wxEvent::Skip() below. + return; + } + } + + event.Skip(); +} + void wxListMainWindow::OnChar( wxKeyEvent &event ) { wxWindow *parent = GetParent(); @@ -2720,6 +2793,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; @@ -2745,7 +2819,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 ) @@ -2828,7 +2903,7 @@ void wxListMainWindow::OnChar( wxKeyEvent &event ) } else // normal space press { - SendNotify( m_current, wxEVT_COMMAND_LIST_ITEM_ACTIVATED ); + SendNotify( m_current, wxEVT_LIST_ITEM_ACTIVATED ); } } else // multiple selection @@ -2839,11 +2914,83 @@ void wxListMainWindow::OnChar( wxKeyEvent &event ) 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(); + } } } @@ -2932,17 +3079,6 @@ void wxListMainWindow::GetImageSize( int index, int &width, int &height ) const } } -int wxListMainWindow::GetTextLength( const wxString &s ) const -{ - wxClientDC dc( wxConstCast(this, wxListMainWindow) ); - dc.SetFont( GetFont() ); - - wxCoord lw; - dc.GetTextExtent( s, &lw, NULL ); - - return lw + AUTOSIZE_COL_MARGIN; -} - void wxListMainWindow::SetImageList( wxImageList *imageList, int which ) { m_dirty = true; @@ -2985,18 +3121,42 @@ int wxListMainWindow::GetItemSpacing( bool isSmall ) // columns // ---------------------------------------------------------------------------- -void wxListMainWindow::SetColumn( int col, wxListItem &item ) +int +wxListMainWindow::ComputeMinHeaderWidth(const wxListHeaderData* column) const +{ + wxClientDC dc(const_cast(this)); + + int width = dc.GetTextExtent(column->GetText()).x + AUTOSIZE_COL_MARGIN; + + width += 2*EXTRA_WIDTH; + + // check for column header's image availability + const int image = column->GetImage(); + if ( image != -1 ) + { + if ( m_small_image_list ) + { + int ix = 0, iy = 0; + m_small_image_list->GetSize(image, ix, iy); + width += ix + HEADER_IMAGE_MARGIN_IN_REPORT_MODE; + } + } + + return width; +} + +void wxListMainWindow::SetColumn( int col, const wxListItem &item ) { wxListHeaderDataList::compatibility_iterator node = m_columns.Item( col ); wxCHECK_RET( node, wxT("invalid column index in SetColumn") ); - if ( item.m_width == wxLIST_AUTOSIZE_USEHEADER ) - item.m_width = GetTextLength( item.m_text ); - wxListHeaderData *column = node->GetData(); column->SetItem( item ); + if ( item.m_width == wxLIST_AUTOSIZE_USEHEADER ) + column->SetWidth(ComputeMinHeaderWidth(column)); + wxListHeaderWindow *headerWin = GetListCtrl()->m_headerWin; if ( headerWin ) headerWin->m_dirty = true; @@ -3030,29 +3190,13 @@ void wxListMainWindow::SetColumnWidth( int col, int width ) if (width == wxLIST_AUTOSIZE_USEHEADER) { - width = GetTextLength(column->GetText()); - width += 2*EXTRA_WIDTH; - - // check for column header's image availability - const int image = column->GetImage(); - if ( image != -1 ) - { - if ( m_small_image_list ) - { - int ix = 0, iy = 0; - m_small_image_list->GetSize(image, ix, iy); - width += ix + HEADER_IMAGE_MARGIN_IN_REPORT_MODE; - } - } + width = ComputeMinHeaderWidth(column); } else if ( width == wxLIST_AUTOSIZE ) { - if ( IsVirtual() ) - { - // TODO: determine the max width somehow... - width = WIDTH_COL_DEFAULT; - } - else // !virtual + width = ComputeMinHeaderWidth(column); + + if ( !IsVirtual() ) { wxClientDC dc(this); dc.SetFont( GetFont() ); @@ -3082,8 +3226,9 @@ void wxListMainWindow::SetColumnWidth( int col, int width ) m_aColWidths.Item(col)->nMaxWidth = max; } - max = m_aColWidths.Item(col)->nMaxWidth; - width = max + AUTOSIZE_COL_MARGIN; + max = m_aColWidths.Item(col)->nMaxWidth + AUTOSIZE_COL_MARGIN; + if ( width < max ) + width = max; } } @@ -3156,10 +3301,14 @@ void wxListMainWindow::SetItem( wxListItem &item ) } } - // update the item on screen - wxRect rectItem; - GetItemRect(id, rectItem); - RefreshRect(rectItem); + // update the item on screen unless we're going to update everything soon + // anyhow + if ( !m_dirty ) + { + wxRect rectItem; + GetItemRect(id, rectItem); + RefreshRect(rectItem); + } } void wxListMainWindow::SetItemStateAll(long state, long stateMask) @@ -3604,6 +3753,9 @@ void wxListMainWindow::RecalculatePositions(bool noRefresh) int x = EXTRA_BORDER_X; int y = EXTRA_BORDER_Y; + + // Note that "row" here is vertical, i.e. what is called + // "column" in many other places in wxWidgets. int maxWidthInThisRow = 0; m_linesPerPage = 0; @@ -3625,8 +3777,24 @@ void wxListMainWindow::RecalculatePositions(bool noRefresh) if (currentlyVisibleLines > m_linesPerPage) m_linesPerPage = currentlyVisibleLines; - if ( y + sizeLine.y >= clientHeight ) + // Have we reached the end of the row either because no + // more items would fit or because there are simply no more + // items? + if ( y + sizeLine.y >= clientHeight + || i == count - 1) { + // Adjust all items in this row to have the same + // width to ensure that they all align horizontally in + // icon view. + if ( HasFlag(wxLC_ICON) || HasFlag(wxLC_SMALL_ICON) ) + { + size_t firstRowLine = i - currentlyVisibleLines + 1; + for (size_t j = firstRowLine; j <= i; j++) + { + GetLine(j)->m_gi->ExtendWidth(maxWidthInThisRow); + } + } + currentlyVisibleLines = 0; y = EXTRA_BORDER_Y; maxWidthInThisRow += MARGIN_BETWEEN_ROWS; @@ -3635,10 +3803,6 @@ void wxListMainWindow::RecalculatePositions(bool noRefresh) maxWidthInThisRow = 0; } - // We have reached the last item. - if ( i == count - 1 ) - entireWidth += maxWidthInThisRow; - if ( (tries == 0) && (entireWidth + SCROLL_UNIT_X > clientWidth) ) { @@ -3777,7 +3941,7 @@ void wxListMainWindow::DeleteItem( long lindex ) ResetVisibleLinesRange(); } - SendNotify( index, wxEVT_COMMAND_LIST_DELETE_ITEM, wxDefaultPosition ); + SendNotify( index, wxEVT_LIST_DELETE_ITEM, wxDefaultPosition ); if ( IsVirtual() ) { @@ -3811,6 +3975,21 @@ void wxListMainWindow::DeleteColumn( int col ) for ( size_t i = 0; i < m_lines.GetCount(); i++ ) { wxListLineData * const line = GetLine(i); + + // In the following atypical but possible scenario it can be + // legal to call DeleteColumn() but the items may not have any + // values for it: + // 1. In report view, insert a second column. + // 2. Still in report view, add an item with 2 values. + // 3. Switch to an icon (or list) view. + // 4. Add an item -- necessarily with 1 value only. + // 5. Switch back to report view. + // 6. Call DeleteColumn(). + // So we need to check for this as otherwise we would simply crash + // if this happens. + if ( line->m_items.GetCount() <= static_cast(col) ) + continue; + wxListItemDataList::compatibility_iterator n = line->m_items.Item( col ); delete n->GetData(); line->m_items.Erase(n); @@ -3840,7 +4019,7 @@ void wxListMainWindow::DoDeleteAllItems() // 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 ); @@ -4027,14 +4206,14 @@ void wxListMainWindow::InsertItem( wxListItem &item ) wxListLineData *line = new wxListLineData(this); line->SetItem( item.m_col, item ); - if ( item.m_mask & wxLIST_MASK_IMAGE && item.GetImage() != -1) + if ( item.m_mask & wxLIST_MASK_IMAGE ) { // Reset the buffered height if it's not big enough for the new image. - if (m_small_image_list) + int image = item.GetImage(); + if ( m_small_image_list && image != -1 && InReportView() ) { int imageWidth, imageHeight; - m_small_image_list->GetSize(item.GetImage(), - imageWidth, imageHeight); + m_small_image_list->GetSize(image, imageWidth, imageHeight); if ( imageHeight > m_lineHeight ) m_lineHeight = 0; @@ -4051,20 +4230,22 @@ void wxListMainWindow::InsertItem( wxListItem &item ) 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, wxListItem &item ) +long wxListMainWindow::InsertColumn( long col, const wxListItem &item ) { + long idx = -1; + m_dirty = true; if ( InReportView() ) { + wxListHeaderData *column = new wxListHeaderData( item ); if (item.m_width == wxLIST_AUTOSIZE_USEHEADER) - item.m_width = GetTextLength( item.m_text ); + column->SetWidth(ComputeMinHeaderWidth(column)); - wxListHeaderData *column = new wxListHeaderData( item ); wxColWidthInfo *colWidthInfo = new wxColWidthInfo(); bool insert = (col >= 0) && ((size_t)col < m_columns.GetCount()); @@ -4074,9 +4255,11 @@ void wxListMainWindow::InsertColumn( long col, 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 ); } @@ -4098,6 +4281,7 @@ void wxListMainWindow::InsertColumn( long col, wxListItem &item ) // invalidate it as it has to be recalculated m_headerWidth = 0; } + return idx; } int wxListMainWindow::GetItemWidthWithImage(wxListItem * item) @@ -4228,13 +4412,68 @@ 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 // ------------------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS(wxGenericListCtrl, wxControl) -BEGIN_EVENT_TABLE(wxGenericListCtrl,wxControl) +BEGIN_EVENT_TABLE(wxGenericListCtrl,wxListCtrlBase) EVT_SIZE(wxGenericListCtrl::OnSize) EVT_SCROLLWIN(wxGenericListCtrl::OnScroll) END_EVENT_TABLE() @@ -4273,7 +4512,13 @@ void wxGenericListCtrl::CreateOrDestroyHeaderWindowAsNeeded() 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), @@ -4284,7 +4529,7 @@ void wxGenericListCtrl::CreateOrDestroyHeaderWindowAsNeeded() ), wxTAB_TRAVERSAL ); - + #if defined( __WXMAC__ ) static wxFont font( wxOSX_SYSTEM_FONT_SMALL ); m_headerWin->SetFont( font ); @@ -4314,15 +4559,12 @@ bool wxGenericListCtrl::Create(wxWindow *parent, wxASSERT_MSG( (style & wxLC_MASK_TYPE), wxT("wxListCtrl style should have exactly one mode bit set") ); - if ( !wxControl::Create( parent, id, pos, size, style|wxVSCROLL|wxHSCROLL, validator, name ) ) + if ( !wxListCtrlBase::Create( parent, id, pos, size, + style | wxVSCROLL | wxHSCROLL, + 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 ); @@ -4352,7 +4594,7 @@ WXLRESULT wxGenericListCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) { - WXLRESULT rc = wxControl::MSWWindowProc(nMsg, wParam, lParam); + WXLRESULT rc = wxListCtrlBase::MSWWindowProc(nMsg, wParam, lParam); // we need to process arrows ourselves for scrolling if ( nMsg == WM_GETDLGCODE ) @@ -4379,13 +4621,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 ) @@ -4460,7 +4703,7 @@ bool wxGenericListCtrl::GetColumn(int col, wxListItem &item) const return true; } -bool wxGenericListCtrl::SetColumn( int col, wxListItem& item ) +bool wxGenericListCtrl::SetColumn( int col, const wxListItem& item ) { m_mainWin->SetColumn( col, item ); return true; @@ -4795,6 +5038,10 @@ bool wxGenericListCtrl::DeleteColumn( int col ) // if we don't have the header any longer, we need to relayout the window // if ( !GetColumnCount() ) + + // Ensure that the non-existent columns are really removed from display. + Refresh(); + return true; } @@ -4873,35 +5120,18 @@ long wxGenericListCtrl::InsertItem( long index, const wxString &label, int image return InsertItem( info ); } -long wxGenericListCtrl::InsertColumn( long col, wxListItem &item ) +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; -} - -long wxGenericListCtrl::InsertColumn( long col, const wxString &heading, - int format, int width ) -{ - wxListItem item; - item.m_mask = wxLIST_MASK_TEXT | wxLIST_MASK_FORMAT; - item.m_text = heading; - if (width >= -2) - { - item.m_mask |= wxLIST_MASK_WIDTH; - item.m_width = width; - } - - item.m_format = format; - - return InsertColumn( col, item ); + return idx; } bool wxGenericListCtrl::ScrollList( int dx, int dy ) @@ -5068,38 +5298,10 @@ bool wxGenericListCtrl::DoPopupMenu( wxMenu *menu, int x, int y ) #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 - wxControl::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 - wxControl::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() ) { @@ -5184,16 +5386,6 @@ int wxGenericListCtrl::OnGetItemColumnImage(long item, long column) const 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") ); @@ -5211,6 +5403,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'.