X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/4176fb7d0f7f19abcd1ac40e29e9fe107a9e0f6a..6cc687bfee071b5328f9e48d1708795287006bb7:/src/generic/listctrl.cpp?ds=sidebyside diff --git a/src/generic/listctrl.cpp b/src/generic/listctrl.cpp index 8ea3034af7..3b9727dd86 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" @@ -34,15 +37,22 @@ #if wxUSE_LISTCTRL -#include "wx/dcscreen.h" -#include "wx/app.h" +#ifndef WX_PRECOMP + #include "wx/app.h" + + #include "wx/dynarray.h" + + #include "wx/dcscreen.h" + + #include "wx/textctrl.h" +#endif + +#include "wx/imaglist.h" #include "wx/listctrl.h" -#include "wx/generic/imaglist.h" -#include "wx/dynarray.h" -#ifdef __WXGTK__ -#include -#include "wx/gtk/win_gtk.h" +#if defined(__WXGTK__) + #include + #include "wx/gtk/win_gtk.h" #endif // ---------------------------------------------------------------------------- @@ -62,9 +72,15 @@ 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) +DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_FOCUSED) +DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_CACHE_HINT) // ---------------------------------------------------------------------------- // constants @@ -78,7 +94,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 +111,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_LONG(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 +203,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 +238,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 @@ -175,16 +272,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 ); @@ -206,8 +293,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(); }; //----------------------------------------------------------------------------- @@ -237,17 +334,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 +352,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 +363,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 +376,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 ); - - void ReverseHilight(); + bool Highlight( bool on ); - // draw the line on the given DC - void Draw( wxDC *dc, int y = 0, int height = 0, bool highlighted = FALSE ); + void ReverseHighlight(); - 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 +407,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); @@ -345,7 +435,7 @@ protected: wxCursor *m_resizeCursor; bool m_isDragging; - // column being resized + // column being resized or -1 int m_column; // divider line position in logical (unscrolled) coords @@ -357,7 +447,6 @@ protected: public: wxListHeaderWindow(); - virtual ~wxListHeaderWindow(); wxListHeaderWindow( wxWindow *win, wxWindowID id, @@ -367,6 +456,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); @@ -379,6 +470,11 @@ public: bool m_dirty; private: + // common part of all ctors + void Init(); + + void SendListEvent(wxEventType type, wxPoint pos); + DECLARE_DYNAMIC_CLASS(wxListHeaderWindow) DECLARE_EVENT_TABLE() }; @@ -408,6 +504,7 @@ private: wxString *m_res; wxListMainWindow *m_owner; wxString m_startValue; + bool m_finished; public: wxListTextCtrl() {} @@ -423,7 +520,7 @@ public: void OnKillFocus( wxFocusEvent &event ); private: - DECLARE_DYNAMIC_CLASS(wxListTextCtrl); + DECLARE_DYNAMIC_CLASS(wxListTextCtrl) DECLARE_EVENT_TABLE() }; @@ -453,34 +550,91 @@ 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); } + + // start editing the label of the given item void EditLabel( long item ); + + // suspend/resume redrawing the control + void Freeze(); + void Thaw(); + + void SetFocus(); + 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 +643,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 +664,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 +675,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 +697,9 @@ public: bool IsEmpty() const { return GetItemCount() == 0; } void SetItemCount(long count); + // change the current (== focused) item, send a notification event + void ChangeCurrent(size_t current); + void ResetCurrent() { ChangeCurrent((size_t)-1); } bool HasCurrent() const { return m_current != (size_t)-1; } // send out a wxListEvent @@ -548,8 +707,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 +732,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 +762,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 +785,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,26 +812,35 @@ 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::GetColour(wxSYS_COLOUR_3DLIGHT); +#endif + } private: // initialize the current item if needed void UpdateCurrent(); - // called when an item is [un]focuded, i.e. becomes [not] current - // - // currently unused - void OnFocusLine( size_t line ); - void OnUnfocusLine( size_t line ); + // delete all items but don't refresh: called from dtor + void DoDeleteAllItems(); // the height of one line using the current font wxCoord m_lineHeight; @@ -671,7 +848,20 @@ private: // the total header width or 0 if not calculated yet wxCoord m_headerWidth; - DECLARE_DYNAMIC_CLASS(wxListMainWindow); + // 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; + + // if this is > 0, the control is frozen and doesn't redraw itself + size_t m_freezeCount; + + DECLARE_DYNAMIC_CLASS(wxListMainWindow) DECLARE_EVENT_TABLE() }; @@ -679,10 +869,199 @@ 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; + } + else + { + 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 +1076,7 @@ wxListItemData::wxListItemData(wxListMainWindow *owner) m_owner = owner; - if ( owner->HasFlag(wxLC_REPORT) ) + if ( owner->InReportView() ) { m_rect = NULL; } @@ -807,12 +1186,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; @@ -820,22 +1197,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 ) @@ -854,7 +1242,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; } @@ -865,7 +1253,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 @@ -873,7 +1261,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; @@ -903,7 +1291,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 +1299,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 +1318,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 +1377,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 +1417,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 +1461,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 +1475,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 +1500,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,170 +1567,193 @@ 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::GetColour(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); - // 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); + // font + wxFont font; + if ( attr && attr->HasFont() ) + { + font = attr->GetFont(); + } + else + { + font = listctrl->GetFont(); + } + + 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??") ); + + bool highlighted = IsHighlighted(); + + wxListItemAttr *attr = GetAttr(); - if ( GetMode() == wxLC_REPORT) + 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(); + + int width = m_owner->GetColumnWidth(col++); + int xOld = x; + x += width; - dc->DestroyClippingRegion(); + if ( item->HasImage() ) + { + int ix, iy; + m_owner->DrawImage( item->GetImage(), dc, xOld, y ); + m_owner->GetImageSize( item->GetImage(), ix, iy ); - x = xOld + width; + ix += IMAGE_MARGIN_IN_REPORT_MODE; - node = node->GetNext(); + 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()); } //----------------------------------------------------------------------------- // wxListHeaderWindow //----------------------------------------------------------------------------- -IMPLEMENT_DYNAMIC_CLASS(wxListHeaderWindow,wxWindow); +IMPLEMENT_DYNAMIC_CLASS(wxListHeaderWindow,wxWindow) BEGIN_EVENT_TABLE(wxListHeaderWindow,wxWindow) EVT_PAINT (wxListHeaderWindow::OnPaint) @@ -1424,50 +1761,62 @@ 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( 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() { + 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 ) +{ + 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 ) ); + SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); } -wxListHeaderWindow::~wxListHeaderWindow( void ) +wxListHeaderWindow::~wxListHeaderWindow() { delete m_resizeCursor; } 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; +#if defined(__WXGTK__) && !defined(__WXUNIVERSAL__) + 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); + gtk_paint_box (m_wxwindow->style, GTK_PIZZA(m_wxwindow)->bin_window, + state, GTK_SHADOW_OUT, + (GdkRectangle*) NULL, m_wxwindow, + (char *)"button", // const_cast + 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->SetPen( wxPen( wxSystemSettings::GetColour( 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) @@ -1482,7 +1831,7 @@ void wxListHeaderWindow::DoDrawRect( wxDC *dc, int x, int y, int w, int h ) 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 +#else // !GTK, !Mac const int m_corner = 1; dc->SetBrush( *wxTRANSPARENT_BRUSH ); @@ -1491,7 +1840,7 @@ void wxListHeaderWindow::DoDrawRect( wxDC *dc, int x, int y, int w, int h ) 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( wxSystemSettings::GetSystemColour( wxSYS_COLOUR_BTNSHADOW ), 1, wxSOLID ); + wxPen pen( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNSHADOW ), 1, wxSOLID ); dc->SetPen( pen ); dc->DrawLine( x+w-m_corner, y, x+w-1, y+h ); // right (inner) @@ -1521,7 +1870,7 @@ void wxListHeaderWindow::AdjustDC(wxDC& dc) void wxListHeaderWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) { -#ifdef __WXGTK__ +#if defined(__WXGTK__) wxClientDC dc( this ); #else wxPaintDC dc( this ); @@ -1551,25 +1900,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(); } @@ -1577,9 +1954,9 @@ void wxListHeaderWindow::DrawCurrent() { int x1 = m_currentX; int y1 = 0; - ClientToScreen( &x1, &y1 ); + m_owner->ClientToScreen( &x1, &y1 ); - int x2 = m_currentX-1; + int x2 = m_currentX; int y2 = 0; m_owner->GetClientSize( NULL, &y2 ); m_owner->ClientToScreen( &x2, &y2 ); @@ -1608,6 +1985,9 @@ void wxListHeaderWindow::OnMouse( wxMouseEvent &event ) if (m_isDragging) { + SendListEvent(wxEVT_COMMAND_LIST_COL_DRAGGING, + event.GetPosition()); + // we don't draw the line beyond our window, but we allow dragging it // there int w = 0; @@ -1625,6 +2005,8 @@ void wxListHeaderWindow::OnMouse( wxMouseEvent &event ) m_isDragging = FALSE; m_dirty = TRUE; m_owner->SetColumnWidth( m_column, m_currentX - m_minX ); + SendListEvent(wxEVT_COMMAND_LIST_COL_END_DRAG, + event.GetPosition()); } else { @@ -1647,8 +2029,9 @@ void wxListHeaderWindow::OnMouse( wxMouseEvent &event ) int xpos = 0; // find the column where this event occured - int countCol = m_owner->GetColumnCount(); - for (int col = 0; col < countCol; col++) + int col, + countCol = m_owner->GetColumnCount(); + for (col = 0; col < countCol; col++) { xpos += m_owner->GetColumnWidth( col ); m_column = col; @@ -1669,22 +2052,26 @@ void wxListHeaderWindow::OnMouse( wxMouseEvent &event ) m_minX = xpos; } - if (event.LeftDown()) + if ( col == countCol ) + m_column = -1; + + if (event.LeftDown() || event.RightUp()) { - if (hit_border) + if (hit_border && event.LeftDown()) { m_isDragging = TRUE; m_currentX = x; DrawCurrent(); CaptureMouse(); + SendListEvent(wxEVT_COMMAND_LIST_COL_BEGIN_DRAG, + event.GetPosition()); } - else + else // click on a column { - wxWindow *parent = GetParent(); - wxListEvent le( wxEVT_COMMAND_LIST_COL_CLICK, parent->GetId() ); - le.SetEventObject( parent ); - le.m_col = m_column; - parent->GetEventHandler()->ProcessEvent( le ); + SendListEvent( event.LeftDown() + ? wxEVT_COMMAND_LIST_COL_CLICK + : wxEVT_COMMAND_LIST_COL_RIGHT_CLICK, + event.GetPosition()); } } else if (event.Moving()) @@ -1712,6 +2099,23 @@ void wxListHeaderWindow::OnSetFocus( wxFocusEvent &WXUNUSED(event) ) m_owner->SetFocus(); } +void wxListHeaderWindow::SendListEvent(wxEventType type, wxPoint pos) +{ + wxWindow *parent = GetParent(); + wxListEvent le( type, parent->GetId() ); + le.SetEventObject( parent ); + le.m_pointDrag = pos; + + // 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 ); +} + //----------------------------------------------------------------------------- // wxListRenameTimer (internal) //----------------------------------------------------------------------------- @@ -1730,7 +2134,7 @@ void wxListRenameTimer::Notify() // wxListTextCtrl (internal) //----------------------------------------------------------------------------- -IMPLEMENT_DYNAMIC_CLASS(wxListTextCtrl,wxTextCtrl); +IMPLEMENT_DYNAMIC_CLASS(wxListTextCtrl,wxTextCtrl) BEGIN_EVENT_TABLE(wxListTextCtrl,wxTextCtrl) EVT_CHAR (wxListTextCtrl::OnChar) @@ -1757,6 +2161,7 @@ wxListTextCtrl::wxListTextCtrl( wxWindow *parent, (*m_accept) = FALSE; (*m_res) = ""; m_startValue = value; + m_finished = FALSE; } void wxListTextCtrl::OnChar( wxKeyEvent &event ) @@ -1769,9 +2174,12 @@ void wxListTextCtrl::OnChar( wxKeyEvent &event ) if (!wxPendingDelete.Member(this)) wxPendingDelete.Append(this); - if ((*m_accept) && ((*m_res) != m_startValue)) + if ((*m_res) != m_startValue) m_owner->OnRenameAccept(); + m_finished = TRUE; + m_owner->SetFocus(); + return; } if (event.m_keyCode == WXK_ESCAPE) @@ -1782,6 +2190,9 @@ void wxListTextCtrl::OnChar( wxKeyEvent &event ) if (!wxPendingDelete.Member(this)) wxPendingDelete.Append(this); + m_finished = TRUE; + m_owner->SetFocus(); + return; } @@ -1790,12 +2201,18 @@ void wxListTextCtrl::OnChar( wxKeyEvent &event ) void wxListTextCtrl::OnKeyUp( wxKeyEvent &event ) { + if (m_finished) + { + event.Skip(); + return; + } + // auto-grow the textctrl: wxSize parentSize = m_owner->GetSize(); wxPoint myPos = GetPosition(); wxSize mySize = GetSize(); int sx, sy; - GetTextExtent(GetValue() + _T("MM"), &sx, &sy); // FIXME: MM?? + GetTextExtent(GetValue() + _T("MM"), &sx, &sy); if (myPos.x + sx > parentSize.x) sx = parentSize.x - myPos.x; if (mySize.x > sx) @@ -1805,12 +2222,21 @@ void wxListTextCtrl::OnKeyUp( wxKeyEvent &event ) event.Skip(); } -void wxListTextCtrl::OnKillFocus( wxFocusEvent &WXUNUSED(event) ) +void wxListTextCtrl::OnKillFocus( wxFocusEvent &event ) { + if (m_finished) + { + event.Skip(); + return; + } + if (!wxPendingDelete.Member(this)) wxPendingDelete.Append(this); - if ((*m_accept) && ((*m_res) != m_startValue)) + (*m_accept) = TRUE; + (*m_res) = GetValue(); + + if ((*m_res) != m_startValue) m_owner->OnRenameAccept(); } @@ -1818,11 +2244,10 @@ void wxListTextCtrl::OnKillFocus( wxFocusEvent &WXUNUSED(event) ) // wxListMainWindow //----------------------------------------------------------------------------- -IMPLEMENT_DYNAMIC_CLASS(wxListMainWindow,wxScrolledWindow); +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) @@ -1837,7 +2262,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,6 +2286,8 @@ void wxListMainWindow::Init() m_currentEdit = m_lineLastClicked = m_lineBeforeLastClicked = (size_t)-1; + + m_freezeCount = 0; } void wxListMainWindow::InitScrolling() @@ -1881,7 +2308,8 @@ wxListMainWindow::wxListMainWindow() { Init(); - m_hilightBrush = (wxBrush *) NULL; + m_highlightBrush = + m_highlightUnfocusedBrush = (wxBrush *) NULL; m_xScroll = m_yScroll = 0; @@ -1898,21 +2326,39 @@ wxListMainWindow::wxListMainWindow( wxWindow *parent, { Init(); - m_hilightBrush = new wxBrush( wxSystemSettings::GetSystemColour(wxSYS_COLOUR_HIGHLIGHT), wxSOLID ); + m_highlightBrush = new wxBrush + ( + wxSystemSettings::GetColour + ( + wxSYS_COLOUR_HIGHLIGHT + ), + wxSOLID + ); + + m_highlightUnfocusedBrush = new wxBrush + ( + wxSystemSettings::GetColour + ( + wxSYS_COLOUR_BTNSHADOW + ), + wxSOLID + ); + wxSize sz = size; sz.y = 25; InitScrolling(); SetScrollbars( m_xScroll, m_yScroll, 0, 0, 0, 0 ); - SetBackgroundColour( wxSystemSettings::GetSystemColour( wxSYS_COLOUR_LISTBOX ) ); + SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_LISTBOX ) ); } wxListMainWindow::~wxListMainWindow() { - DeleteEverything(); + DoDeleteAllItems(); - delete m_hilightBrush; + delete m_highlightBrush; + delete m_highlightUnfocusedBrush; delete m_renameTimer; } @@ -1921,7 +2367,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++ ) @@ -1929,11 +2375,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") ); @@ -1944,15 +2390,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") ); @@ -1985,59 +2433,153 @@ 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 +{ + wxASSERT_MSG( line < GetItemCount(), _T("invalid line in HitTestLine") ); + + wxListLineData *ld = GetLine(line); + + if ( ld->HasImage() && GetLineIconRect(line).Inside(x, y) ) + return wxLIST_HITTEST_ONITEMICON; + + // VS: Testing for "ld->HasText() || InReportView()" instead of + // "ld->HasText()" is needed to make empty lines in report view + // possible + if ( ld->HasText() || InReportView() ) + { + 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; @@ -2045,7 +2587,16 @@ 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 ); @@ -2056,18 +2607,23 @@ 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) ) { - if ( lineFrom < m_lineFrom ) - lineFrom = m_lineFrom; - if ( lineTo > m_lineTo ) - lineTo = m_lineTo; + 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; + rect.height = GetLineY(lineTo) - rect.y + GetLineHeight(); CalcScrolledPosition( rect.x, rect.y, &rect.x, &rect.y ); RefreshRect( rect ); @@ -2082,21 +2638,96 @@ void wxListMainWindow::RefreshLines( size_t lineFrom, size_t lineTo ) } } +void wxListMainWindow::RefreshAfter( size_t lineFrom ) +{ + if ( HasFlag(wxLC_REPORT) ) + { + size_t visibleFrom; + GetVisibleLinesRange(&visibleFrom, NULL); + + if ( lineFrom < visibleFrom ) + lineFrom = visibleFrom; + + wxRect rect; + rect.x = 0; + rect.y = GetLineY(lineFrom); + + 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: 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); + } + } +} + +void wxListMainWindow::Freeze() +{ + m_freezeCount++; +} + +void wxListMainWindow::Thaw() +{ + wxCHECK_RET( m_freezeCount > 0, _T("thawing unfrozen list control?") ); + + if ( !--m_freezeCount ) + { + Refresh(); + } +} + void wxListMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) { // Note: a wxPaintDC must be constructed even if no drawing is // done (a Windows requirement). wxPaintDC dc( this ); - if ( m_dirty ) + if ( IsEmpty() || m_freezeCount ) { - // postpone redrawing until the next OnIdle() call to minimize flicker + // nothing to draw or not the moment to draw it return; } - if ( IsEmpty() ) + if ( m_dirty ) { - // empty control. nothing to draw + // delay the repainting until we calculate all the items positions return; } @@ -2111,53 +2742,70 @@ void wxListMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) if ( HasFlag(wxLC_REPORT) ) { - int lineSpacing = GetLineHeight(); + int lineHeight = GetLineHeight(); + + size_t visibleFrom, visibleTo; + GetVisibleLinesRange(&visibleFrom, &visibleTo); + + wxRect rectLine; + wxCoord xOrig, yOrig; + CalcUnscrolledPosition(0, 0, &xOrig, &yOrig); - for ( size_t line = m_lineFrom; line <= m_lineTo; line++ ) + // 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) ) { -#ifdef __WXMAC__ - wxPen pen(wxWHITE, 1, wxSOLID); -#else - wxPen pen(wxSystemSettings:: - GetSystemColour(wxSYS_COLOUR_3DLIGHT), 1, wxSOLID); -#endif + 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() ) { -#ifdef __WXMAC__ - wxPen pen(wxWHITE, 1, wxSOLID); -#else - wxPen pen(wxSystemSettings:: - GetSystemColour(wxSYS_COLOUR_3DLIGHT), 1, wxSOLID); -#endif + wxPen pen(GetRuleColour(), 1, wxSOLID); int col = 0; wxRect firstItemRect; @@ -2185,56 +2833,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) ); } -#ifdef __WXMAC__ - // no rect outline, we already have the background color -#else - dc.SetPen( *wxBLACK_PEN ); - dc.SetBrush( *wxTRANSPARENT_BRUSH ); - dc.DrawRectangle( rect ); -#endif } 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); } } @@ -2250,18 +2884,28 @@ 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) ) + { + if ( line != (size_t)-1 ) + { + GetLine(line)->GetItem( 0, le.m_item ); + } + //else: this happens for wxEVT_COMMAND_LIST_ITEM_FOCUSED event + } + //else: there may be no more such item + GetParent()->GetEventHandler()->ProcessEvent( le ); } -void wxListMainWindow::OnFocusLine( size_t WXUNUSED(line) ) +void wxListMainWindow::ChangeCurrent(size_t current) { -// SendNotify( line, wxEVT_COMMAND_LIST_ITEM_FOCUSSED ); -} + m_current = current; -void wxListMainWindow::OnUnfocusLine( size_t WXUNUSED(line) ) -{ -// SendNotify( line, wxEVT_COMMAND_LIST_ITEM_UNFOCUSSED ); + SendNotify(current, wxEVT_COMMAND_LIST_ITEM_FOCUSED); } void wxListMainWindow::EditLabel( long item ) @@ -2287,14 +2931,10 @@ void wxListMainWindow::EditLabel( long item ) if (m_dirty) wxSafeYield(); - wxClientDC dc(this); - 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 ); + CalcScrolledPosition(rectLabel.x, rectLabel.y, &rectLabel.x, &rectLabel.y); wxListTextCtrl *text = new wxListTextCtrl ( @@ -2359,7 +2999,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(), @@ -2367,10 +3007,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 { @@ -2378,10 +3017,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; } } @@ -2389,7 +3026,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++; @@ -2423,7 +3065,15 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) m_renameTimer->Stop(); m_lastOnSame = FALSE; +#ifdef __WXGTK__ + // FIXME: wxGTK generates bad sequence of events prior to doubleclick + // ("down, up, down, double, up" while other ports + // do "down, up, double, up"). We have to have this hack + // in place till somebody fixes wxGTK... if ( current == m_lineBeforeLastClicked ) +#else + if ( current == m_lineLastClicked ) +#endif { SendNotify( current, wxEVT_COMMAND_LIST_ITEM_ACTIVATED ); @@ -2463,25 +3113,25 @@ 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()) ) { - m_current = current; - HilightAll( FALSE ); + HighlightAll( FALSE ); + + ChangeCurrent(current); - ReverseHilight(m_current); + ReverseHighlight(m_current); } - else // multi sel + else // multi sel & either ctrl or shift is down { if (event.ControlDown()) { - m_current = current; + ChangeCurrent(current); - ReverseHilight(m_current); + ReverseHighlight(m_current); } else if (event.ShiftDown()) { - m_current = current; + ChangeCurrent(current); size_t lineFrom = oldCurrent, lineTo = current; @@ -2492,17 +3142,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 { @@ -2514,8 +3154,6 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) if (m_current != oldCurrent) { RefreshLine( oldCurrent ); - OnUnfocusLine( oldCurrent ); - OnFocusLine( m_current ); } // forceClick is only set if the previous click was on another item @@ -2523,13 +3161,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 ); @@ -2539,14 +3176,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 ); @@ -2568,9 +3207,9 @@ 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; + ChangeCurrent(newCurrent); // select all the items between the old and the new one if ( oldCurrent > newCurrent ) @@ -2579,33 +3218,25 @@ 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 { - m_current = newCurrent; + // all previously selected items are unselected unless ctrl is held + if ( !event.ControlDown() ) + HighlightAll(FALSE); - HilightLine( oldCurrent, FALSE ); + ChangeCurrent(newCurrent); + + HighlightLine( oldCurrent, FALSE ); RefreshLine( oldCurrent ); if ( !event.ControlDown() ) { - HilightLine( m_current, TRUE ); + HighlightLine( m_current, TRUE ); } } - OnUnfocusLine( oldCurrent ); - OnFocusLine( m_current ); RefreshLine( m_current ); MoveToFocus(); @@ -2664,7 +3295,8 @@ void wxListMainWindow::OnChar( wxKeyEvent &event ) nevent.SetDirection( !event.ShiftDown() ); nevent.SetEventObject( GetParent()->GetParent() ); nevent.SetCurrentFocus( m_parent ); - if (GetParent()->GetParent()->GetEventHandler()->ProcessEvent( nevent )) return; + if (GetParent()->GetParent()->GetEventHandler()->ProcessEvent( nevent )) + return; } /* no item -> nothing to do */ @@ -2762,31 +3394,24 @@ void wxListMainWindow::OnChar( wxKeyEvent &event ) break; case WXK_SPACE: - if ( HasFlag(wxLC_SINGLE_SEL) ) - { - wxListEvent le( wxEVT_COMMAND_LIST_ITEM_ACTIVATED, - GetParent()->GetId() ); - le.SetEventObject( GetParent() ); - le.m_itemIndex = m_current; - GetLine(m_current)->GetItem( 0, le.m_item ); - GetParent()->GetEventHandler()->ProcessEvent( le ); - } - else + if ( IsSingleSel() ) { - ReverseHilight(m_current); + SendNotify( m_current, wxEVT_COMMAND_LIST_ITEM_ACTIVATED ); + + if ( IsHighlighted(m_current) ) + { + // don't unselect the item in single selection mode + break; + } + //else: select it in ReverseHighlight() below if unselected } + + ReverseHighlight(m_current); break; case WXK_RETURN: case WXK_EXECUTE: - { - wxListEvent le( wxEVT_COMMAND_LIST_ITEM_ACTIVATED, - GetParent()->GetId() ); - le.SetEventObject( GetParent() ); - le.m_itemIndex = m_current; - GetLine(m_current)->GetItem( 0, le.m_item ); - GetParent()->GetEventHandler()->ProcessEvent( le ); - } + SendNotify( m_current, wxEVT_COMMAND_LIST_ITEM_ACTIVATED ); break; default: @@ -2798,24 +3423,42 @@ void wxListMainWindow::OnChar( wxKeyEvent &event ) // focus handling // ---------------------------------------------------------------------------- -#ifdef __WXGTK__ -extern wxWindow *g_focusWindow; -#endif +void wxListMainWindow::SetFocus() +{ + // VS: wxListMainWindow derives from wxPanel (via wxScrolledWindow) and wxPanel + // overrides SetFocus in such way that it does never change focus from + // panel's child to the panel itself. Unfortunately, we must be able to change + // focus to the panel from wxListTextCtrl because the text control should + // disappear when the user clicks outside it. + + wxWindow *oldFocus = FindFocus(); + + if ( oldFocus && oldFocus->GetParent() == this ) + { + wxWindow::SetFocus(); + } + else + { + wxScrolledWindow::SetFocus(); + } +} 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__ - g_focusWindow = GetParent(); -#endif - wxFocusEvent event( wxEVT_SET_FOCUS, GetParent()->GetId() ); event.SetEventObject( GetParent() ); GetParent()->GetEventHandler()->ProcessEvent( event ); @@ -2825,8 +3468,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 ) @@ -2849,7 +3491,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 ) { @@ -2874,9 +3516,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; @@ -2963,6 +3605,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?") ); @@ -3071,19 +3716,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 ); } } @@ -3093,8 +3740,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 ) @@ -3102,13 +3750,15 @@ void wxListMainWindow::SetItemState( long litem, long state, long stateMask ) // don't do anything if this item is already focused if ( item != m_current ) { - OnUnfocusLine( m_current ); - m_current = item; - OnFocusLine( m_current ); + ChangeCurrent(item); - if ( HasFlag(wxLC_SINGLE_SEL) && (oldCurrent != (size_t)-1) ) + if ( oldCurrent != (size_t)-1 ) { - HilightLine(oldCurrent, FALSE); + if ( IsSingleSel() ) + { + HighlightLine(oldCurrent, FALSE); + } + RefreshLine(oldCurrent); } @@ -3120,17 +3770,19 @@ void wxListMainWindow::SetItemState( long litem, long state, long stateMask ) // don't do anything if this item is not focused if ( item == m_current ) { - OnUnfocusLine( m_current ); - m_current = (size_t)-1; + ResetCurrent(); + + 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 ) { @@ -3138,13 +3790,11 @@ void wxListMainWindow::SetItemState( long litem, long state, long stateMask ) // single sel mode if ( m_current != item ) { - OnUnfocusLine( m_current ); - m_current = item; - OnFocusLine( m_current ); + ChangeCurrent(item); if ( oldCurrent != (size_t)-1 ) { - HilightLine( oldCurrent, FALSE ); + HighlightLine( oldCurrent, FALSE ); RefreshLine( oldCurrent ); } } @@ -3157,7 +3807,7 @@ void wxListMainWindow::SetItemState( long litem, long state, long stateMask ) } } - if ( HilightLine(item, on) ) + if ( HighlightLine(item, on) ) { RefreshLine(item); } @@ -3179,7 +3829,7 @@ int wxListMainWindow::GetItemState( long item, long stateMask ) if ( stateMask & wxLIST_STATE_SELECTED ) { - if ( IsHilighted(item) ) + if ( IsHighlighted(item) ) ret |= wxLIST_STATE_SELECTED; } @@ -3206,22 +3856,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... @@ -3229,7 +3883,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++; } @@ -3245,7 +3899,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); } @@ -3264,17 +3919,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() ); @@ -3286,22 +3932,40 @@ void wxListMainWindow::CalculatePositions() else iconSpacing = 0; + // Note that we do not call GetClientSize() here but + // GetSize() and substract the border size for sunken + // borders manually. This is technically incorrect, + // but we need to know the client area's size WITHOUT + // scrollbars here. Since we don't know if there are + // any scrollbars, we use GetSize() instead. Another + // solution would be to call SetScrollbars() here to + // remove the scrollbars and call GetClientSize() then, + // but this might result in flicker and - worse - will + // reset the scrollbars to 0 which is not good at all + // if you resize a dialog/window, but don't want to + // reset the window scrolling. RR. + // Furthermore, we actually do NOT subtract the border + // width as 2 pixels is just the extra space which we + // need around the actual content in the window. Other- + // wise the text would e.g. touch the upper border. RR. int clientWidth, clientHeight; - GetClientSize( &clientWidth, &clientHeight ); + GetSize( &clientWidth, &clientHeight ); 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 / lineSpacing; + m_linesPerPage = clientHeight / lineHeight; + + ResetVisibleLinesRange(); SetScrollbars( m_xScroll, m_yScroll, (GetHeaderWidth() + m_xScroll - 1)/m_xScroll, @@ -3309,8 +3973,6 @@ void wxListMainWindow::CalculatePositions() GetScrollPos(wxHORIZONTAL), GetScrollPos(wxVERTICAL), TRUE ); - - UpdateShownLinesRange(); } else // !report { @@ -3318,13 +3980,26 @@ void wxListMainWindow::CalculatePositions() // fit into the window, we recalculate after subtracting an // approximated 15 pt for the horizontal scrollbar - clientHeight -= 4; // sunken frame - - int entireWidth; + int entireWidth = 0; for (int tries = 0; tries < 2; tries++) { - entireWidth = 0; + // We start with 4 for the border around all items + entireWidth = 4; + + if (tries == 1) + { + // Now we have decided that the items do not fit into the + // client area. Unfortunately, wxWindows sometimes thinks + // that it does fit and therefore NO horizontal scrollbar + // is inserted. This looks ugly, so we fudge here and make + // the calculated width bigger than was actually has been + // calculated. This ensures that wxScrolledWindows puts + // a scrollbar at the bottom of its client area. + entireWidth += SCROLL_UNIT_X; + } + + // Start at 2,2 so the text does not touch the border int x = 2; int y = 2; int maxWidth = 0; @@ -3337,9 +4012,9 @@ void wxListMainWindow::CalculatePositions() currentlyVisibleLines++; wxListLineData *line = GetLine(i); line->CalculateSize( &dc, iconSpacing ); - line->SetPosition( x, y, clientWidth, iconSpacing ); + line->SetPosition( x, y, clientWidth, iconSpacing ); // Why clientWidth? (FIXME) - wxSize sizeLine = line->GetSize(); + wxSize sizeLine = GetLineSize(i); if ( maxWidth < sizeLine.x ) maxWidth = sizeLine.x; @@ -3348,8 +4023,8 @@ void wxListMainWindow::CalculatePositions() if (currentlyVisibleLines > m_linesPerPage) m_linesPerPage = currentlyVisibleLines; - // assume that the size of the next one is the same... (FIXME) - if ( y + sizeLine.y - 6 >= clientHeight ) + // Assume that the size of the next one is the same... (FIXME) + if ( y + sizeLine.y >= clientHeight ) { currentlyVisibleLines = 0; y = 2; @@ -3357,17 +4032,21 @@ void wxListMainWindow::CalculatePositions() entireWidth += maxWidth+6; maxWidth = 0; } + + // We have reached the last item. if ( i == count - 1 ) entireWidth += maxWidth; - if ((tries == 0) && (entireWidth > clientWidth)) + + if ( (tries == 0) && (entireWidth+SCROLL_UNIT_X > clientWidth) ) { - clientHeight -= 15; // scrollbar height + clientHeight -= 15; // We guess the scrollbar height. (FIXME) m_linesPerPage = 0; currentlyVisibleLines = 0; break; } + if ( i == count - 1 ) - tries = 1; // everything fits, no second try required + tries = 1; // Everything fits, no second try required. } } @@ -3375,20 +4054,33 @@ 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::UpdateCurrent() +void wxListMainWindow::RefreshAll() { - if ( (m_current == (size_t)-1) && !IsEmpty() ) + m_dirty = FALSE; + Refresh(); + + wxListHeaderWindow *headerWin = GetListCtrl()->m_headerWin; + if ( headerWin && headerWin->m_dirty ) { - m_current = 0; + headerWin->m_dirty = FALSE; + headerWin->Refresh(); } +} - if ( m_current != (size_t)-1 ) +void wxListMainWindow::UpdateCurrent() +{ + if ( !HasCurrent() && !IsEmpty() ) { - OnFocusLine( m_current ); + ChangeCurrent(0); } } @@ -3424,7 +4116,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; } @@ -3435,31 +4127,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 ) @@ -3470,9 +4180,12 @@ void wxListMainWindow::DeleteColumn( int col ) m_dirty = TRUE; m_columns.DeleteNode( node ); + + // invalidate it as it has to be recalculated + m_headerWidth = 0; } -void wxListMainWindow::DeleteAllItems() +void wxListMainWindow::DoDeleteAllItems() { if ( IsEmpty() ) { @@ -3480,8 +4193,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 @@ -3492,9 +4204,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(); +} - m_selections.Clear(); +void wxListMainWindow::DeleteAllItems() +{ + DoDeleteAllItems(); + + RecalculatePositions(); } void wxListMainWindow::DeleteEverything() @@ -3514,14 +4243,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) ) @@ -3566,14 +4294,26 @@ 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(); + if ( current < count ) { - flags = (int)ret; - return i; + 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!! + for ( size_t current = 0; current < count; current++ ) + { + flags = HitTestLine(current, x, y); + if ( flags ) + return current; } } @@ -3610,11 +4350,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 ) @@ -3634,6 +4377,9 @@ void wxListMainWindow::InsertColumn( long col, wxListItem &item ) { m_columns.Append( column ); } + + // invalidate it as it has to be recalculated + m_headerWidth = 0; } } @@ -3670,8 +4416,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__) @@ -3685,36 +4432,58 @@ void wxListMainWindow::OnScroll(wxScrollWinEvent& event) wxListCtrl* lc = GetListCtrl(); wxCHECK_RET( lc, _T("no listctrl window?") ); - lc->m_headerWin->Refresh() ; -#ifdef __WXMAC__ - lc->m_headerWin->MacUpdateImmediately() ; -#endif + lc->m_headerWin->Refresh(); + lc->m_headerWin->Update(); } } -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; } // ------------------------------------------------------------------------------------- @@ -3725,17 +4494,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() @@ -3745,11 +4506,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(); } @@ -3763,58 +4524,14 @@ void wxListItem::ClearAttributes() } } -// ------------------------------------------------------------------------------------- -// wxListEvent -// ------------------------------------------------------------------------------------- - -IMPLEMENT_DYNAMIC_CLASS(wxListEvent, wxNotifyEvent) - -wxListEvent::wxListEvent( wxEventType commandType, int id ) - : wxNotifyEvent( commandType, id ) -{ - m_code = 0; - m_itemIndex = 0; - m_oldItemIndex = 0; - m_col = 0; - m_cancelled = FALSE; - m_pointDrag.x = 0; - m_pointDrag.y = 0; -} - -void wxListEvent::CopyObject(wxObject& object_dest) const -{ - wxListEvent *obj = (wxListEvent *)&object_dest; - - wxNotifyEvent::CopyObject(object_dest); - - obj->m_code = m_code; - obj->m_itemIndex = m_itemIndex; - obj->m_oldItemIndex = m_oldItemIndex; - obj->m_col = m_col; - obj->m_cancelled = m_cancelled; - obj->m_pointDrag = m_pointDrag; - obj->m_item.m_mask = m_item.m_mask; - obj->m_item.m_itemId = m_item.m_itemId; - obj->m_item.m_col = m_item.m_col; - obj->m_item.m_state = m_item.m_state; - obj->m_item.m_stateMask = m_item.m_stateMask; - obj->m_item.m_text = m_item.m_text; - obj->m_item.m_image = m_item.m_image; - obj->m_item.m_data = m_item.m_data; - obj->m_item.m_format = m_item.m_format; - obj->m_item.m_width = m_item.m_width; - - if ( m_item.HasAttributes() ) - { - obj->m_item.SetTextColour(m_item.GetTextColour()); - } -} - // ------------------------------------------------------------------------------------- // wxListCtrl // ------------------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS(wxListCtrl, wxControl) +IMPLEMENT_DYNAMIC_CLASS(wxListView, wxListCtrl) + +IMPLEMENT_DYNAMIC_CLASS(wxListEvent, wxNotifyEvent) BEGIN_EVENT_TABLE(wxListCtrl,wxControl) EVT_SIZE(wxListCtrl::OnSize) @@ -3874,7 +4591,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; } @@ -3882,16 +4599,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 ); @@ -3901,21 +4618,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) @@ -3940,36 +4653,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); } } @@ -4223,7 +4935,7 @@ bool wxListCtrl::DeleteAllColumns() { size_t count = m_mainWin->m_columns.GetCount(); for ( size_t n = 0; n < count; n++ ) - DeleteColumn(n); + DeleteColumn(0); return TRUE; } @@ -4352,20 +5064,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 ); @@ -4374,17 +5091,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(); } // ---------------------------------------------------------------------------- @@ -4437,9 +5154,6 @@ bool wxListCtrl::SetFont( const wxFont &font ) m_headerWin->SetFont( font ); } - // invalidate it as the font changed - m_mainWin->OnFontChange(); - return TRUE; } @@ -4514,6 +5228,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") ); @@ -4521,6 +5244,24 @@ 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); +} + +void wxListCtrl::Freeze() +{ + m_mainWin->Freeze(); +} + +void wxListCtrl::Thaw() +{ + m_mainWin->Thaw(); +} + +#endif // wxUSE_LISTCTRL