X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/cf1dfa6b8f0142d1e4879580501f22cf8373435d..adac097759c089b3d47f48c7756ace4f0b8df879:/src/generic/listctrl.cpp diff --git a/src/generic/listctrl.cpp b/src/generic/listctrl.cpp index 3bec06f92d..3ca9aa51ac 100644 --- a/src/generic/listctrl.cpp +++ b/src/generic/listctrl.cpp @@ -8,6 +8,13 @@ // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// +/* + TODO + + 1. we need to implement searching/sorting for virtual controls somehow + 2. when changing selection the lines are refreshed twice + */ + // ============================================================================ // declarations // ============================================================================ @@ -21,10 +28,6 @@ #pragma implementation "listctrlbase.h" #endif -#if 0 - #include "listctrl.old.cpp" -#else - // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" @@ -37,7 +40,7 @@ #include "wx/dcscreen.h" #include "wx/app.h" #include "wx/listctrl.h" -#include "wx/generic/imaglist.h" +#include "wx/imaglist.h" #include "wx/dynarray.h" #ifdef __WXGTK__ @@ -65,6 +68,7 @@ DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_COL_CLICK) 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) +DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_CACHE_HINT) // ---------------------------------------------------------------------------- // constants @@ -78,7 +82,7 @@ static const int SCROLL_UNIT_X = 15; static const int SCROLL_UNIT_Y = 15; // the spacing between the lines (in report mode) -static const int LINE_SPACING = 1; +static const int LINE_SPACING = 0; // extra margins around the text label static const int EXTRA_WIDTH = 3; @@ -95,10 +99,90 @@ 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 // ============================================================================ +// ---------------------------------------------------------------------------- +// wxSelectionStore +// ---------------------------------------------------------------------------- + +int CMPFUNC_CONV wxSizeTCmpFn(size_t n1, size_t n2) { return n1 - n2; } + +WX_DEFINE_SORTED_EXPORTED_ARRAY(size_t, wxIndexArray); + +// this class is used to store the selected items in the virtual list control +// (but it is not tied to list control and so can be used with other controls +// such as wxListBox in wxUniv) +// +// the idea is to make it really smart later (i.e. store the selections as an +// array of ranes + individual items) but, as I don't have time to do it now +// (this would require writing code to merge/break ranges and much more) keep +// it simple but define a clean interface to it which allows it to be made +// smarter later +class WXDLLEXPORT wxSelectionStore +{ +public: + wxSelectionStore() : m_itemsSel(wxSizeTCmpFn) { Init(); } + + // set the total number of items we handle + void SetItemCount(size_t count) { m_count = count; } + + // special case of SetItemCount(0) + void Clear() { m_itemsSel.Clear(); m_count = 0; } + + // must be called when a new item is inserted/added + void OnItemAdd(size_t item) { wxFAIL_MSG( _T("TODO") ); } + + // must be called when an item is deleted + void OnItemDelete(size_t item); + + // select one item, use SelectRange() insted if possible! + // + // returns true if the items selection really changed + bool SelectItem(size_t item, bool select = TRUE); + + // select the range of items + // + // 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; + + // return the total number of selected items + size_t GetSelectedCount() const + { + return m_defaultState ? m_count - m_itemsSel.GetCount() + : m_itemsSel.GetCount(); + } + +private: + // (re)init + void Init() { m_defaultState = FALSE; } + + // the total number of items we handle + size_t m_count; + + // the default state: normally, FALSE (i.e. off) but maybe set to TRUE if + // there are more selected items than non selected ones - this allows to + // handle selection of all items efficiently + bool m_defaultState; + + // the array of items whose selection state is different from default + wxIndexArray m_itemsSel; + + DECLARE_NO_COPY_CLASS(wxSelectionStore) +}; + //----------------------------------------------------------------------------- // wxListItemData (internal) //----------------------------------------------------------------------------- @@ -107,7 +191,7 @@ class WXDLLEXPORT wxListItemData { public: wxListItemData(wxListMainWindow *owner); - ~wxListItemData() { delete m_attr; delete m_rect; } + ~wxListItemData(); void SetItem( const wxListItem &info ); void SetImage( int image ) { m_image = image; } @@ -142,7 +226,8 @@ public: void GetItem( wxListItem &info ) const; - wxListItemAttr *GetAttributes() const { return m_attr; } + void SetAttr(wxListItemAttr *attr) { m_attr = attr; } + wxListItemAttr *GetAttr() const { return m_attr; } public: // the item image or -1 @@ -237,17 +322,17 @@ public: wxRect m_rectIcon; // the part to be highlighted - wxRect m_rectHilight; + wxRect m_rectHighlight; } *m_gi; // is this item selected? [NB: not used in virtual mode] - bool m_hilighted; + bool m_highlighted; // back pointer to the list ctrl wxListMainWindow *m_owner; public: - wxListLineData( wxListMainWindow *owner, size_t line ); + wxListLineData(wxListMainWindow *owner); ~wxListLineData() { delete m_gi; } @@ -255,7 +340,7 @@ public: inline bool InReportView() const; // are we in virtual report mode? - inline bool IsVirtal() const; + inline bool IsVirtual() const; // these 2 methods shouldn't be called for report view controls, in that // case we determine our position/size ourselves @@ -266,7 +351,12 @@ public: // remember the position this line appears at void SetPosition( int x, int y, int window_width, int spacing ); - long IsHit( int x, int y ); + // wxListCtrl API + + void SetImage( int image ) { SetImage(0, image); } + int GetImage() const { return GetImage(0); } + bool HasImage() const { return GetImage() != -1; } + bool HasText() const { return !GetText(0).empty(); } void SetItem( int index, const wxListItem &info ); void GetItem( int index, wxListItem &info ); @@ -274,42 +364,29 @@ public: wxString GetText(int index) const; void SetText( int index, const wxString s ); - void SetImage( int index, int image ); - int GetImage( int index ) const; - - // get the bound rect of this line - wxRect GetRect() const; - - // get the bound rect of the label - wxRect GetLabelRect() const; - - // get the bound rect of the items icon (only may be called if we do have - // an icon!) - wxRect GetIconRect() const; - - // get the rect to be highlighted when the item has focus - wxRect GetHighlightRect() const; - - // get the size of the total line rect - wxSize GetSize() const { return GetRect().GetSize(); } + wxListItemAttr *GetAttr() const; + void SetAttr(wxListItemAttr *attr); // return true if the highlighting really changed - bool Hilight( bool on ); + bool Highlight( bool on ); - void ReverseHilight(); + void ReverseHighlight(); - // draw the line on the given DC - void Draw( wxDC *dc, int y = 0, int height = 0, bool highlighted = FALSE ); - - bool IsHilighted() const + bool IsHighlighted() const { - wxASSERT_MSG( !IsVirtal(), _T("unexpected call to IsHilighted") ); + wxASSERT_MSG( !IsVirtual(), _T("unexpected call to IsHighlighted") ); - return m_hilighted; + return m_highlighted; } - // only for wxListMainWindow::CacheLineData() - void SetLineIndex(size_t line) { m_lineIndex = line; } + // draw the line on the given DC in icon/list mode + void Draw( wxDC *dc ); + + // the same in report mode + void DrawInReportMode( wxDC *dc, + const wxRect& rect, + const wxRect& rectHL, + bool highlighted ); private: // set the line to contain num items (only can be > 1 in report mode) @@ -318,17 +395,18 @@ 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 hilight); + bool highlight); - // the index of this line (only used in report mode) - size_t m_lineIndex; + // these are only used by GetImage/SetImage above, we don't support images + // with subitems at the public API level yet + void SetImage( int index, int image ); + int GetImage( int index ) const; }; - WX_DECLARE_EXPORTED_OBJARRAY(wxListLineData, wxListLineDataArray); #include "wx/arrimpl.cpp" WX_DEFINE_OBJARRAY(wxListLineDataArray); @@ -453,34 +531,83 @@ public: // return true if this is a virtual list control bool IsVirtual() const { return HasFlag(wxLC_VIRTUAL); } + // return true if the control is in report mode + bool InReportView() const { return HasFlag(wxLC_REPORT); } + + // return true if we are in single selection mode, false if multi sel + bool IsSingleSel() const { return HasFlag(wxLC_SINGLE_SEL); } + // do we have a header window? bool HasHeader() const { return HasFlag(wxLC_REPORT) && !HasFlag(wxLC_NO_HEADER); } - void HilightAll( bool on ); + void HighlightAll( bool on ); // all these functions only do something if the line is currently visible // change the line "selected" state, return TRUE if it really changed - bool HilightLine( size_t line, bool hilight = TRUE); + bool HighlightLine( size_t line, bool highlight = TRUE); + + // as HighlightLine() but do it for the range of lines: this is incredibly + // more efficient for virtual list controls! + // + // NB: unlike HighlightLine() this one does refresh the lines on screen + void HighlightLines( size_t lineFrom, size_t lineTo, bool on = TRUE ); // toggle the line state and refresh it - void ReverseHilight( size_t line ) - { HilightLine(line, !IsHilighted(line)); RefreshLine(line); } + void ReverseHighlight( size_t line ) + { HighlightLine(line, !IsHighlighted(line)); RefreshLine(line); } + + // return true if the line is highlighted + bool IsHighlighted(size_t line) const; // refresh one or several lines at once void RefreshLine( size_t line ); void RefreshLines( size_t lineFrom, size_t lineTo ); - // return true if the line is highlighted - bool IsHilighted(size_t line) const; + // 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) + void RefreshAfter( size_t lineFrom ); + + // the methods which are forwarded to wxListLineData itself in list/icon + // modes but are here because the lines don't store their positions in the + // report mode + + // get the bound rect for the entire line + wxRect GetLineRect(size_t line) const; + + // get the bound rect of the label + wxRect GetLineLabelRect(size_t line) const; + + // get the bound rect of the items icon (only may be called if we do have + // an icon!) + wxRect GetLineIconRect(size_t line) const; + + // get the rect to be highlighted when the item has focus + wxRect GetLineHighlightRect(size_t line) const; + + // get the size of the total line rect + wxSize GetLineSize(size_t line) const + { return GetLineRect(line).GetSize(); } + + // 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 ); @@ -489,14 +616,13 @@ public: void OnKeyDown( wxKeyEvent &event ); void OnSetFocus( wxFocusEvent &event ); void OnKillFocus( wxFocusEvent &event ); - void OnSize( wxSizeEvent &event ); void OnScroll(wxScrollWinEvent& event) ; void OnPaint( wxPaintEvent &event ); void DrawImage( int index, wxDC *dc, int x, int y ); - void GetImageSize( int index, int &width, int &height ); - int GetTextLength( const wxString &s ); + void GetImageSize( int index, int &width, int &height ) const; + int GetTextLength( const wxString &s ) const; void SetImageList( wxImageList *imageList, int which ); void SetItemSpacing( int spacing, bool isSmall = FALSE ); @@ -511,7 +637,7 @@ public: // returns the sum of the heights of all columns int GetHeaderWidth() const; - int GetCountPerPage() { return m_linesPerPage; } + int GetCountPerPage() const; void SetItem( wxListItem &item ); void GetItem( wxListItem &item ); @@ -522,7 +648,10 @@ public: int GetSelectedItemCount(); // set the scrollbars and update the positions of the items - void CalculatePositions(); + void RecalculatePositions(bool noRefresh = FALSE); + + // refresh the window and the header + void RefreshAll(); long GetNextItem( long item, int geometry, int state ); void DeleteItem( long index ); @@ -541,6 +670,7 @@ public: bool IsEmpty() const { return GetItemCount() == 0; } void SetItemCount(long count); + void ResetCurrent() { m_current = (size_t)-1; } bool HasCurrent() const { return m_current != (size_t)-1; } // send out a wxListEvent @@ -548,8 +678,16 @@ public: wxEventType command, wxPoint point = wxDefaultPosition ); - // called by wxListCtrl when its font changes - void OnFontChange() { m_lineHeight = 0; } + // override base class virtual to reset m_lineHeight when the font changes + virtual bool SetFont(const wxFont& font) + { + if ( !wxScrolledWindow::SetFont(font) ) + return FALSE; + + m_lineHeight = 0; + + return TRUE; + } // these are for wxListLineData usage only @@ -565,6 +703,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; @@ -589,8 +733,7 @@ public: // call bool m_dirty; - wxBrush *m_hilightBrush; - wxColour *m_hilightColour; + wxColour *m_highlightColour; int m_xScroll, m_yScroll; wxImageList *m_small_image_list; @@ -613,15 +756,11 @@ public: protected: // the total count of items in a virtual list control - long m_countVirt; - - // the first and last lines being shown on screen right now (inclusive) - size_t m_lineFrom, - m_lineTo; + size_t m_countVirt; - // the array containing the indices of all selected items, only used in - // virtual controls - wxArrayInt m_selections; + // the object maintaining the items selection state, only used in virtual + // controls + wxSelectionStore m_selStore; // common part of all ctors void Init(); @@ -644,21 +783,36 @@ protected: return &m_lines[n]; } - // get the first line: this one is special as we have it even in virtual - // list control (it is useful to cache it as we use it for measuring, hit - // testing &c) - wxListLineData *GetFirstLine() const; + // get a dummy line which can be used for geometry calculations and such: + // you must use GetLine() if you want to really draw the line + wxListLineData *GetDummyLine() const; // cache the line data of the n-th line in m_lines[0] void CacheLineData(size_t line); - // update m_lineFrom/To - void UpdateShownLinesRange(); + // get the range of visible lines + void GetVisibleLinesRange(size_t *from, size_t *to); + + // force us to recalculate the range of visible lines + void ResetVisibleLinesRange() { m_lineFrom = (size_t)-1; } + + // get the colour to be used for drawing the rules + wxColour GetRuleColour() const + { +#ifdef __WXMAC__ + return *wxWHITE; +#else + return wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DLIGHT); +#endif + } 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 @@ -671,6 +825,16 @@ private: // the total header width or 0 if not calculated yet wxCoord m_headerWidth; + // the first and last lines being shown on screen right now (inclusive), + // both may be -1 if they must be calculated so never access them directly: + // use GetVisibleLinesRange() above instead + 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() }; @@ -679,10 +843,197 @@ private: // implementation // ============================================================================ +// ---------------------------------------------------------------------------- +// wxSelectionStore +// ---------------------------------------------------------------------------- + +bool wxSelectionStore::IsSelected(size_t item) const +{ + bool isSel = m_itemsSel.Index(item) != wxNOT_FOUND; + + // if the default state is to be selected, being in m_itemsSel means that + // the item is not selected, so we have to inverse the logic + return m_defaultState ? !isSel : isSel; +} + +bool wxSelectionStore::SelectItem(size_t item, bool select) +{ + // search for the item ourselves as like this we get the index where to + // insert it later if needed, so we do only one search in the array instead + // of two (adding item to a sorted array requires a search) + size_t index = m_itemsSel.IndexForInsert(item); + bool isSel = index < m_itemsSel.GetCount() && m_itemsSel[index] == item; + + if ( select != m_defaultState ) + { + if ( !isSel ) + { + m_itemsSel.AddAt(item, index); + + return TRUE; + } + } + else // reset to default state + { + if ( isSel ) + { + m_itemsSel.RemoveAt(index); + return TRUE; + } + } + + return FALSE; +} + +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 ( select != m_defaultState ) + { + // the default state now becomes the same as 'select' + m_defaultState = select; + + // so all the old selections (which had state select) shouldn't be + // selected any more, but all the other ones should + wxIndexArray selOld = m_itemsSel; + m_itemsSel.Empty(); + + // TODO: it should be possible to optimize the searches a bit + // knowing the possible range + + size_t item; + for ( item = 0; item < itemFrom; item++ ) + { + if ( selOld.Index(item) == wxNOT_FOUND ) + m_itemsSel.Add(item); + } + + for ( item = itemTo + 1; item < m_count; item++ ) + { + if ( selOld.Index(item) == wxNOT_FOUND ) + m_itemsSel.Add(item); + } + + // many items (> half) changed state + itemsChanged = NULL; + } + else // select == m_defaultState + { + // get the inclusive range of items between itemFrom and itemTo + size_t count = m_itemsSel.GetCount(), + start = m_itemsSel.IndexForInsert(itemFrom), + end = m_itemsSel.IndexForInsert(itemTo); + + if ( start == count || m_itemsSel[start] < itemFrom ) + { + start++; + } + + if ( end == count || m_itemsSel[end] > itemTo ) + { + end--; + } + + if ( start <= end ) + { + // 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); + } + } + } + } + else // "few" items change state + { + if ( itemsChanged ) + { + itemsChanged->Empty(); + } + + // just add the items to the selection + for ( size_t item = itemFrom; item <= itemTo; item++ ) + { + 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) +{ + size_t count = m_itemsSel.GetCount(), + i = m_itemsSel.IndexForInsert(item); + + if ( i < count && m_itemsSel[i] == item ) + { + // this item itself was in m_itemsSel, remove it from there + m_itemsSel.RemoveAt(i); + + count--; + } + + // and adjust the index of all which follow it + while ( i < count ) + { + // all following elements must be greater than the one we deleted + wxASSERT_MSG( m_itemsSel[i] > item, _T("logic error") ); + + m_itemsSel[i++]--; + } +} + //----------------------------------------------------------------------------- // wxListItemData //----------------------------------------------------------------------------- +wxListItemData::~wxListItemData() +{ + // in the virtual list control the attributes are managed by the main + // program, so don't delete them + if ( !m_owner->IsVirtual() ) + { + delete m_attr; + } + + delete m_rect; +} + void wxListItemData::Init() { m_image = -1; @@ -697,7 +1048,7 @@ wxListItemData::wxListItemData(wxListMainWindow *owner) m_owner = owner; - if ( owner->HasFlag(wxLC_REPORT) ) + if ( owner->InReportView() ) { m_rect = NULL; } @@ -903,7 +1254,7 @@ int wxListHeaderData::GetFormat() const inline int wxListLineData::GetMode() const { - return m_owner->GetListCtrl()->GetWindowStyleFlag() & wxLC_MODE_MASK; + return m_owner->GetListCtrl()->GetWindowStyleFlag() & wxLC_MASK_TYPE; } inline bool wxListLineData::InReportView() const @@ -911,18 +1262,16 @@ inline bool wxListLineData::InReportView() const return m_owner->HasFlag(wxLC_REPORT); } -inline bool wxListLineData::IsVirtal() const +inline bool wxListLineData::IsVirtual() const { return m_owner->IsVirtual(); } -wxListLineData::wxListLineData( wxListMainWindow *owner, size_t line ) +wxListLineData::wxListLineData( wxListMainWindow *owner ) { m_owner = owner; m_items.DeleteContents( TRUE ); - SetLineIndex(line); - if ( InReportView() ) { m_gi = NULL; @@ -932,64 +1281,11 @@ wxListLineData::wxListLineData( wxListMainWindow *owner, size_t line ) m_gi = new GeometryInfo; } - m_hilighted = FALSE; + m_highlighted = FALSE; InitItems( GetMode() == wxLC_REPORT ? m_owner->GetColumnCount() : 1 ); } -wxRect wxListLineData::GetRect() const -{ - if ( !InReportView() ) - return m_gi->m_rectAll; - - wxRect rect; - rect.x = HEADER_OFFSET_X; - rect.y = m_owner->GetLineY(m_lineIndex); - rect.width = m_owner->GetHeaderWidth(); - rect.height = m_owner->GetLineHeight(); - - return rect; -} - -wxRect wxListLineData::GetLabelRect() const -{ - if ( !InReportView() ) - return m_gi->m_rectLabel; - - wxRect rect; - rect.x = HEADER_OFFSET_X; - rect.y = m_owner->GetLineY(m_lineIndex); - rect.width = m_owner->GetColumnWidth(0); - rect.height = m_owner->GetLineHeight(); - - return rect; -} - -wxRect wxListLineData::GetIconRect() const -{ - if ( !InReportView() ) - return m_gi->m_rectIcon; - - wxRect rect; - - wxListItemDataList::Node *node = m_items.GetFirst(); - wxCHECK_MSG( node, rect, _T("no subitems at all??") ); - - wxListItemData *item = node->GetData(); - wxASSERT_MSG( item->HasImage(), _T("GetIconRect() called but no image") ); - - rect.x = HEADER_OFFSET_X; - rect.y = m_owner->GetLineY(m_lineIndex); - m_owner->GetImageSize(item->GetImage(), rect.width, rect.height); - - return rect; -} - -wxRect wxListLineData::GetHighlightRect() const -{ - return InReportView() ? GetRect() : m_gi->m_rectHilight; -} - void wxListLineData::CalculateSize( wxDC *dc, int spacing ) { wxListItemDataList::Node *node = m_items.GetFirst(); @@ -1044,13 +1340,13 @@ void wxListLineData::CalculateSize( wxDC *dc, int spacing ) if ( item->HasText() ) { - m_gi->m_rectHilight.width = m_gi->m_rectLabel.width; - m_gi->m_rectHilight.height = m_gi->m_rectLabel.height; + m_gi->m_rectHighlight.width = m_gi->m_rectLabel.width; + m_gi->m_rectHighlight.height = m_gi->m_rectLabel.height; } else // no text, highlight the icon { - m_gi->m_rectHilight.width = m_gi->m_rectIcon.width; - m_gi->m_rectHilight.height = m_gi->m_rectIcon.height; + m_gi->m_rectHighlight.width = m_gi->m_rectIcon.width; + m_gi->m_rectHighlight.height = m_gi->m_rectIcon.height; } } break; @@ -1084,8 +1380,8 @@ void wxListLineData::CalculateSize( wxDC *dc, int spacing ) m_gi->m_rectAll.height = h; } - m_gi->m_rectHilight.width = m_gi->m_rectAll.width; - m_gi->m_rectHilight.height = m_gi->m_rectAll.height; + m_gi->m_rectHighlight.width = m_gi->m_rectAll.width; + m_gi->m_rectHighlight.height = m_gi->m_rectAll.height; } break; @@ -1128,13 +1424,13 @@ void wxListLineData::SetPosition( int x, int y, else m_gi->m_rectLabel.x = m_gi->m_rectAll.x + 2 + (spacing/2) - (m_gi->m_rectLabel.width/2); m_gi->m_rectLabel.y = m_gi->m_rectAll.y + m_gi->m_rectAll.height + 2 - m_gi->m_rectLabel.height; - m_gi->m_rectHilight.x = m_gi->m_rectLabel.x - 2; - m_gi->m_rectHilight.y = m_gi->m_rectLabel.y - 2; + m_gi->m_rectHighlight.x = m_gi->m_rectLabel.x - 2; + m_gi->m_rectHighlight.y = m_gi->m_rectLabel.y - 2; } else // no text, highlight the icon { - m_gi->m_rectHilight.x = m_gi->m_rectIcon.x - 4; - m_gi->m_rectHilight.y = m_gi->m_rectIcon.y - 4; + m_gi->m_rectHighlight.x = m_gi->m_rectIcon.x - 4; + m_gi->m_rectHighlight.y = m_gi->m_rectIcon.y - 4; } break; @@ -1142,8 +1438,8 @@ void wxListLineData::SetPosition( int x, int y, m_gi->m_rectAll.x = x; m_gi->m_rectAll.y = y; - m_gi->m_rectHilight.x = m_gi->m_rectAll.x; - m_gi->m_rectHilight.y = m_gi->m_rectAll.y; + m_gi->m_rectHighlight.x = m_gi->m_rectAll.x; + m_gi->m_rectHighlight.y = m_gi->m_rectAll.y; m_gi->m_rectLabel.y = m_gi->m_rectAll.y + 2; if (item->HasImage()) @@ -1167,25 +1463,6 @@ void wxListLineData::SetPosition( int x, int y, } } -long wxListLineData::IsHit( int x, int y ) -{ - wxListItemDataList::Node *node = m_items.GetFirst(); - wxCHECK_MSG( node, 0, _T("no subitems at all??") ); - - wxListItemData *item = node->GetData(); - if ( item->HasImage() && GetIconRect().Inside(x, y) ) - return wxLIST_HITTEST_ONITEMICON; - - if ( item->HasText() ) - { - wxRect rect = InReportView() ? GetRect() : GetLabelRect(); - if ( rect.Inside(x, y) ) - return wxLIST_HITTEST_ONITEMLABEL; - } - - return 0; -} - void wxListLineData::InitItems( int num ) { for (int i = 0; i < num; i++) @@ -1253,163 +1530,186 @@ int wxListLineData::GetImage( int index ) const return item->GetImage(); } -void wxListLineData::SetAttributes(wxDC *dc, - const wxListItemAttr *attr, - const wxColour& colText, - const wxFont& font, - bool hilight) +wxListItemAttr *wxListLineData::GetAttr() const { - // don't use foregroud colour for drawing highlighted items - this might - // make them completely invisible (and there is no way to do bit - // arithmetics on wxColour, unfortunately) - if ( !hilight && attr && attr->HasTextColour() ) - { - dc->SetTextForeground(attr->GetTextColour()); - } - else - { - dc->SetTextForeground(colText); - } + wxListItemDataList::Node *node = m_items.GetFirst(); + wxCHECK_MSG( node, NULL, _T("invalid column index in GetAttr()") ); - if ( attr && attr->HasFont() ) - { - dc->SetFont(attr->GetFont()); - } - else - { - dc->SetFont(font); - } + wxListItemData *item = node->GetData(); + return item->GetAttr(); } -void wxListLineData::Draw( wxDC *dc, int y, int height, bool hilighted ) +void wxListLineData::SetAttr(wxListItemAttr *attr) { - wxRect rect = GetRect(); - m_owner->CalcScrolledPosition( rect.x, rect.y, &rect.x, &rect.y ); + wxListItemDataList::Node *node = m_items.GetFirst(); + wxCHECK_RET( node, _T("invalid column index in SetAttr()") ); - if ( !m_owner->IsExposed( rect ) ) - return; + wxListItemData *item = node->GetData(); + item->SetAttr(attr); +} +bool wxListLineData::SetAttributes(wxDC *dc, + const wxListItemAttr *attr, + bool highlighted) +{ wxWindow *listctrl = m_owner->GetParent(); - // use our own flag if we maintain it - if ( !m_owner->IsVirtual() ) - hilighted = m_hilighted; + // fg colour - // default foreground 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) wxColour colText; - if ( hilighted ) + if ( highlighted ) { - colText = wxSystemSettings::GetSystemColour( wxSYS_COLOUR_HIGHLIGHTTEXT ); + colText = wxSystemSettings::GetSystemColour(wxSYS_COLOUR_HIGHLIGHTTEXT); } else { - colText = listctrl->GetForegroundColour(); + if ( attr && attr->HasTextColour() ) + { + colText = attr->GetTextColour(); + } + else + { + colText = listctrl->GetForegroundColour(); + } } - // default font - wxFont font = listctrl->GetFont(); + dc->SetTextForeground(colText); + + // font + wxFont font; + if ( attr && attr->HasFont() ) + { + font = attr->GetFont(); + } + else + { + font = listctrl->GetFont(); + } - // VZ: currently we set the colours/fonts only once, but like this (i.e. - // using SetAttributes() inside the loop), it will be trivial to - // customize the subitems (in report mode) too. - wxListItemData *item = m_items.GetFirst()->GetData(); - wxListItemAttr *attr = item->GetAttributes(); - SetAttributes(dc, attr, colText, font, hilighted); + dc->SetFont(font); + // bg colour bool hasBgCol = attr && attr->HasBackgroundColour(); - if ( hilighted || hasBgCol ) + if ( highlighted || hasBgCol ) { - if ( hilighted ) + if ( highlighted ) { - dc->SetBrush( *m_owner->m_hilightBrush ); + dc->SetBrush( *m_owner->GetHighlightBrush() ); } else { - if ( hasBgCol ) - dc->SetBrush(wxBrush(attr->GetBackgroundColour(), wxSOLID)); - else - dc->SetBrush( * wxWHITE_BRUSH ); + dc->SetBrush(wxBrush(attr->GetBackgroundColour(), wxSOLID)); } - dc->SetPen( * wxTRANSPARENT_PEN ); - dc->DrawRectangle( GetHighlightRect() ); + dc->SetPen( *wxTRANSPARENT_PEN ); + + return TRUE; } + return FALSE; +} + +void wxListLineData::Draw( wxDC *dc ) +{ wxListItemDataList::Node *node = m_items.GetFirst(); + wxCHECK_RET( node, _T("no subitems at all??") ); - if ( GetMode() == wxLC_REPORT) + bool highlighted = IsHighlighted(); + + wxListItemAttr *attr = GetAttr(); + + if ( SetAttributes(dc, attr, highlighted) ) { - size_t col = 0; - int x = HEADER_OFFSET_X; + dc->DrawRectangle( m_gi->m_rectHighlight ); + } - y += EXTRA_HEIGHT / 2; + wxListItemData *item = node->GetData(); + if (item->HasImage()) + { + wxRect rectIcon = m_gi->m_rectIcon; + m_owner->DrawImage( item->GetImage(), dc, + rectIcon.x, rectIcon.y ); + } - while ( node ) - { - wxListItemData *item = node->GetData(); + if (item->HasText()) + { + wxRect rectLabel = m_gi->m_rectLabel; - int xOld = x; + wxDCClipper clipper(*dc, rectLabel); + dc->DrawText( item->GetText(), rectLabel.x, rectLabel.y ); + } +} - if ( item->HasImage() ) - { - int ix, iy; - m_owner->DrawImage( item->GetImage(), dc, x, y ); - m_owner->GetImageSize( item->GetImage(), ix, iy ); - x += ix + 5; // FIXME: what is "5"? - } +void wxListLineData::DrawInReportMode( wxDC *dc, + const wxRect& rect, + const wxRect& rectHL, + bool highlighted ) +{ + // TODO: later we should support setting different attributes for + // different columns - to do it, just add "col" argument to + // GetAttr() and move these lines into the loop below + wxListItemAttr *attr = GetAttr(); + if ( SetAttributes(dc, attr, highlighted) ) + { + dc->DrawRectangle( rectHL ); + } - int width = m_owner->GetColumnWidth(col++); + wxListItemDataList::Node *node = m_items.GetFirst(); + wxCHECK_RET( node, _T("no subitems at all??") ); - dc->SetClippingRegion(x, y, width, height); + size_t col = 0; + wxCoord x = rect.x + HEADER_OFFSET_X, + y = rect.y + (LINE_SPACING + EXTRA_HEIGHT) / 2; - if ( item->HasText() ) - { - dc->DrawText( item->GetText(), x, y + 1 ); - } + while ( node ) + { + wxListItemData *item = node->GetData(); - dc->DestroyClippingRegion(); + int width = m_owner->GetColumnWidth(col++); + int xOld = x; + x += width; - x = xOld + width; + if ( item->HasImage() ) + { + int ix, iy; + m_owner->DrawImage( item->GetImage(), dc, xOld, y ); + m_owner->GetImageSize( item->GetImage(), ix, iy ); - node = node->GetNext(); + ix += IMAGE_MARGIN_IN_REPORT_MODE; + + xOld += ix; + width -= ix; } - } - else // !report - { - if (node) - { - wxListItemData *item = node->GetData(); - if (item->HasImage()) - { - wxRect rectIcon = GetIconRect(); - m_owner->DrawImage( item->GetImage(), dc, - rectIcon.x, rectIcon.y ); - } - if (item->HasText()) - { - wxRect rectLabel = GetLabelRect(); - dc->DrawText( item->GetText(), rectLabel.x, rectLabel.y ); - } + wxDCClipper clipper(*dc, xOld, y, width, rect.height); + + if ( item->HasText() ) + { + dc->DrawText( item->GetText(), xOld, y ); } + + node = node->GetNext(); } } -bool wxListLineData::Hilight( bool on ) +bool wxListLineData::Highlight( bool on ) { - wxCHECK_MSG( !m_owner->IsVirtual(), FALSE, _T("unexpected call to Hilight") ); + wxCHECK_MSG( !m_owner->IsVirtual(), FALSE, _T("unexpected call to Highlight") ); - if ( on == m_hilighted ) + if ( on == m_highlighted ) return FALSE; - m_hilighted = on; + m_highlighted = on; return TRUE; } -void wxListLineData::ReverseHilight( void ) +void wxListLineData::ReverseHighlight( void ) { - Hilight(!IsHilighted()); + Highlight(!IsHighlighted()); } //----------------------------------------------------------------------------- @@ -1455,14 +1755,36 @@ wxListHeaderWindow::~wxListHeaderWindow( void ) void wxListHeaderWindow::DoDrawRect( wxDC *dc, int x, int y, int w, int h ) { #ifdef __WXGTK__ - GtkStateType state = GTK_STATE_NORMAL; - if (!m_parent->IsEnabled()) state = GTK_STATE_INSENSITIVE; + GtkStateType state = m_parent->IsEnabled() ? GTK_STATE_NORMAL + : GTK_STATE_INSENSITIVE; x = dc->XLOG2DEV( x ); - gtk_paint_box (m_wxwindow->style, GTK_PIZZA(m_wxwindow)->bin_window, state, GTK_SHADOW_OUT, - (GdkRectangle*) NULL, m_wxwindow, "button", x-1, y-1, w+2, h+2); -#else + gtk_paint_box (m_wxwindow->style, GTK_PIZZA(m_wxwindow)->bin_window, + state, GTK_SHADOW_OUT, + (GdkRectangle*) NULL, m_wxwindow, "button", + x-1, y-1, w+2, h+2); +#elif defined( __WXMAC__ ) + const int m_corner = 1; + + dc->SetBrush( *wxTRANSPARENT_BRUSH ); + + dc->SetPen( wxPen( wxSystemSettings::GetSystemColour( wxSYS_COLOUR_BTNSHADOW ) , 1 , wxSOLID ) ); + dc->DrawLine( x+w-m_corner+1, y, x+w, y+h ); // right (outer) + dc->DrawRectangle( x, y+h, w+1, 1 ); // bottom (outer) + + wxPen pen( wxColour( 0x88 , 0x88 , 0x88 ), 1, wxSOLID ); + + dc->SetPen( pen ); + dc->DrawLine( x+w-m_corner, y, x+w-1, y+h ); // right (inner) + dc->DrawRectangle( x+1, y+h-1, w-2, 1 ); // bottom (inner) + + dc->SetPen( *wxWHITE_PEN ); + dc->DrawRectangle( x, y, w-m_corner+1, 1 ); // top (outer) + dc->DrawRectangle( x, y, 1, h ); // left (outer) + dc->DrawLine( x, y+h-1, x+1, y+h-1 ); + dc->DrawLine( x+w-1, y, x+w-1, y+1 ); +#else // !GTK, !Mac const int m_corner = 1; dc->SetBrush( *wxTRANSPARENT_BRUSH ); @@ -1542,9 +1864,11 @@ void wxListHeaderWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) 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(); + wxDCClipper clipper(dc, x, HEADER_OFFSET_Y, cw-5, h-4 ); + + dc.DrawText( item.GetText(), + x + EXTRA_WIDTH, HEADER_OFFSET_Y + EXTRA_HEIGHT ); + x += wCol; if (xEnd > w+5) @@ -1802,7 +2126,6 @@ IMPLEMENT_DYNAMIC_CLASS(wxListMainWindow,wxScrolledWindow); BEGIN_EVENT_TABLE(wxListMainWindow,wxScrolledWindow) EVT_PAINT (wxListMainWindow::OnPaint) - EVT_SIZE (wxListMainWindow::OnSize) EVT_MOUSE_EVENTS (wxListMainWindow::OnMouse) EVT_CHAR (wxListMainWindow::OnChar) EVT_KEY_DOWN (wxListMainWindow::OnKeyDown) @@ -1817,7 +2140,7 @@ void wxListMainWindow::Init() m_dirty = TRUE; m_countVirt = 0; m_lineFrom = - m_lineTo = 0; + m_lineTo = (size_t)-1; m_linesPerPage = 0; m_headerWidth = @@ -1861,7 +2184,8 @@ wxListMainWindow::wxListMainWindow() { Init(); - m_hilightBrush = (wxBrush *) NULL; + m_highlightBrush = + m_highlightUnfocusedBrush = (wxBrush *) NULL; m_xScroll = m_yScroll = 0; @@ -1878,7 +2202,24 @@ wxListMainWindow::wxListMainWindow( wxWindow *parent, { Init(); - m_hilightBrush = 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; @@ -1890,9 +2231,10 @@ wxListMainWindow::wxListMainWindow( wxWindow *parent, wxListMainWindow::~wxListMainWindow() { - DeleteEverything(); + DoDeleteAllItems(); - delete m_hilightBrush; + delete m_highlightBrush; + delete m_highlightUnfocusedBrush; delete m_renameTimer; } @@ -1901,7 +2243,7 @@ void wxListMainWindow::CacheLineData(size_t line) { wxListCtrl *listctrl = GetListCtrl(); - wxListLineData *ld = GetFirstLine(); + wxListLineData *ld = GetDummyLine(); size_t countCol = GetColumnCount(); for ( size_t col = 0; col < countCol; col++ ) @@ -1909,11 +2251,11 @@ void wxListMainWindow::CacheLineData(size_t line) ld->SetText(col, listctrl->OnGetItemText(line, col)); } - ld->SetImage(0, listctrl->OnGetItemImage(line)); - ld->SetLineIndex(line); + ld->SetImage(listctrl->OnGetItemImage(line)); + ld->SetAttr(listctrl->OnGetItemAttr(line)); } -wxListLineData *wxListMainWindow::GetFirstLine() const +wxListLineData *wxListMainWindow::GetDummyLine() const { wxASSERT_MSG( !IsEmpty(), _T("invalid line index") ); @@ -1924,15 +2266,17 @@ wxListLineData *wxListMainWindow::GetFirstLine() const wxASSERT_MSG( IsVirtual(), _T("logic error") ); wxListMainWindow *self = wxConstCast(this, wxListMainWindow); - wxListLineData *line = new wxListLineData( self, 0 ); + wxListLineData *line = new wxListLineData(self); self->m_lines.Add(line); } - m_lines[0].SetLineIndex(0); - return &m_lines[0]; } +// ---------------------------------------------------------------------------- +// line geometry (report mode only) +// ---------------------------------------------------------------------------- + wxCoord wxListMainWindow::GetLineHeight() const { wxASSERT_MSG( HasFlag(wxLC_REPORT), _T("only works in report mode") ); @@ -1965,59 +2309,148 @@ wxCoord wxListMainWindow::GetLineY(size_t line) const return LINE_SPACING + line*GetLineHeight(); } -bool wxListMainWindow::IsHilighted(size_t line) const +wxRect wxListMainWindow::GetLineRect(size_t line) const +{ + if ( !InReportView() ) + return GetLine(line)->m_gi->m_rectAll; + + wxRect rect; + rect.x = HEADER_OFFSET_X; + rect.y = GetLineY(line); + rect.width = GetHeaderWidth(); + rect.height = GetLineHeight(); + + return rect; +} + +wxRect wxListMainWindow::GetLineLabelRect(size_t line) const +{ + if ( !InReportView() ) + return GetLine(line)->m_gi->m_rectLabel; + + wxRect rect; + rect.x = HEADER_OFFSET_X; + rect.y = GetLineY(line); + rect.width = GetColumnWidth(0); + rect.height = GetLineHeight(); + + return rect; +} + +wxRect wxListMainWindow::GetLineIconRect(size_t line) const +{ + if ( !InReportView() ) + return GetLine(line)->m_gi->m_rectIcon; + + wxListLineData *ld = GetLine(line); + wxASSERT_MSG( ld->HasImage(), _T("should have an image") ); + + wxRect rect; + rect.x = HEADER_OFFSET_X; + rect.y = GetLineY(line); + GetImageSize(ld->GetImage(), rect.width, rect.height); + + return rect; +} + +wxRect wxListMainWindow::GetLineHighlightRect(size_t line) const +{ + return InReportView() ? GetLineRect(line) + : GetLine(line)->m_gi->m_rectHighlight; +} + +long wxListMainWindow::HitTestLine(size_t line, int x, int y) const +{ + wxListLineData *ld = GetLine(line); + + if ( ld->HasImage() && GetLineIconRect(line).Inside(x, y) ) + return wxLIST_HITTEST_ONITEMICON; + + if ( ld->HasText() ) + { + wxRect rect = InReportView() ? GetLineRect(line) + : GetLineLabelRect(line); + + if ( rect.Inside(x, y) ) + return wxLIST_HITTEST_ONITEMLABEL; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// highlight (selection) handling +// ---------------------------------------------------------------------------- + +bool wxListMainWindow::IsHighlighted(size_t line) const { if ( IsVirtual() ) { - return m_selections.Index(line) != wxNOT_FOUND; + return m_selStore.IsSelected(line); } else // !virtual { wxListLineData *ld = GetLine(line); - wxCHECK_MSG( ld, FALSE, _T("invalid index in IsHilighted") ); + wxCHECK_MSG( ld, FALSE, _T("invalid index in IsHighlighted") ); - return ld->IsHilighted(); + return ld->IsHighlighted(); } } -bool wxListMainWindow::HilightLine( size_t line, bool hilight ) +void wxListMainWindow::HighlightLines( size_t lineFrom, + size_t lineTo, + bool highlight ) { - bool changed; - if ( IsVirtual() ) { - changed = FALSE; - - int index = m_selections.Index(line); - if ( hilight ) + wxArrayInt linesChanged; + if ( !m_selStore.SelectRange(lineFrom, lineTo, highlight, + &linesChanged) ) { - if ( index == wxNOT_FOUND ) + // 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++ ) { - m_selections.Add(line); - changed = TRUE; + RefreshLine(linesChanged[n]); } } - else // !hilight + } + else // iterate over all items in non report view + { + for ( size_t line = lineFrom; line <= lineTo; line++ ) { - if ( index != wxNOT_FOUND ) + if ( HighlightLine(line, highlight) ) { - m_selections.RemoveAt((size_t)index); - changed = TRUE; + RefreshLine(line); } } } +} + +bool wxListMainWindow::HighlightLine( size_t line, bool highlight ) +{ + bool changed; + + if ( IsVirtual() ) + { + changed = m_selStore.SelectItem(line, highlight); + } else // !virtual { wxListLineData *ld = GetLine(line); - wxCHECK_MSG( ld, FALSE, _T("invalid index in IsHilighted") ); + wxCHECK_MSG( ld, FALSE, _T("invalid index in HighlightLine") ); - changed = ld->Hilight(hilight); + changed = ld->Highlight(highlight); } if ( changed ) { - SendNotify( line, hilight ? wxEVT_COMMAND_LIST_ITEM_SELECTED - : wxEVT_COMMAND_LIST_ITEM_DESELECTED ); + SendNotify( line, highlight ? wxEVT_COMMAND_LIST_ITEM_SELECTED + : wxEVT_COMMAND_LIST_ITEM_DESELECTED ); } return changed; @@ -2025,37 +2458,111 @@ bool wxListMainWindow::HilightLine( size_t line, bool hilight ) void wxListMainWindow::RefreshLine( size_t line ) { - wxRect rect = GetLine(line)->GetRect(); + 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 ); RefreshRect( rect ); } -void wxListMainWindow::RefreshLines( size_t lineFrom, size_t lineTo ) +void wxListMainWindow::RefreshLines( size_t lineFrom, size_t lineTo ) +{ + // we suppose that they are ordered by caller + wxASSERT_MSG( lineFrom <= lineTo, _T("indices in disorder") ); + + wxASSERT_MSG( lineTo < GetItemCount(), _T("invalid line range") ); + + if ( HasFlag(wxLC_REPORT) ) + { + size_t visibleFrom, visibleTo; + GetVisibleLinesRange(&visibleFrom, &visibleTo); + + if ( lineFrom < visibleFrom ) + lineFrom = visibleFrom; + if ( lineTo > visibleTo ) + lineTo = visibleTo; + + wxRect rect; + rect.x = 0; + rect.y = GetLineY(lineFrom); + rect.width = GetClientSize().x; + rect.height = GetLineY(lineTo) - rect.y + GetLineHeight(); + + CalcScrolledPosition( rect.x, rect.y, &rect.x, &rect.y ); + RefreshRect( rect ); + } + else // !report + { + // TODO: this should be optimized... + for ( size_t line = lineFrom; line <= lineTo; line++ ) + { + RefreshLine(line); + } + } +} + +void wxListMainWindow::RefreshAfter( size_t lineFrom ) { - // we suppose that they are ordered by caller - wxASSERT_MSG( lineFrom <= lineTo, _T("indices in disorder") ); - if ( HasFlag(wxLC_REPORT) ) { - if ( lineFrom < m_lineFrom ) - lineFrom = m_lineFrom; - if ( lineTo > m_lineTo ) - lineTo = m_lineTo; + size_t visibleFrom; + GetVisibleLinesRange(&visibleFrom, NULL); + + if ( lineFrom < visibleFrom ) + lineFrom = visibleFrom; wxRect rect; rect.x = 0; rect.y = GetLineY(lineFrom); - rect.width = GetClientSize().x; - rect.height = GetLineY(lineTo) - rect.y; + + wxSize size = GetClientSize(); + rect.width = size.x; + // refresh till the bottom of the window + rect.height = size.y - rect.y; CalcScrolledPosition( rect.x, rect.y, &rect.x, &rect.y ); RefreshRect( rect ); } else // !report { - // TODO: this should be optimized... - for ( size_t line = lineFrom; line <= lineTo; line++ ) + // TODO: how to do it more efficiently? + m_dirty = TRUE; + } +} + +void wxListMainWindow::RefreshSelected() +{ + if ( IsEmpty() ) + return; + + size_t from, to; + if ( InReportView() ) + { + GetVisibleLinesRange(&from, &to); + } + else // !virtual + { + from = 0; + to = GetItemCount() - 1; + } + + 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); } @@ -2068,15 +2575,15 @@ void wxListMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) // done (a Windows requirement). wxPaintDC dc( this ); - if ( m_dirty ) + if ( IsEmpty() ) { - // postpone redrawing until the next OnIdle() call to minimize flicker + // empty control. nothing to draw return; } - if ( IsEmpty() ) + if ( m_dirty ) { - // empty control. nothing to draw + // delay the repainting until we calculate all the items positions return; } @@ -2091,45 +2598,70 @@ void wxListMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) if ( HasFlag(wxLC_REPORT) ) { - int lineSpacing = GetLineHeight(); + int lineHeight = GetLineHeight(); + + size_t visibleFrom, visibleTo; + GetVisibleLinesRange(&visibleFrom, &visibleTo); - for ( size_t line = m_lineFrom; line <= m_lineTo; line++ ) + wxRect rectLine; + 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++ ) { - GetLine(line)->Draw( &dc, - GetLineY(line), - lineSpacing, - IsHilighted(line) ); + rectLine = GetLineRect(line); + + if ( !IsExposed(rectLine.x - xOrig, rectLine.y - yOrig, + rectLine.width, rectLine.height) ) + { + // don't redraw unaffected lines to avoid flicker + continue; + } + + GetLine(line)->DrawInReportMode( &dc, + rectLine, + GetLineHighlightRect(line), + IsHighlighted(line) ); } if ( HasFlag(wxLC_HRULES) ) { - wxPen pen(wxSystemSettings:: - GetSystemColour(wxSYS_COLOUR_3DLIGHT), 1, wxSOLID); + wxPen pen(GetRuleColour(), 1, wxSOLID); wxSize clientSize = GetClientSize(); - for ( size_t i = m_lineFrom; i <= m_lineTo; i++ ) + for ( size_t i = visibleFrom; i <= visibleTo; i++ ) { dc.SetPen(pen); dc.SetBrush( *wxTRANSPARENT_BRUSH ); - dc.DrawLine(0 - dev_x, i*lineSpacing, - clientSize.x - dev_x, i*lineSpacing); + dc.DrawLine(0 - dev_x, i*lineHeight, + clientSize.x - dev_x, i*lineHeight); } // Draw last horizontal rule - if ( m_lineTo > m_lineFrom ) + if ( visibleTo > visibleFrom ) { dc.SetPen(pen); dc.SetBrush( *wxTRANSPARENT_BRUSH ); - dc.DrawLine(0 - dev_x, m_lineTo*lineSpacing, - clientSize.x - dev_x , m_lineTo*lineSpacing ); + dc.DrawLine(0 - dev_x, m_lineTo*lineHeight, + clientSize.x - dev_x , m_lineTo*lineHeight ); } } // Draw vertical rules if required if ( HasFlag(wxLC_VRULES) && !IsEmpty() ) { - wxPen pen(wxSystemSettings:: - GetSystemColour(wxSYS_COLOUR_3DLIGHT), 1, wxSOLID); + wxPen pen(GetRuleColour(), 1, wxSOLID); int col = 0; wxRect firstItemRect; @@ -2157,53 +2689,42 @@ void wxListMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) } } - if ( HasCurrent() && m_hasFocus ) + if ( HasCurrent() ) { - wxRect rect; - - if ( IsVirtual() ) - { - // just offset the rect of the first line to position it correctly - wxListLineData *line = GetFirstLine(); - rect = line->GetHighlightRect(); - rect.y = GetLineY(m_current); - } - else + // 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__ + if ( !m_hasFocus ) +#else // !__WXMAC__ + if ( m_hasFocus ) +#endif // __WXMAC__/!__WXMAC__ { - rect = GetLine(m_current)->GetHighlightRect(); + dc.SetPen( *wxBLACK_PEN ); + dc.SetBrush( *wxTRANSPARENT_BRUSH ); + dc.DrawRectangle( GetLineHighlightRect(m_current) ); } - - dc.SetPen( *wxBLACK_PEN ); - dc.SetBrush( *wxTRANSPARENT_BRUSH ); - dc.DrawRectangle( rect ); } dc.EndDrawing(); } -void wxListMainWindow::HilightAll( bool on ) +void wxListMainWindow::HighlightAll( bool on ) { - bool needsRefresh = FALSE; - - size_t count = GetItemCount(); - for ( size_t line = 0; line < count; line++ ) + if ( IsSingleSel() ) { - if ( HilightLine( line, on ) ) + wxASSERT_MSG( !on, _T("can't do this in a single sel control") ); + + // we just have one item to turn off + if ( HasCurrent() && IsHighlighted(m_current) ) { - if ( HasFlag(wxLC_REPORT) ) - { - needsRefresh = TRUE; - } - else - { - RefreshLine(line); - } + HighlightLine(m_current, FALSE); + RefreshLine(m_current); } } - - if ( needsRefresh ) + else // multi sel { - RefreshLines( 0, count - 1 ); + HighlightLines(0, GetItemCount() - 1, on); } } @@ -2219,7 +2740,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 ); } @@ -2260,7 +2790,7 @@ void wxListMainWindow::EditLabel( long item ) PrepareDC( dc ); wxString s = data->GetText(0); - wxRect rectLabel = data->GetLabelRect(); + wxRect rectLabel = GetLineLabelRect(m_currentEdit); rectLabel.x = dc.LogicalToDeviceX( rectLabel.x ); rectLabel.y = dc.LogicalToDeviceY( rectLabel.y ); @@ -2328,7 +2858,7 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) int y = event.GetY(); CalcUnscrolledPosition( x, y, &x, &y ); - /* Did we actually hit an item ? */ + // where did we hit it (if we did)? long hitResult = 0; size_t count = GetItemCount(), @@ -2336,10 +2866,9 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) if ( HasFlag(wxLC_REPORT) ) { - wxCoord lineHeight = GetLineHeight(); - - current = y / lineHeight; - hitResult = GetFirstLine()->IsHit( x, y % lineHeight ); + current = y / GetLineHeight(); + if ( current < count ) + hitResult = HitTestLine(current, x, y); } else // !report { @@ -2347,10 +2876,8 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) // enumerating all items is still not a way to do it!! for ( current = 0; current < count; current++ ) { - wxListLineData *line = (wxListLineData *) NULL; - line = GetLine(current); - hitResult = line->IsHit( x, y ); - if (hitResult) + hitResult = HitTestLine(current, x, y); + if ( hitResult ) break; } } @@ -2432,21 +2959,20 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) size_t oldCurrent = m_current; - if ( HasFlag(wxLC_SINGLE_SEL) || - !(event.ControlDown() || event.ShiftDown()) ) + if ( IsSingleSel() || !(event.ControlDown() || event.ShiftDown()) ) { + HighlightAll( FALSE ); m_current = current; - HilightAll( FALSE ); - ReverseHilight(m_current); + ReverseHighlight(m_current); } - else // multi sel + else // multi sel & either ctrl or shift is down { if (event.ControlDown()) { m_current = current; - ReverseHilight(m_current); + ReverseHighlight(m_current); } else if (event.ShiftDown()) { @@ -2461,17 +2987,7 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) lineFrom = m_current; } - bool needsRefresh = FALSE; - for ( size_t i = lineFrom; i <= lineTo; i++ ) - { - if ( HilightLine(i, TRUE) ) - needsRefresh = TRUE; - } - - if ( needsRefresh ) - { - RefreshLines(lineFrom, lineTo); - } + HighlightLines(lineFrom, lineTo); } else // !ctrl, !shift { @@ -2492,13 +3008,12 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) } } -void wxListMainWindow::MoveToFocus() +void wxListMainWindow::MoveToItem(size_t item) { - if (!HasCurrent()) + if ( item == (size_t)-1 ) return; - wxListLineData *data = IsVirtual() ? GetFirstLine() : GetLine(m_current); - wxRect rect = data->GetRect(); + wxRect rect = GetLineRect(item); int client_w, client_h; GetClientSize( &client_w, &client_h ); @@ -2508,14 +3023,16 @@ void wxListMainWindow::MoveToFocus() if ( HasFlag(wxLC_REPORT) ) { + // the next we need the range of lines shown it might be different, so + // recalculate it + ResetVisibleLinesRange(); + if (rect.y < view_y ) Scroll( -1, rect.y/m_yScroll ); if (rect.y+rect.height+5 > view_y+client_h) Scroll( -1, (rect.y+rect.height-client_h+SCROLL_UNIT_Y)/m_yScroll ); - - UpdateShownLinesRange(); } - else + else // !report { if (rect.x-view_x < 5) Scroll( (rect.x-5)/m_xScroll, -1 ); @@ -2537,7 +3054,7 @@ void wxListMainWindow::OnArrowChar(size_t newCurrent, const wxKeyEvent& event) // in single selection we just ignore Shift as we can't select several // items anyhow - if ( event.ShiftDown() && !HasFlag(wxLC_SINGLE_SEL) ) + if ( event.ShiftDown() && !IsSingleSel() ) { m_current = newCurrent; @@ -2548,28 +3065,22 @@ void wxListMainWindow::OnArrowChar(size_t newCurrent, const wxKeyEvent& event) oldCurrent = m_current; } - bool needsRefresh = FALSE; - for ( size_t line = oldCurrent; line <= newCurrent; line++ ) - { - if ( HilightLine( line ) ) - { - needsRefresh = TRUE; - } - } - - if ( needsRefresh ) - RefreshLines( oldCurrent, newCurrent ); + HighlightLines(oldCurrent, newCurrent); } else // !shift { + // all previously selected items are unselected unless ctrl is held + if ( !event.ControlDown() ) + HighlightAll(FALSE); + m_current = newCurrent; - HilightLine( oldCurrent, FALSE ); + HighlightLine( oldCurrent, FALSE ); RefreshLine( oldCurrent ); if ( !event.ControlDown() ) { - HilightLine( m_current, TRUE ); + HighlightLine( m_current, TRUE ); } } @@ -2731,7 +3242,7 @@ void wxListMainWindow::OnChar( wxKeyEvent &event ) break; case WXK_SPACE: - if ( HasFlag(wxLC_SINGLE_SEL) ) + if ( IsSingleSel() ) { wxListEvent le( wxEVT_COMMAND_LIST_ITEM_ACTIVATED, GetParent()->GetId() ); @@ -2739,11 +3250,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 - { - ReverseHilight(m_current); - } + + ReverseHighlight(m_current); break; case WXK_RETURN: @@ -2775,12 +3291,11 @@ void wxListMainWindow::OnSetFocus( wxFocusEvent &WXUNUSED(event) ) { m_hasFocus = TRUE; - if ( HasCurrent() ) - RefreshLine( m_current ); - if (!GetParent()) return; + RefreshSelected(); + #ifdef __WXGTK__ g_focusWindow = GetParent(); #endif @@ -2794,8 +3309,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 ) @@ -2818,7 +3332,7 @@ void wxListMainWindow::DrawImage( int index, wxDC *dc, int x, int y ) } } -void wxListMainWindow::GetImageSize( int index, int &width, int &height ) +void wxListMainWindow::GetImageSize( int index, int &width, int &height ) const { if ( HasFlag(wxLC_ICON) && m_normal_image_list ) { @@ -2843,9 +3357,9 @@ void wxListMainWindow::GetImageSize( int index, int &width, int &height ) } } -int wxListMainWindow::GetTextLength( const wxString &s ) +int wxListMainWindow::GetTextLength( const wxString &s ) const { - wxClientDC dc( this ); + wxClientDC dc( wxConstCast(this, wxListMainWindow) ); dc.SetFont( GetFont() ); wxCoord lw; @@ -3040,19 +3554,21 @@ void wxListMainWindow::SetItem( wxListItem &item ) wxCHECK_RET( id >= 0 && (size_t)id < GetItemCount(), _T("invalid item index in SetItem") ); - if ( IsVirtual() ) + if ( !IsVirtual() ) + { + wxListLineData *line = GetLine((size_t)id); + line->SetItem( item.m_col, item ); + } + + if ( InReportView() ) { // just refresh the line to show the new value of the text/image RefreshLine((size_t)id); } - else // !virtual + else // !report { + // refresh everything (resulting in horrible flicker - FIXME!) m_dirty = TRUE; - - wxListLineData *line = GetLine((size_t)id); - if ( HasFlag(wxLC_REPORT) ) - item.m_width = GetColumnWidth( item.m_col ); - line->SetItem( item.m_col, item ); } } @@ -3062,8 +3578,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 ) @@ -3075,9 +3592,13 @@ void wxListMainWindow::SetItemState( long litem, long state, long stateMask ) m_current = item; OnFocusLine( m_current ); - if ( HasFlag(wxLC_SINGLE_SEL) && (oldCurrent != (size_t)-1) ) + if ( oldCurrent != (size_t)-1 ) { - HilightLine(oldCurrent, FALSE); + if ( IsSingleSel() ) + { + HighlightLine(oldCurrent, FALSE); + } + RefreshLine(oldCurrent); } @@ -3091,15 +3612,18 @@ 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; - if ( HasFlag(wxLC_SINGLE_SEL) ) + if ( IsSingleSel() ) { if ( on ) { @@ -3113,7 +3637,7 @@ void wxListMainWindow::SetItemState( long litem, long state, long stateMask ) if ( oldCurrent != (size_t)-1 ) { - HilightLine( oldCurrent, FALSE ); + HighlightLine( oldCurrent, FALSE ); RefreshLine( oldCurrent ); } } @@ -3126,7 +3650,7 @@ void wxListMainWindow::SetItemState( long litem, long state, long stateMask ) } } - if ( HilightLine(item, on) ) + if ( HighlightLine(item, on) ) { RefreshLine(item); } @@ -3148,7 +3672,7 @@ int wxListMainWindow::GetItemState( long item, long stateMask ) if ( stateMask & wxLIST_STATE_SELECTED ) { - if ( IsHilighted(item) ) + if ( IsHighlighted(item) ) ret |= wxLIST_STATE_SELECTED; } @@ -3175,22 +3699,26 @@ size_t wxListMainWindow::GetItemCount() const void wxListMainWindow::SetItemCount(long count) { + m_selStore.SetItemCount(count); m_countVirt = count; - Refresh(); + ResetVisibleLinesRange(); + + // scrollbars must be reset + m_dirty = TRUE; } int wxListMainWindow::GetSelectedItemCount() { // deal with the quick case first - if ( HasFlag(wxLC_SINGLE_SEL) ) + if ( IsSingleSel() ) { - return m_current == (size_t)-1 ? FALSE : IsHilighted(m_current); + return HasCurrent() ? IsHighlighted(m_current) : FALSE; } // virtual controls remmebers all its selections itself if ( IsVirtual() ) - return m_selections.GetCount(); + return m_selStore.GetSelectedCount(); // TODO: we probably should maintain the number of items selected even for // non virtual controls as enumerating all lines is really slow... @@ -3198,7 +3726,7 @@ int wxListMainWindow::GetSelectedItemCount() size_t count = GetItemCount(); for ( size_t line = 0; line < count; line++ ) { - if ( GetLine(line)->IsHilighted() ) + if ( GetLine(line)->IsHighlighted() ) countSel++; } @@ -3214,7 +3742,8 @@ void wxListMainWindow::GetItemRect( long index, wxRect &rect ) wxCHECK_RET( index >= 0 && (size_t)index < GetItemCount(), _T("invalid index in GetItemRect") ); - rect = GetLine((size_t)index)->GetRect(); + rect = GetLineRect((size_t)index); + CalcScrolledPosition(rect.x, rect.y, &rect.x, &rect.y); } @@ -3233,17 +3762,8 @@ bool wxListMainWindow::GetItemPosition(long item, wxPoint& pos) // geometry calculation // ---------------------------------------------------------------------------- -void wxListMainWindow::OnSize( wxSizeEvent &WXUNUSED(event) ) -{ - // wait for the next OnIdle() with geometry recalculation - m_dirty = TRUE; -} - -void wxListMainWindow::CalculatePositions() +void wxListMainWindow::RecalculatePositions(bool noRefresh) { - if ( IsEmpty() ) - return; - wxClientDC dc( this ); dc.SetFont( GetFont() ); @@ -3262,15 +3782,17 @@ void wxListMainWindow::CalculatePositions() if ( HasFlag(wxLC_REPORT) ) { // all lines have the same height - int lineSpacing = GetLineHeight(); + int lineHeight = GetLineHeight(); // scroll one line per step - m_yScroll = lineSpacing; + m_yScroll = lineHeight; size_t lineCount = GetItemCount(); - int entireHeight = lineCount*lineSpacing + 2*LINE_SPACING; + int entireHeight = lineCount*lineHeight + LINE_SPACING; + + m_linesPerPage = clientHeight / lineHeight; - m_linesPerPage = clientHeight / lineSpacing; + ResetVisibleLinesRange(); SetScrollbars( m_xScroll, m_yScroll, (GetHeaderWidth() + m_xScroll - 1)/m_xScroll, @@ -3278,8 +3800,6 @@ void wxListMainWindow::CalculatePositions() GetScrollPos(wxHORIZONTAL), GetScrollPos(wxVERTICAL), TRUE ); - - UpdateShownLinesRange(); } else // !report { @@ -3289,7 +3809,7 @@ void wxListMainWindow::CalculatePositions() clientHeight -= 4; // sunken frame - int entireWidth; + int entireWidth = 0; for (int tries = 0; tries < 2; tries++) { @@ -3308,7 +3828,7 @@ void wxListMainWindow::CalculatePositions() line->CalculateSize( &dc, iconSpacing ); line->SetPosition( x, y, clientWidth, iconSpacing ); - wxSize sizeLine = line->GetSize(); + wxSize sizeLine = GetLineSize(i); if ( maxWidth < sizeLine.x ) maxWidth = sizeLine.x; @@ -3344,13 +3864,31 @@ void wxListMainWindow::CalculatePositions() 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(); + } +} + +void wxListMainWindow::RefreshAll() +{ + m_dirty = FALSE; + Refresh(); + + wxListHeaderWindow *headerWin = GetListCtrl()->m_headerWin; + if ( headerWin && headerWin->m_dirty ) + { + headerWin->m_dirty = FALSE; + headerWin->Refresh(); + } } void wxListMainWindow::UpdateCurrent() { - if ( (m_current == (size_t)-1) && !IsEmpty() ) + if ( !HasCurrent() && !IsEmpty() ) { m_current = 0; } @@ -3393,7 +3931,7 @@ long wxListMainWindow::GetNextItem( long item, if ( (state & wxLIST_STATE_FOCUSED) && (line == m_current) ) return line; - if ( (state & wxLIST_STATE_SELECTED) && IsHilighted(line) ) + if ( (state & wxLIST_STATE_SELECTED) && IsHighlighted(line) ) return line; } @@ -3404,31 +3942,49 @@ long wxListMainWindow::GetNextItem( long item, // deleting stuff // ---------------------------------------------------------------------------- -void wxListMainWindow::DeleteItem( long index ) +void wxListMainWindow::DeleteItem( long lindex ) { size_t count = GetItemCount(); - wxCHECK_RET( (index >= 0) && ((size_t)index < count), + wxCHECK_RET( (lindex >= 0) && ((size_t)lindex < count), _T("invalid item index in DeleteItem") ); - m_dirty = TRUE; + size_t index = (size_t)lindex; - // select the next item when the selected one is deleted - if ( m_current == (size_t)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 - 2 ) + // 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 = count - 2; + m_current--; } } - SendNotify( (size_t)index, wxEVT_COMMAND_LIST_DELETE_ITEM ); + if ( InReportView() ) + { + ResetVisibleLinesRange(); + } - if ( !IsVirtual() ) + if ( IsVirtual() ) + { + m_countVirt--; + + m_selStore.OnItemDelete(index); + } + else { - m_lines.RemoveAt( (size_t)index ); + 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); } void wxListMainWindow::DeleteColumn( int col ) @@ -3441,7 +3997,7 @@ void wxListMainWindow::DeleteColumn( int col ) m_columns.DeleteNode( node ); } -void wxListMainWindow::DeleteAllItems() +void wxListMainWindow::DoDeleteAllItems() { if ( IsEmpty() ) { @@ -3449,8 +4005,7 @@ void wxListMainWindow::DeleteAllItems() return; } - m_dirty = TRUE; - m_current = (size_t)-1; + ResetCurrent(); // to make the deletion of all items faster, we don't send the // notifications for each item deletion in this case but only one event @@ -3461,9 +4016,26 @@ void wxListMainWindow::DeleteAllItems() event.SetEventObject( GetParent() ); GetParent()->GetEventHandler()->ProcessEvent( event ); + if ( IsVirtual() ) + { + m_countVirt = 0; + + m_selStore.Clear(); + } + + if ( InReportView() ) + { + ResetVisibleLinesRange(); + } + m_lines.Clear(); +} + +void wxListMainWindow::DeleteAllItems() +{ + DoDeleteAllItems(); - m_selections.Clear(); + RecalculatePositions(); } void wxListMainWindow::DeleteEverything() @@ -3483,14 +4055,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) ) @@ -3534,15 +4105,23 @@ long wxListMainWindow::HitTest( int x, int y, int &flags ) { CalcUnscrolledPosition( x, y, &x, &y ); - size_t count = GetItemCount(); - for (size_t i = 0; i < count; i++) + if ( HasFlag(wxLC_REPORT) ) { - wxListLineData *line = GetLine(i); - long ret = line->IsHit( x, y ); - if (ret) + size_t current = y / GetLineHeight(); + 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 = (int)ret; - return i; + flags = HitTestLine(current, x, y); + if ( flags ) + return current; } } @@ -3579,11 +4158,14 @@ void wxListMainWindow::InsertItem( wxListItem &item ) wxFAIL_MSG( _T("unknown mode") ); } - wxListLineData *line = new wxListLineData( this, id ); + wxListLineData *line = new wxListLineData(this); line->SetItem( 0, item ); m_lines.Insert( line, id ); + + m_dirty = TRUE; + RefreshLines(id, GetItemCount() - 1); } void wxListMainWindow::InsertColumn( long col, wxListItem &item ) @@ -3639,8 +4221,9 @@ void wxListMainWindow::SortItems( wxListCtrlCompare fn, long data ) void wxListMainWindow::OnScroll(wxScrollWinEvent& event) { - // update our idea of which lines are shown before scrolling the window - UpdateShownLinesRange(); + // update our idea of which lines are shown when we redraw the window the + // next time + ResetVisibleLinesRange(); // FIXME #if defined(__WXGTK__) && !defined(__WXUNIVERSAL__) @@ -3661,29 +4244,53 @@ void wxListMainWindow::OnScroll(wxScrollWinEvent& event) } } -void wxListMainWindow::UpdateShownLinesRange() +int wxListMainWindow::GetCountPerPage() const { - // only optimize redrawing for the report view using m_lineFrom/To - if ( !HasFlag(wxLC_REPORT) ) - return; - - size_t lineFromOld = m_lineFrom, - lineToOld = m_lineTo; + if ( !m_linesPerPage ) + { + wxConstCast(this, wxListMainWindow)-> + m_linesPerPage = GetClientSize().y / GetLineHeight(); + } - m_lineFrom = GetScrollPos(wxVERTICAL); + return m_linesPerPage; +} - size_t count = GetItemCount(); +void wxListMainWindow::GetVisibleLinesRange(size_t *from, size_t *to) +{ + wxASSERT_MSG( HasFlag(wxLC_REPORT), _T("this is for report mode only") ); - wxASSERT_MSG( m_lineFrom < count, _T("invalid scroll position?") ); + if ( m_lineFrom == (size_t)-1 ) + { + size_t count = GetItemCount(); + if ( count ) + { + m_lineFrom = GetScrollPos(wxVERTICAL); - m_lineTo = m_lineFrom + m_linesPerPage - 1; - if ( m_lineTo >= count ) - m_lineTo = count - 1; + // this may happen if SetScrollbars() hadn't been called yet + if ( m_lineFrom >= count ) + m_lineFrom = count - 1; - if ( m_lineFrom != lineFromOld || m_lineTo != lineToOld ) - { - m_dirty = TRUE; + // we redraw one extra line but this is needed to make the redrawing + // logic work when there is a fractional number of lines on screen + m_lineTo = m_lineFrom + m_linesPerPage; + if ( m_lineTo >= count ) + m_lineTo = count - 1; + } + else // empty control + { + m_lineFrom = 0; + m_lineTo = (size_t)-1; + } } + + wxASSERT_MSG( IsEmpty() || + (m_lineFrom <= m_lineTo && m_lineTo < GetItemCount()), + _T("GetVisibleLinesRange() returns incorrect result") ); + + if ( from ) + *from = m_lineFrom; + if ( to ) + *to = m_lineTo; } // ------------------------------------------------------------------------------------- @@ -3784,6 +4391,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) @@ -3806,6 +4414,9 @@ wxListCtrl::wxListCtrl() wxListCtrl::~wxListCtrl() { + if ( m_mainWin ) + m_mainWin->ResetCurrent(); + if (m_ownsImageListNormal) delete m_imageListNormal; if (m_ownsImageListSmall) @@ -3843,7 +4454,7 @@ bool wxListCtrl::Create(wxWindow *parent, m_mainWin = (wxListMainWindow*) NULL; m_headerWin = (wxListHeaderWindow*) NULL; - if ( !(style & (wxLC_REPORT | wxLC_LIST | wxLC_ICON)) ) + if ( !(style & wxLC_MASK_TYPE) ) { style = style | wxLC_LIST; } @@ -3851,16 +4462,16 @@ bool wxListCtrl::Create(wxWindow *parent, if ( !wxControl::Create( parent, id, pos, size, style, validator, name ) ) return FALSE; - if ( style & wxSUNKEN_BORDER ) - style -= wxSUNKEN_BORDER; + // don't create the inner window with the border + style &= ~wxSUNKEN_BORDER; m_mainWin = new wxListMainWindow( this, -1, wxPoint(0,0), size, style ); - if (HasFlag(wxLC_REPORT)) + if ( HasFlag(wxLC_REPORT) ) { CreateHeaderWindow(); - if (HasFlag(wxLC_NO_HEADER)) + if ( HasFlag(wxLC_NO_HEADER) ) { // VZ: why do we create it at all then? m_headerWin->Show( FALSE ); @@ -3870,21 +4481,17 @@ bool wxListCtrl::Create(wxWindow *parent, return TRUE; } -void wxListCtrl::OnSize( wxSizeEvent &WXUNUSED(event) ) -{ - /* handled in OnIdle */ - - if (m_mainWin) m_mainWin->m_dirty = TRUE; -} - void wxListCtrl::SetSingleStyle( long style, bool add ) { + wxASSERT_MSG( !(style & wxLC_VIRTUAL), + _T("wxLC_VIRTUAL can't be [un]set") ); + long flag = GetWindowStyle(); if (add) { if (style & wxLC_MASK_TYPE) - flag &= ~wxLC_MASK_TYPE; + flag &= ~(wxLC_MASK_TYPE | wxLC_VIRTUAL); if (style & wxLC_MASK_ALIGN) flag &= ~wxLC_MASK_ALIGN; if (style & wxLC_MASK_SORT) @@ -3909,36 +4516,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); } } @@ -4321,20 +4927,25 @@ bool wxListCtrl::SortItems( wxListCtrlCompare fn, long data ) } // ---------------------------------------------------------------------------- -// OnIdle() handler: this is the place where we redraw the control +// event handlers // ---------------------------------------------------------------------------- -void wxListCtrl::OnIdle( wxIdleEvent & WXUNUSED(event) ) +void wxListCtrl::OnSize(wxSizeEvent& event) { - // do it only if needed - if ( !m_mainWin->m_dirty ) + if ( !m_mainWin ) return; - int cw = 0; - int ch = 0; + 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 ); @@ -4343,17 +4954,17 @@ void wxListCtrl::OnIdle( wxIdleEvent & WXUNUSED(event) ) { m_mainWin->SetSize( 0, 0, cw, ch ); } +} - m_mainWin->CalculatePositions(); +void wxListCtrl::OnIdle( wxIdleEvent & event ) +{ + event.Skip(); - m_mainWin->m_dirty = FALSE; - m_mainWin->Refresh(); + // do it only if needed + if ( !m_mainWin->m_dirty ) + return; - if ( m_headerWin && m_headerWin->m_dirty ) - { - m_headerWin->m_dirty = FALSE; - m_headerWin->Refresh(); - } + m_mainWin->RecalculatePositions(); } // ---------------------------------------------------------------------------- @@ -4406,9 +5017,6 @@ bool wxListCtrl::SetFont( const wxFont &font ) m_headerWin->SetFont( font ); } - // invalidate it as the font changed - m_mainWin->OnFontChange(); - return TRUE; } @@ -4483,6 +5091,15 @@ int wxListCtrl::OnGetItemImage(long item) const return -1; } +wxListItemAttr *wxListCtrl::OnGetItemAttr(long item) const +{ + wxASSERT_MSG( item >= 0 && item < GetItemCount(), + _T("invalid item index in OnGetItemAttr()") ); + + // no attributes by default + return NULL; +} + void wxListCtrl::SetItemCount(long count) { wxASSERT_MSG( IsVirtual(), _T("this is for virtual controls only") ); @@ -4490,6 +5107,14 @@ void wxListCtrl::SetItemCount(long count) m_mainWin->SetItemCount(count); } -#endif // wxUSE_LISTCTRL +void wxListCtrl::RefreshItem(long item) +{ + m_mainWin->RefreshLine(item); +} -#endif +void wxListCtrl::RefreshItems(long itemFrom, long itemTo) +{ + m_mainWin->RefreshLines(itemFrom, itemTo); +} + +#endif // wxUSE_LISTCTRL