X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/614391dc087ca70b145e07c442815217f8aa3162..c5b7bb59ec1c1a4326b58ce48e1feb76544bf414:/src/generic/listctrl.cpp diff --git a/src/generic/listctrl.cpp b/src/generic/listctrl.cpp index 1b48ff05de..69f4d47a54 100644 --- a/src/generic/listctrl.cpp +++ b/src/generic/listctrl.cpp @@ -9,42 +9,10 @@ ///////////////////////////////////////////////////////////////////////////// /* - FIXME for virtual list controls + TODO - +1. clicking on the item with a mouse is awfully slow, what is going on? - note that selecting with keyboard seems to be much faster - => fixed HighlightAll() - iterating over 1000000 items *is* slow - - 2. background colour is wrong? - */ - -/* - TODO for better virtual list control support: - - 1. less dumb line caching, we should cache at least all those visible - in the control itself and probably twice as many (we might also need to - cache the first one always for geometry calculations?) - - +2. storing selections: we can't use an array to store the selected indices - like right now as selecting all in a control with 1000000 items is not - doable like this - instead, store selections as collection of individual - items and ranges - - => wxSelectionStore - - 3. we need to implement searching/sorting somehow - - 4. the idea of storing the line index in the line itself is really stupid, - we shouldn't need it - but for this we have to get rid of all calles to - wxListLineData::GetFoo() and replace them with something like - if ( IsVirtual() - ... we have it ourselves ... - else - line->GetFoo(); - - => done - - 5. attributes support: we need OnGetItemAttr() as well! + 1. we need to implement searching/sorting for virtual controls somehow + ?2. when changing selection the lines are refreshed twice */ // ============================================================================ @@ -97,6 +65,10 @@ DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_DESELECTED) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_KEY_DOWN) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_INSERT_ITEM) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_COL_CLICK) +DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_COL_RIGHT_CLICK) +DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_COL_BEGIN_DRAG) +DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_COL_DRAGGING) +DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_COL_END_DRAG) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_MIDDLE_CLICK) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_ACTIVATED) @@ -131,6 +103,9 @@ static const int AUTOSIZE_COL_MARGIN = 10; static const int WIDTH_COL_DEFAULT = 80; static const int WIDTH_COL_MIN = 10; +// the space between the image and the text in the report mode +static const int IMAGE_MARGIN_IN_REPORT_MODE = 5; + // ============================================================================ // private classes // ============================================================================ @@ -175,7 +150,14 @@ public: bool SelectItem(size_t item, bool select = TRUE); // select the range of items - void SelectRange(size_t itemFrom, size_t itemTo, bool select = TRUE); + // + // return true and fill the itemsChanged array with the indices of items + // which have changed state if "few" of them did, otherwise return false + // (meaning that too many items changed state to bother counting them + // individually) + bool SelectRange(size_t itemFrom, size_t itemTo, + bool select = TRUE, + wxArrayInt *itemsChanged = NULL); // return true if the given item is selected bool IsSelected(size_t item) const; @@ -282,16 +264,6 @@ protected: class WXDLLEXPORT wxListHeaderData : public wxObject { -protected: - long m_mask; - int m_image; - wxString m_text; - int m_format; - int m_width; - int m_xpos, - m_ypos; - int m_height; - public: wxListHeaderData(); wxListHeaderData( const wxListItem &info ); @@ -313,8 +285,18 @@ public: int GetWidth() const; int GetFormat() const; +protected: + long m_mask; + int m_image; + wxString m_text; + int m_format; + int m_width; + int m_xpos, + m_ypos; + int m_height; + private: - DECLARE_DYNAMIC_CLASS(wxListHeaderData); + void Init(); }; //----------------------------------------------------------------------------- @@ -417,10 +399,10 @@ private: // get the mode (i.e. style) of the list control inline int GetMode() const; - void SetAttributes(wxDC *dc, + // prepare the DC for drawing with these item's attributes, return true if + // we need to draw the items background to highlight it, false otherwise + bool SetAttributes(wxDC *dc, const wxListItemAttr *attr, - const wxColour& colText, - const wxFont& font, bool highlight); // these are only used by GetImage/SetImage above, we don't support images @@ -457,7 +439,6 @@ protected: public: wxListHeaderWindow(); - virtual ~wxListHeaderWindow(); wxListHeaderWindow( wxWindow *win, wxWindowID id, @@ -467,6 +448,8 @@ public: long style = 0, const wxString &name = "wxlistctrlcolumntitles" ); + virtual ~wxListHeaderWindow(); + void DoDrawRect( wxDC *dc, int x, int y, int w, int h ); void DrawCurrent(); void AdjustDC(wxDC& dc); @@ -479,6 +462,9 @@ public: bool m_dirty; private: + // common part of all ctors + void Init(); + DECLARE_DYNAMIC_CLASS(wxListHeaderWindow) DECLARE_EVENT_TABLE() }; @@ -587,6 +573,9 @@ public: void RefreshLine( size_t line ); void RefreshLines( size_t lineFrom, size_t lineTo ); + // refresh all selected items + void RefreshSelected(); + // refresh all lines below the given one: the difference with // RefreshLines() is that the index here might not be a valid one (happens // when the last line is deleted) @@ -616,12 +605,17 @@ public: // return the hit code for the corresponding position (in this line) long HitTestLine(size_t line, int x, int y) const; + // bring the selected item into view, scrolling to it if necessary + void MoveToItem(size_t item); + + // bring the current item into view + void MoveToFocus() { MoveToItem(m_current); } + void EditLabel( long item ); void OnRenameTimer(); void OnRenameAccept(); void OnMouse( wxMouseEvent &event ); - void MoveToFocus(); // called to switch the selection from the current item to newCurrent, void OnArrowChar( size_t newCurrent, const wxKeyEvent& event ); @@ -662,7 +656,7 @@ public: int GetSelectedItemCount(); // set the scrollbars and update the positions of the items - void RecalculatePositions(); + void RecalculatePositions(bool noRefresh = FALSE); // refresh the window and the header void RefreshAll(); @@ -717,6 +711,12 @@ public: // get the y position of the given line (only for report view) wxCoord GetLineY(size_t line) const; + // get the brush to use for the item highlighting + wxBrush *GetHighlightBrush() const + { + return m_hasFocus ? m_highlightBrush : m_highlightUnfocusedBrush; + } + //protected: // the array of all line objects for a non virtual list control wxListLineDataArray m_lines; @@ -741,7 +741,6 @@ public: // call bool m_dirty; - wxBrush *m_highlightBrush; wxColour *m_highlightColour; int m_xScroll, m_yScroll; @@ -819,6 +818,9 @@ private: // initialize the current item if needed void UpdateCurrent(); + // delete all items but don't refresh: called from dtor + void DoDeleteAllItems(); + // called when an item is [un]focuded, i.e. becomes [not] current // // currently unused @@ -837,6 +839,10 @@ private: size_t m_lineFrom, m_lineTo; + // the brushes to use for item highlighting when we do/don't have focus + wxBrush *m_highlightBrush, + *m_highlightUnfocusedBrush; + DECLARE_DYNAMIC_CLASS(wxListMainWindow); DECLARE_EVENT_TABLE() }; @@ -887,12 +893,19 @@ bool wxSelectionStore::SelectItem(size_t item, bool select) return FALSE; } -void wxSelectionStore::SelectRange(size_t itemFrom, size_t itemTo, bool select) +bool wxSelectionStore::SelectRange(size_t itemFrom, size_t itemTo, + bool select, + wxArrayInt *itemsChanged) { + // 100 is hardcoded but it shouldn't matter much: the important thing is + // that we don't refresh everything when really few (e.g. 1 or 2) items + // change state + static const size_t MANY_ITEMS = 100; + wxASSERT_MSG( itemFrom <= itemTo, _T("should be in order") ); // are we going to have more [un]selected items than the other ones? - if ( itemTo - itemFrom > m_count / 2 ) + if ( itemTo - itemFrom > m_count/2 ) { if ( select != m_defaultState ) { @@ -919,6 +932,9 @@ void wxSelectionStore::SelectRange(size_t itemFrom, size_t itemTo, bool select) if ( selOld.Index(item) == wxNOT_FOUND ) m_itemsSel.Add(item); } + + // many items (> half) changed state + itemsChanged = NULL; } else // select == m_defaultState { @@ -942,6 +958,17 @@ void wxSelectionStore::SelectRange(size_t itemFrom, size_t itemTo, bool select) // delete all of them (from end to avoid changing indices) for ( int i = end; i >= (int)start; i-- ) { + if ( itemsChanged ) + { + if ( itemsChanged->GetCount() > MANY_ITEMS ) + { + // stop counting (see comment below) + itemsChanged = NULL; + } + + itemsChanged->Add(m_itemsSel[i]); + } + m_itemsSel.RemoveAt(i); } } @@ -949,12 +976,31 @@ void wxSelectionStore::SelectRange(size_t itemFrom, size_t itemTo, bool select) } else // "few" items change state { + if ( itemsChanged ) + { + itemsChanged->Empty(); + } + // just add the items to the selection for ( size_t item = itemFrom; item <= itemTo; item++ ) { - SelectItem(item, select); + if ( SelectItem(item, select) && itemsChanged ) + { + itemsChanged->Add(item); + + if ( itemsChanged->GetCount() > MANY_ITEMS ) + { + // stop counting them, we'll just eat gobs of memory + // for nothing at all - faster to refresh everything in + // this case + itemsChanged = NULL; + } + } } } + + // we set it to NULL if there are many items changing state + return itemsChanged != NULL; } void wxSelectionStore::OnItemDelete(size_t item) @@ -1120,12 +1166,10 @@ void wxListItemData::GetItem( wxListItem &info ) const // wxListHeaderData //----------------------------------------------------------------------------- -IMPLEMENT_DYNAMIC_CLASS(wxListHeaderData,wxObject); - -wxListHeaderData::wxListHeaderData() +void wxListHeaderData::Init() { m_mask = 0; - m_image = 0; + m_image = -1; m_format = 0; m_width = 0; m_xpos = 0; @@ -1133,22 +1177,33 @@ wxListHeaderData::wxListHeaderData() m_height = 0; } +wxListHeaderData::wxListHeaderData() +{ + Init(); +} + wxListHeaderData::wxListHeaderData( const wxListItem &item ) { + Init(); + SetItem( item ); - m_xpos = 0; - m_ypos = 0; - m_height = 0; } void wxListHeaderData::SetItem( const wxListItem &item ) { m_mask = item.m_mask; - m_text = item.m_text; - m_image = item.m_image; - m_format = item.m_format; - SetWidth(item.m_width); + if ( m_mask & wxLIST_MASK_TEXT ) + m_text = item.m_text; + + if ( m_mask & wxLIST_MASK_IMAGE ) + m_image = item.m_image; + + if ( m_mask & wxLIST_MASK_FORMAT ) + m_format = item.m_format; + + if ( m_mask & wxLIST_MASK_WIDTH ) + SetWidth(item.m_width); } void wxListHeaderData::SetPosition( int x, int y ) @@ -1167,7 +1222,7 @@ void wxListHeaderData::SetWidth( int w ) m_width = w; if (m_width < 0) m_width = WIDTH_COL_DEFAULT; - if (m_width < WIDTH_COL_MIN) + else if (m_width < WIDTH_COL_MIN) m_width = WIDTH_COL_MIN; } @@ -1178,7 +1233,7 @@ void wxListHeaderData::SetFormat( int format ) bool wxListHeaderData::HasImage() const { - return (m_image != 0); + return m_image != -1; } bool wxListHeaderData::IsHit( int x, int y ) const @@ -1186,7 +1241,7 @@ bool wxListHeaderData::IsHit( int x, int y ) const return ((x >= m_xpos) && (x <= m_xpos+m_width) && (y >= m_ypos) && (y <= m_ypos+m_height)); } -void wxListHeaderData::GetItem( wxListItem &item ) +void wxListHeaderData::GetItem( wxListItem& item ) { item.m_mask = m_mask; item.m_text = m_text; @@ -1510,32 +1565,68 @@ void wxListLineData::SetAttr(wxListItemAttr *attr) item->SetAttr(attr); } -void wxListLineData::SetAttributes(wxDC *dc, +bool wxListLineData::SetAttributes(wxDC *dc, const wxListItemAttr *attr, - const wxColour& colText, - const wxFont& font, - bool highlight) + bool highlighted) { - // don't use foregroud colour for drawing highlighted items - this might + wxWindow *listctrl = m_owner->GetParent(); + + // fg colour + + // don't use foreground colour for drawing highlighted items - this might // make them completely invisible (and there is no way to do bit // arithmetics on wxColour, unfortunately) - if ( !highlight && attr && attr->HasTextColour() ) + wxColour colText; + if ( highlighted ) { - dc->SetTextForeground(attr->GetTextColour()); + colText = wxSystemSettings::GetSystemColour(wxSYS_COLOUR_HIGHLIGHTTEXT); } else { - dc->SetTextForeground(colText); + if ( attr && attr->HasTextColour() ) + { + colText = attr->GetTextColour(); + } + else + { + colText = listctrl->GetForegroundColour(); + } } + dc->SetTextForeground(colText); + + // font + wxFont font; if ( attr && attr->HasFont() ) { - dc->SetFont(attr->GetFont()); + font = attr->GetFont(); } else { - dc->SetFont(font); + font = listctrl->GetFont(); } + + dc->SetFont(font); + + // bg colour + bool hasBgCol = attr && attr->HasBackgroundColour(); + if ( highlighted || hasBgCol ) + { + if ( highlighted ) + { + dc->SetBrush( *m_owner->GetHighlightBrush() ); + } + else + { + dc->SetBrush(wxBrush(attr->GetBackgroundColour(), wxSOLID)); + } + + dc->SetPen( *wxTRANSPARENT_PEN ); + + return TRUE; + } + + return FALSE; } void wxListLineData::Draw( wxDC *dc ) @@ -1543,6 +1634,15 @@ void wxListLineData::Draw( wxDC *dc ) wxListItemDataList::Node *node = m_items.GetFirst(); wxCHECK_RET( node, _T("no subitems at all??") ); + bool highlighted = IsHighlighted(); + + wxListItemAttr *attr = GetAttr(); + + if ( SetAttributes(dc, attr, highlighted) ) + { + dc->DrawRectangle( m_gi->m_rectHighlight ); + } + wxListItemData *item = node->GetData(); if (item->HasImage()) { @@ -1554,6 +1654,8 @@ void wxListLineData::Draw( wxDC *dc ) if (item->HasText()) { wxRect rectLabel = m_gi->m_rectLabel; + + wxDCClipper clipper(*dc, rectLabel); dc->DrawText( item->GetText(), rectLabel.x, rectLabel.y ); } } @@ -1563,47 +1665,12 @@ void wxListLineData::DrawInReportMode( wxDC *dc, const wxRect& rectHL, bool highlighted ) { - // use our own flag if we maintain it - if ( !IsVirtual() ) - highlighted = m_highlighted; - - // default foreground colour - wxWindow *listctrl = m_owner->GetParent(); - wxColour colText; - if ( highlighted ) - { - colText = wxSystemSettings::GetSystemColour( wxSYS_COLOUR_HIGHLIGHTTEXT ); - } - else - { - colText = listctrl->GetForegroundColour(); - } - - // default font - wxFont font = listctrl->GetFont(); - // TODO: later we should support setting different attributes for // different columns - to do it, just add "col" argument to - // GetAttr() and move this code into the loop below + // GetAttr() and move these lines into the loop below wxListItemAttr *attr = GetAttr(); - SetAttributes(dc, attr, colText, font, highlighted); - - bool hasBgCol = attr && attr->HasBackgroundColour(); - if ( highlighted || hasBgCol ) + if ( SetAttributes(dc, attr, highlighted) ) { - if ( highlighted ) - { - dc->SetBrush( *m_owner->m_highlightBrush ); - } - else - { - if ( hasBgCol ) - dc->SetBrush(wxBrush(attr->GetBackgroundColour(), wxSOLID)); - else - dc->SetBrush( * wxWHITE_BRUSH ); - } - - dc->SetPen( * wxTRANSPARENT_PEN ); dc->DrawRectangle( rectHL ); } @@ -1618,27 +1685,29 @@ void wxListLineData::DrawInReportMode( wxDC *dc, { wxListItemData *item = node->GetData(); + int width = m_owner->GetColumnWidth(col++); int xOld = x; + x += width; if ( item->HasImage() ) { int ix, iy; - m_owner->DrawImage( item->GetImage(), dc, x, y ); + m_owner->DrawImage( item->GetImage(), dc, xOld, y ); m_owner->GetImageSize( item->GetImage(), ix, iy ); - x += ix + 5; // FIXME: what is "5"? - } - int width = m_owner->GetColumnWidth(col++); + ix += IMAGE_MARGIN_IN_REPORT_MODE; + + xOld += ix; + width -= ix; + } - wxDCClipper clipper(*dc, x, y, width, rect.height); + wxDCClipper clipper(*dc, xOld, y, width, rect.height); if ( item->HasText() ) { - dc->DrawText( item->GetText(), x, y ); + dc->DrawText( item->GetText(), xOld, y ); } - x = xOld + width; - node = node->GetNext(); } } @@ -1672,30 +1741,39 @@ BEGIN_EVENT_TABLE(wxListHeaderWindow,wxWindow) EVT_SET_FOCUS (wxListHeaderWindow::OnSetFocus) END_EVENT_TABLE() -wxListHeaderWindow::wxListHeaderWindow( void ) +void wxListHeaderWindow::Init() { - m_owner = (wxListMainWindow *) NULL; m_currentCursor = (wxCursor *) NULL; - m_resizeCursor = (wxCursor *) NULL; m_isDragging = FALSE; + m_dirty = FALSE; +} + +wxListHeaderWindow::wxListHeaderWindow() +{ + Init(); + + m_owner = (wxListMainWindow *) NULL; + m_resizeCursor = (wxCursor *) 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 ) +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 ) { + Init(); + m_owner = owner; -// m_currentCursor = wxSTANDARD_CURSOR; - m_currentCursor = (wxCursor *) NULL; m_resizeCursor = new wxCursor( wxCURSOR_SIZEWE ); - m_isDragging = FALSE; - m_dirty = FALSE; SetBackgroundColour( wxSystemSettings::GetSystemColour( wxSYS_COLOUR_BTNFACE ) ); } -wxListHeaderWindow::~wxListHeaderWindow( void ) +wxListHeaderWindow::~wxListHeaderWindow() { delete m_resizeCursor; } @@ -1801,25 +1879,53 @@ void wxListHeaderWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) int numColumns = m_owner->GetColumnCount(); wxListItem item; - for (int i = 0; i < numColumns; i++) + for ( int i = 0; i < numColumns && x < w; i++ ) { m_owner->GetColumn( i, item ); int wCol = item.m_width; - int cw = wCol - 2; // the width of the rect to draw - int xEnd = x + wCol; + // the width of the rect to draw: make it smaller to fit entirely + // inside the column rect + int cw = wCol - 2; dc.SetPen( *wxWHITE_PEN ); DoDrawRect( &dc, x, HEADER_OFFSET_Y, cw, h-2 ); - dc.SetClippingRegion( x, HEADER_OFFSET_Y, cw-5, h-4 ); - dc.DrawText( item.GetText(), x + EXTRA_WIDTH, HEADER_OFFSET_Y + EXTRA_HEIGHT ); - dc.DestroyClippingRegion(); - x += wCol; - if (xEnd > w+5) - break; + // if we have an image, draw it on the right of the label + int image = item.m_image; + if ( image != -1 ) + { + wxImageList *imageList = m_owner->m_small_image_list; + if ( imageList ) + { + int ix, iy; + imageList->GetSize(image, ix, iy); + + imageList->Draw + ( + image, + dc, + x + cw - ix - 1, + HEADER_OFFSET_Y + (h - 4 - iy)/2, + wxIMAGELIST_DRAW_TRANSPARENT + ); + + cw -= ix + 2; + } + //else: ignore the column image + } + + // draw the text clipping it so that it doesn't overwrite the column + // boundary + wxDCClipper clipper(dc, x, HEADER_OFFSET_Y, cw, h - 4 ); + + dc.DrawText( item.GetText(), + x + EXTRA_WIDTH, HEADER_OFFSET_Y + EXTRA_HEIGHT ); + + x += wCol; } + dc.EndDrawing(); } @@ -1919,20 +2025,31 @@ void wxListHeaderWindow::OnMouse( wxMouseEvent &event ) m_minX = xpos; } - if (event.LeftDown()) + if (event.LeftDown() || event.RightUp()) { - if (hit_border) + if (hit_border && event.LeftDown()) { m_isDragging = TRUE; m_currentX = x; DrawCurrent(); CaptureMouse(); } - else + else // click on a column { wxWindow *parent = GetParent(); - wxListEvent le( wxEVT_COMMAND_LIST_COL_CLICK, parent->GetId() ); + wxListEvent le( event.LeftDown() + ? wxEVT_COMMAND_LIST_COL_CLICK + : wxEVT_COMMAND_LIST_COL_RIGHT_CLICK, + parent->GetId() ); le.SetEventObject( parent ); + le.m_pointDrag = event.GetPosition(); + + // the position should be relative to the parent window, not + // this one for compatibility with MSW and common sense: the + // user code doesn't know anything at all about this header + // window, so why should it get positions relative to it? + le.m_pointDrag.y -= GetSize().y; + le.m_col = m_column; parent->GetEventHandler()->ProcessEvent( le ); } @@ -2130,7 +2247,8 @@ wxListMainWindow::wxListMainWindow() { Init(); - m_highlightBrush = (wxBrush *) NULL; + m_highlightBrush = + m_highlightUnfocusedBrush = (wxBrush *) NULL; m_xScroll = m_yScroll = 0; @@ -2147,7 +2265,24 @@ wxListMainWindow::wxListMainWindow( wxWindow *parent, { Init(); - m_highlightBrush = new wxBrush( wxSystemSettings::GetSystemColour(wxSYS_COLOUR_HIGHLIGHT), wxSOLID ); + m_highlightBrush = new wxBrush + ( + wxSystemSettings::GetSystemColour + ( + wxSYS_COLOUR_HIGHLIGHT + ), + wxSOLID + ); + + m_highlightUnfocusedBrush = new wxBrush + ( + wxSystemSettings::GetSystemColour + ( + wxSYS_COLOUR_BTNSHADOW + ), + wxSOLID + ); + wxSize sz = size; sz.y = 25; @@ -2159,9 +2294,10 @@ wxListMainWindow::wxListMainWindow( wxWindow *parent, wxListMainWindow::~wxListMainWindow() { - DeleteEverything(); + DoDeleteAllItems(); delete m_highlightBrush; + delete m_highlightUnfocusedBrush; delete m_renameTimer; } @@ -2288,6 +2424,8 @@ wxRect wxListMainWindow::GetLineHighlightRect(size_t line) const long wxListMainWindow::HitTestLine(size_t line, int x, int y) const { + wxASSERT_MSG( line < GetItemCount(), _T("invalid line in HitTestLine") ); + wxListLineData *ld = GetLine(line); if ( ld->HasImage() && GetLineIconRect(line).Inside(x, y) ) @@ -2324,25 +2462,37 @@ bool wxListMainWindow::IsHighlighted(size_t line) const } } -void wxListMainWindow::HighlightLines( size_t lineFrom, size_t lineTo, bool highlight ) +void wxListMainWindow::HighlightLines( size_t lineFrom, + size_t lineTo, + bool highlight ) { if ( IsVirtual() ) { - m_selStore.SelectRange(lineFrom, lineTo, highlight); - RefreshLines(lineFrom, lineTo); + wxArrayInt linesChanged; + if ( !m_selStore.SelectRange(lineFrom, lineTo, highlight, + &linesChanged) ) + { + // meny items changed state, refresh everything + RefreshLines(lineFrom, lineTo); + } + else // only a few items changed state, refresh only them + { + size_t count = linesChanged.GetCount(); + for ( size_t n = 0; n < count; n++ ) + { + RefreshLine(linesChanged[n]); + } + } } - else + else // iterate over all items in non report view { - // do it the dumb way - bool needsRefresh = FALSE; for ( size_t line = lineFrom; line <= lineTo; line++ ) { if ( HighlightLine(line, highlight) ) - needsRefresh = TRUE; + { + RefreshLine(line); + } } - - if ( needsRefresh ) - RefreshLines(lineFrom, lineTo); } } @@ -2357,7 +2507,7 @@ bool wxListMainWindow::HighlightLine( size_t line, bool highlight ) else // !virtual { wxListLineData *ld = GetLine(line); - wxCHECK_MSG( ld, FALSE, _T("invalid index in IsHighlighted") ); + wxCHECK_MSG( ld, FALSE, _T("invalid index in HighlightLine") ); changed = ld->Highlight(highlight); } @@ -2373,6 +2523,15 @@ bool wxListMainWindow::HighlightLine( size_t line, bool highlight ) void wxListMainWindow::RefreshLine( size_t line ) { + if ( HasFlag(wxLC_REPORT) ) + { + size_t visibleFrom, visibleTo; + GetVisibleLinesRange(&visibleFrom, &visibleTo); + + if ( line < visibleFrom || line > visibleTo ) + return; + } + wxRect rect = GetLineRect(line); CalcScrolledPosition( rect.x, rect.y, &rect.x, &rect.y ); @@ -2400,7 +2559,7 @@ void wxListMainWindow::RefreshLines( size_t lineFrom, size_t lineTo ) rect.x = 0; rect.y = GetLineY(lineFrom); rect.width = GetClientSize().x; - rect.height = GetLineY(lineTo) - rect.y; + rect.height = GetLineY(lineTo) - rect.y + GetLineHeight(); CalcScrolledPosition( rect.x, rect.y, &rect.x, &rect.y ); RefreshRect( rect ); @@ -2444,6 +2603,65 @@ void wxListMainWindow::RefreshAfter( size_t lineFrom ) } } +void wxListMainWindow::RefreshSelected() +{ + if ( IsEmpty() ) + return; + + size_t from, to; + if ( InReportView() ) + { + GetVisibleLinesRange(&from, &to); + } + else // !virtual + { + from = 0; + to = GetItemCount() - 1; + } + + // VZ: this code would work fine if wxGTK wxWindow::Refresh() were + // reasonable, i.e. if it only generated one expose event for + // several calls to it - as it is, each Refresh() results in a + // repaint which provokes flicker too horrible to be seen + // + // when/if wxGTK is fixed, this code should be restored as normally it + // should generate _less_ flicker than the version below +#ifndef __WXGTK__ + if ( HasCurrent() && m_current >= from && m_current <= to ) + { + RefreshLine(m_current); + } + + for ( size_t line = from; line <= to; line++ ) + { + // NB: the test works as expected even if m_current == -1 + if ( line != m_current && IsHighlighted(line) ) + { + RefreshLine(line); + } + } +#else // __WXGTK__ + size_t selMin = (size_t)-1, + selMax = 0; + + for ( size_t line = from; line <= to; line++ ) + { + if ( IsHighlighted(line) || (line == m_current) ) + { + if ( line < selMin ) + selMin = line; + if ( line > selMax ) + selMax = line; + } + } + + if ( selMin != (size_t)-1 ) + { + RefreshLines(selMin, selMax); + } +#endif // !__WXGTK__/__WXGTK__ +} + void wxListMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) { // Note: a wxPaintDC must be constructed even if no drawing is @@ -2482,6 +2700,17 @@ void wxListMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) wxCoord xOrig, yOrig; CalcUnscrolledPosition(0, 0, &xOrig, &yOrig); + // tell the caller cache to cache the data + if ( IsVirtual() ) + { + wxListEvent evCache(wxEVT_COMMAND_LIST_CACHE_HINT, + GetParent()->GetId()); + evCache.SetEventObject( GetParent() ); + evCache.m_oldItemIndex = visibleFrom; + evCache.m_itemIndex = visibleTo; + GetParent()->GetEventHandler()->ProcessEvent( evCache ); + } + for ( size_t line = visibleFrom; line <= visibleTo; line++ ) { rectLine = GetLineRect(line); @@ -2553,15 +2782,21 @@ void wxListMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) } } - if ( HasCurrent() && m_hasFocus ) + if ( HasCurrent() ) { + // don't draw rect outline under Max if we already have the background + // color but under other platforms only draw it if we do: it is a bit + // silly to draw "focus rect" if we don't have focus! #ifdef __WXMAC__ - // no rect outline, we already have the background color -#else - dc.SetPen( *wxBLACK_PEN ); - dc.SetBrush( *wxTRANSPARENT_BRUSH ); - dc.DrawRectangle( GetLineHighlightRect(m_current) ); -#endif + if ( !m_hasFocus ) +#else // !__WXMAC__ + if ( m_hasFocus ) +#endif // __WXMAC__/!__WXMAC__ + { + dc.SetPen( *wxBLACK_PEN ); + dc.SetBrush( *wxTRANSPARENT_BRUSH ); + dc.DrawRectangle( GetLineHighlightRect(m_current) ); + } } dc.EndDrawing(); @@ -2598,7 +2833,16 @@ void wxListMainWindow::SendNotify( size_t line, if ( point != wxDefaultPosition ) le.m_pointDrag = point; - GetLine(line)->GetItem( 0, le.m_item ); + // don't try to get the line info for virtual list controls: the main + // program has it anyhow and if we did it would result in accessing all + // the lines, even those which are not visible now and this is precisely + // what we're trying to avoid + if ( !IsVirtual() && (command != wxEVT_COMMAND_LIST_DELETE_ITEM) ) + { + GetLine(line)->GetItem( 0, le.m_item ); + } + //else: there may be no more such item + GetParent()->GetEventHandler()->ProcessEvent( le ); } @@ -2734,7 +2978,12 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) if (event.Dragging()) { if (m_dragCount == 0) - m_dragStart = wxPoint(x,y); + { + // 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++; @@ -2857,12 +3106,12 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) } } -void wxListMainWindow::MoveToFocus() +void wxListMainWindow::MoveToItem(size_t item) { - if ( !HasCurrent() ) + if ( item == (size_t)-1 ) return; - wxRect rect = GetLineRect(m_current); + wxRect rect = GetLineRect(item); int client_w, client_h; GetClientSize( &client_w, &client_h ); @@ -3099,11 +3348,16 @@ void wxListMainWindow::OnChar( wxKeyEvent &event ) le.m_itemIndex = m_current; GetLine(m_current)->GetItem( 0, le.m_item ); GetParent()->GetEventHandler()->ProcessEvent( le ); + + if ( IsHighlighted(m_current) ) + { + // don't unselect the item in single selection mode + break; + } + //else: select it in ReverseHighlight() below if unselected } - else - { - ReverseHighlight(m_current); - } + + ReverseHighlight(m_current); break; case WXK_RETURN: @@ -3133,12 +3387,18 @@ extern wxWindow *g_focusWindow; void wxListMainWindow::OnSetFocus( wxFocusEvent &WXUNUSED(event) ) { - m_hasFocus = TRUE; + // wxGTK sends us EVT_SET_FOCUS events even if we had never got + // EVT_KILL_FOCUS before which means that we finish by redrawing the items + // which are already drawn correctly resulting in horrible flicker - avoid + // it + if ( !m_hasFocus ) + { + m_hasFocus = TRUE; - if ( HasCurrent() ) - RefreshLine( m_current ); + RefreshSelected(); + } - if (!GetParent()) + if ( !GetParent() ) return; #ifdef __WXGTK__ @@ -3154,8 +3414,7 @@ void wxListMainWindow::OnKillFocus( wxFocusEvent &WXUNUSED(event) ) { m_hasFocus = FALSE; - if ( HasCurrent() ) - RefreshLine( m_current ); + RefreshSelected(); } void wxListMainWindow::DrawImage( int index, wxDC *dc, int x, int y ) @@ -3292,6 +3551,9 @@ void wxListMainWindow::SetColumnWidth( int col, int width ) _T("SetColumnWidth() can only be called in report mode.") ); m_dirty = TRUE; + wxListHeaderWindow *headerWin = GetListCtrl()->m_headerWin; + if ( headerWin ) + headerWin->m_dirty = TRUE; wxListHeaderDataList::Node *node = m_columns.Item( col ); wxCHECK_RET( node, _T("no column?") ); @@ -3424,8 +3686,9 @@ void wxListMainWindow::SetItemState( long litem, long state, long stateMask ) _T("invalid list ctrl item index in SetItem") ); size_t oldCurrent = m_current; - size_t item = (size_t)litem; // sdafe because of the check above + size_t item = (size_t)litem; // safe because of the check above + // do we need to change the focus? if ( stateMask & wxLIST_STATE_FOCUSED ) { if ( state & wxLIST_STATE_FOCUSED ) @@ -3437,9 +3700,13 @@ void wxListMainWindow::SetItemState( long litem, long state, long stateMask ) m_current = item; OnFocusLine( m_current ); - if ( IsSingleSel() && (oldCurrent != (size_t)-1) ) + if ( oldCurrent != (size_t)-1 ) { - HighlightLine(oldCurrent, FALSE); + if ( IsSingleSel() ) + { + HighlightLine(oldCurrent, FALSE); + } + RefreshLine(oldCurrent); } @@ -3453,10 +3720,13 @@ void wxListMainWindow::SetItemState( long litem, long state, long stateMask ) { OnUnfocusLine( m_current ); m_current = (size_t)-1; + + RefreshLine( oldCurrent ); } } } + // do we need to change the selection state? if ( stateMask & wxLIST_STATE_SELECTED ) { bool on = (state & wxLIST_STATE_SELECTED) != 0; @@ -3542,7 +3812,8 @@ void wxListMainWindow::SetItemCount(long count) ResetVisibleLinesRange(); - Refresh(); + // scrollbars must be reset + m_dirty = TRUE; } int wxListMainWindow::GetSelectedItemCount() @@ -3599,11 +3870,8 @@ bool wxListMainWindow::GetItemPosition(long item, wxPoint& pos) // geometry calculation // ---------------------------------------------------------------------------- -void wxListMainWindow::RecalculatePositions() +void wxListMainWindow::RecalculatePositions(bool noRefresh) { - if ( IsEmpty() ) - return; - wxClientDC dc( this ); dc.SetFont( GetFont() ); @@ -3704,10 +3972,13 @@ void wxListMainWindow::RecalculatePositions() SetScrollbars( m_xScroll, m_yScroll, (entireWidth+SCROLL_UNIT_X) / m_xScroll, 0, scroll_pos, 0, TRUE ); } - // FIXME: why should we call it from here? - UpdateCurrent(); + if ( !noRefresh ) + { + // FIXME: why should we call it from here? + UpdateCurrent(); - RefreshAll(); + RefreshAll(); + } } void wxListMainWindow::RefreshAll() @@ -3716,7 +3987,7 @@ void wxListMainWindow::RefreshAll() Refresh(); wxListHeaderWindow *headerWin = GetListCtrl()->m_headerWin; - if ( headerWin ) + if ( headerWin && headerWin->m_dirty ) { headerWin->m_dirty = FALSE; headerWin->Refresh(); @@ -3788,20 +4059,18 @@ void wxListMainWindow::DeleteItem( long lindex ) size_t index = (size_t)lindex; - m_dirty = TRUE; - - // select the next item when the selected one is deleted - if ( m_current == index ) + // we don't need to adjust the index for the previous items + if ( HasCurrent() && m_current >= index ) { - // the last valid index after deleting the item will be count-2 - if ( m_current == count - 1 ) + // if the current item is being deleted, we want the next one to + // become selected - unless there is no next one - so don't adjust + // m_current in this case + if ( m_current != index || m_current == count - 1 ) { m_current--; } } - SendNotify( index, wxEVT_COMMAND_LIST_DELETE_ITEM ); - if ( InReportView() ) { ResetVisibleLinesRange(); @@ -3818,7 +4087,11 @@ void wxListMainWindow::DeleteItem( long lindex ) m_lines.RemoveAt( index ); } + // we need to refresh the (vert) scrollbar as the number of items changed m_dirty = TRUE; + + SendNotify( index, wxEVT_COMMAND_LIST_DELETE_ITEM ); + RefreshAfter(index); } @@ -3832,7 +4105,7 @@ void wxListMainWindow::DeleteColumn( int col ) m_columns.DeleteNode( node ); } -void wxListMainWindow::DeleteAllItems() +void wxListMainWindow::DoDeleteAllItems() { if ( IsEmpty() ) { @@ -3864,11 +4137,13 @@ void wxListMainWindow::DeleteAllItems() } m_lines.Clear(); +} - // NB: don't just set m_dirty to TRUE here as RecalculatePositions() - // doesn't do anything if the control is empty and so we won't be - // refreshed - Refresh(); +void wxListMainWindow::DeleteAllItems() +{ + DoDeleteAllItems(); + + RecalculatePositions(); } void wxListMainWindow::DeleteEverything() @@ -3888,14 +4163,13 @@ void wxListMainWindow::EnsureVisible( long index ) _T("invalid index in EnsureVisible") ); // 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) - wxSafeYield(); + // been added and its position is not known yet + if ( m_dirty ) + { + RecalculatePositions(TRUE /* no refresh */); + } - size_t oldCurrent = m_current; - m_current = (size_t)index; - MoveToFocus(); - m_current = oldCurrent; + MoveToItem((size_t)index); } long wxListMainWindow::FindItem(long start, const wxString& str, bool WXUNUSED(partial) ) @@ -3939,18 +4213,22 @@ long wxListMainWindow::HitTest( int x, int y, int &flags ) { CalcUnscrolledPosition( x, y, &x, &y ); + size_t count = GetItemCount(); + if ( HasFlag(wxLC_REPORT) ) { size_t current = y / GetLineHeight(); - flags = HitTestLine(current, x, y); - if ( flags ) - return current; + if ( current < count ) + { + flags = HitTestLine(current, x, y); + if ( flags ) + return current; + } } else // !report { // TODO: optimize it too! this is less simple than for report view but // enumerating all items is still not a way to do it!! - size_t count = GetItemCount(); for ( size_t current = 0; current < count; current++ ) { flags = HitTestLine(current, x, y); @@ -4135,17 +4413,9 @@ IMPLEMENT_DYNAMIC_CLASS(wxListItem, wxObject) wxListItem::wxListItem() { - m_mask = 0; - m_itemId = 0; - m_col = 0; - m_state = 0; - m_stateMask = 0; - m_image = 0; - m_data = 0; - m_format = wxLIST_FORMAT_CENTRE; - m_width = 0; - m_attr = NULL; + + Clear(); } void wxListItem::Clear() @@ -4155,11 +4425,11 @@ void wxListItem::Clear() m_col = 0; m_state = 0; m_stateMask = 0; - m_image = 0; + m_image = -1; m_data = 0; m_format = wxLIST_FORMAT_CENTRE; m_width = 0; - m_text = _T(""); + m_text.clear(); ClearAttributes(); } @@ -4225,6 +4495,7 @@ void wxListEvent::CopyObject(wxObject& object_dest) const // ------------------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS(wxListCtrl, wxControl) +IMPLEMENT_DYNAMIC_CLASS(wxListView, wxListCtrl) BEGIN_EVENT_TABLE(wxListCtrl,wxControl) EVT_SIZE(wxListCtrl::OnSize) @@ -4349,36 +4620,35 @@ void wxListCtrl::SetWindowStyleFlag( long flag ) { m_mainWin->DeleteEverything(); - int width = 0; - int height = 0; - GetClientSize( &width, &height ); + // has the header visibility changed? + bool hasHeader = HasFlag(wxLC_REPORT) && !HasFlag(wxLC_NO_HEADER), + willHaveHeader = (flag & wxLC_REPORT) && !(flag & wxLC_NO_HEADER); - if (flag & wxLC_REPORT) + if ( hasHeader != willHaveHeader ) { - if (!HasFlag(wxLC_REPORT)) + // toggle it + if ( hasHeader ) + { + if ( m_headerWin ) + { + // don't delete, just hide, as we can reuse it later + m_headerWin->Show(FALSE); + } + //else: nothing to do + } + else // must show header { if (!m_headerWin) { CreateHeaderWindow(); - - if (HasFlag(wxLC_NO_HEADER)) - m_headerWin->Show( FALSE ); } - else + else // already have it, just show { - if (flag & wxLC_NO_HEADER) - m_headerWin->Show( FALSE ); - else - m_headerWin->Show( TRUE ); + m_headerWin->Show( TRUE ); } } - } - else // !report - { - if ( m_mainWin->HasHeader() ) - { - m_headerWin->Show( FALSE ); - } + + ResizeReportView(willHaveHeader); } } @@ -4769,10 +5039,17 @@ void wxListCtrl::OnSize(wxSizeEvent& event) if ( !m_mainWin ) return; + ResizeReportView(m_mainWin->HasHeader()); + + m_mainWin->RecalculatePositions(); +} + +void wxListCtrl::ResizeReportView(bool showHeader) +{ int cw, ch; GetClientSize( &cw, &ch ); - if ( m_mainWin->HasHeader() ) + if ( showHeader ) { m_headerWin->SetSize( 0, 0, cw, HEADER_HEIGHT ); m_mainWin->SetSize( 0, HEADER_HEIGHT + 1, cw, ch - HEADER_HEIGHT - 1 ); @@ -4781,8 +5058,6 @@ void wxListCtrl::OnSize(wxSizeEvent& event) { m_mainWin->SetSize( 0, 0, cw, ch ); } - - m_mainWin->RecalculatePositions(); } void wxListCtrl::OnIdle( wxIdleEvent & event )