///////////////////////////////////////////////////////////////////////////// // Name: generic/listctrl.cpp // Purpose: generic implementation of wxListCtrl // Author: Robert Roebling // Vadim Zeitlin (virtual list control support) // Id: $Id$ // Copyright: (c) 1998 Robert Roebling // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// /* FIXME for virtual list controls +1. clicking on the item with a mouse is awfully slow, what is going on? note that selecting with keyboard seems to be much faster => fixed HighlightAll() - iterating over 1000000 items *is* slow 2. background colour is wrong? */ /* TODO for better virtual list control support: 1. less dumb line caching, we should cache at least all those visible in the control itself and probably twice as many (we might also need to cache the first one always for geometry calculations?) +2. storing selections: we can't use an array to store the selected indices like right now as selecting all in a control with 1000000 items is not doable like this - instead, store selections as collection of individual items and ranges => wxSelectionStore 3. we need to implement searching/sorting somehow 4. the idea of storing the line index in the line itself is really stupid, we shouldn't need it - but for this we have to get rid of all calles to wxListLineData::GetFoo() and replace them with something like if ( IsVirtual() ... we have it ourselves ... else line->GetFoo(); => done 5. attributes support: we need OnGetItemAttr() as well! */ // ============================================================================ // declarations // ============================================================================ // ---------------------------------------------------------------------------- // headers // ---------------------------------------------------------------------------- #ifdef __GNUG__ #pragma implementation "listctrl.h" #pragma implementation "listctrlbase.h" #endif // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #if wxUSE_LISTCTRL #include "wx/dcscreen.h" #include "wx/app.h" #include "wx/listctrl.h" #include "wx/imaglist.h" #include "wx/dynarray.h" #ifdef __WXGTK__ #include #include "wx/gtk/win_gtk.h" #endif // ---------------------------------------------------------------------------- // events // ---------------------------------------------------------------------------- DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_BEGIN_DRAG) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_BEGIN_RDRAG) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_BEGIN_LABEL_EDIT) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_END_LABEL_EDIT) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_DELETE_ITEM) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_DELETE_ALL_ITEMS) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_GET_INFO) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_SET_INFO) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_SELECTED) 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_ITEM_RIGHT_CLICK) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_MIDDLE_CLICK) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_ACTIVATED) // ---------------------------------------------------------------------------- // constants // ---------------------------------------------------------------------------- // the height of the header window (FIXME: should depend on its font!) static const int HEADER_HEIGHT = 23; // the scrollbar units 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 = 0; // extra margins around the text label static const int EXTRA_WIDTH = 3; static const int EXTRA_HEIGHT = 4; // offset for the header window static const int HEADER_OFFSET_X = 1; static const int HEADER_OFFSET_Y = 1; // when autosizing the columns, add some slack static const int AUTOSIZE_COL_MARGIN = 10; // default and minimal widths for the header columns static const int WIDTH_COL_DEFAULT = 80; static const int WIDTH_COL_MIN = 10; // ============================================================================ // private classes // ============================================================================ // ---------------------------------------------------------------------------- // wxSelectionStore // ---------------------------------------------------------------------------- int CMPFUNC_CONV wxSizeTCmpFn(size_t n1, size_t n2) { return n1 - n2; } WX_DEFINE_SORTED_EXPORTED_ARRAY(size_t, wxIndexArray); // this class is used to store the selected items in the virtual list control // (but it is not tied to list control and so can be used with other controls // such as wxListBox in wxUniv) // // the idea is to make it really smart later (i.e. store the selections as an // array of ranes + individual items) but, as I don't have time to do it now // (this would require writing code to merge/break ranges and much more) keep // it simple but define a clean interface to it which allows it to be made // smarter later class WXDLLEXPORT wxSelectionStore { public: wxSelectionStore() : m_itemsSel(wxSizeTCmpFn) { Init(); } // set the total number of items we handle void SetItemCount(size_t count) { m_count = count; } // special case of SetItemCount(0) void Clear() { m_itemsSel.Clear(); m_count = 0; } // must be called when a new item is inserted/added void OnItemAdd(size_t item) { wxFAIL_MSG( _T("TODO") ); } // must be called when an item is deleted void OnItemDelete(size_t item); // select one item, use SelectRange() insted if possible! // // returns true if the items selection really changed bool SelectItem(size_t item, bool select = TRUE); // select the range of items void SelectRange(size_t itemFrom, size_t itemTo, bool select = TRUE); // 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) //----------------------------------------------------------------------------- class WXDLLEXPORT wxListItemData { public: wxListItemData(wxListMainWindow *owner); ~wxListItemData() { delete m_attr; delete m_rect; } void SetItem( const wxListItem &info ); void SetImage( int image ) { m_image = image; } void SetData( long data ) { m_data = data; } void SetPosition( int x, int y ); void SetSize( int width, int height ); bool HasText() const { return !m_text.empty(); } const wxString& GetText() const { return m_text; } void SetText(const wxString& text) { m_text = text; } // we can't use empty string for measuring the string width/height, so // always return something wxString GetTextForMeasuring() const { wxString s = GetText(); if ( s.empty() ) s = _T('H'); return s; } bool IsHit( int x, int y ) const; int GetX() const; int GetY() const; int GetWidth() const; int GetHeight() const; int GetImage() const { return m_image; } bool HasImage() const { return GetImage() != -1; } void GetItem( wxListItem &info ) const; wxListItemAttr *GetAttributes() const { return m_attr; } public: // the item image or -1 int m_image; // user data associated with the item long m_data; // the item coordinates are not used in report mode, instead this pointer // is NULL and the owner window is used to retrieve the item position and // size wxRect *m_rect; // the list ctrl we are in wxListMainWindow *m_owner; // custom attributes or NULL wxListItemAttr *m_attr; protected: // common part of all ctors void Init(); wxString m_text; }; //----------------------------------------------------------------------------- // wxListHeaderData (internal) //----------------------------------------------------------------------------- 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 ); void SetItem( const wxListItem &item ); void SetPosition( int x, int y ); void SetWidth( int w ); void SetFormat( int format ); void SetHeight( int h ); bool HasImage() const; bool HasText() const { return !m_text.empty(); } const wxString& GetText() const { return m_text; } void SetText(const wxString& text) { m_text = text; } void GetItem( wxListItem &item ); bool IsHit( int x, int y ) const; int GetImage() const; int GetWidth() const; int GetFormat() const; private: DECLARE_DYNAMIC_CLASS(wxListHeaderData); }; //----------------------------------------------------------------------------- // wxListLineData (internal) //----------------------------------------------------------------------------- WX_DECLARE_LIST(wxListItemData, wxListItemDataList); #include "wx/listimpl.cpp" WX_DEFINE_LIST(wxListItemDataList); class WXDLLEXPORT wxListLineData { public: // the list of subitems: only may have more than one item in report mode wxListItemDataList m_items; // this is not used in report view struct GeometryInfo { // total item rect wxRect m_rectAll; // label only wxRect m_rectLabel; // icon only wxRect m_rectIcon; // the part to be highlighted wxRect m_rectHighlight; } *m_gi; // is this item selected? [NB: not used in virtual mode] bool m_highlighted; // back pointer to the list ctrl wxListMainWindow *m_owner; public: wxListLineData(wxListMainWindow *owner); ~wxListLineData() { delete m_gi; } // are we in report mode? inline bool InReportView() const; // are we in virtual report mode? inline bool IsVirtual() const; // these 2 methods shouldn't be called for report view controls, in that // case we determine our position/size ourselves // calculate the size of the line void CalculateSize( wxDC *dc, int spacing ); // remember the position this line appears at void SetPosition( int x, int y, int window_width, int spacing ); // 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 ); wxString GetText(int index) const; void SetText( int index, const wxString s ); // return true if the highlighting really changed bool Highlight( bool on ); void ReverseHighlight(); bool IsHighlighted() const { wxASSERT_MSG( !IsVirtual(), _T("unexpected call to IsHighlighted") ); return m_highlighted; } // 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) void InitItems( int num ); // get the mode (i.e. style) of the list control inline int GetMode() const; void SetAttributes(wxDC *dc, const wxListItemAttr *attr, const wxColour& colText, const wxFont& font, bool highlight); // these are only used by GetImage/SetImage above, we don't support images // 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); //----------------------------------------------------------------------------- // wxListHeaderWindow (internal) //----------------------------------------------------------------------------- class WXDLLEXPORT wxListHeaderWindow : public wxWindow { protected: wxListMainWindow *m_owner; wxCursor *m_currentCursor; wxCursor *m_resizeCursor; bool m_isDragging; // column being resized int m_column; // divider line position in logical (unscrolled) coords int m_currentX; // minimal position beyond which the divider line can't be dragged in // logical coords int m_minX; public: wxListHeaderWindow(); virtual ~wxListHeaderWindow(); wxListHeaderWindow( wxWindow *win, wxWindowID id, wxListMainWindow *owner, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = 0, const wxString &name = "wxlistctrlcolumntitles" ); void DoDrawRect( wxDC *dc, int x, int y, int w, int h ); void DrawCurrent(); void AdjustDC(wxDC& dc); void OnPaint( wxPaintEvent &event ); void OnMouse( wxMouseEvent &event ); void OnSetFocus( wxFocusEvent &event ); // needs refresh bool m_dirty; private: DECLARE_DYNAMIC_CLASS(wxListHeaderWindow) DECLARE_EVENT_TABLE() }; //----------------------------------------------------------------------------- // wxListRenameTimer (internal) //----------------------------------------------------------------------------- class WXDLLEXPORT wxListRenameTimer: public wxTimer { private: wxListMainWindow *m_owner; public: wxListRenameTimer( wxListMainWindow *owner ); void Notify(); }; //----------------------------------------------------------------------------- // wxListTextCtrl (internal) //----------------------------------------------------------------------------- class WXDLLEXPORT wxListTextCtrl: public wxTextCtrl { private: bool *m_accept; wxString *m_res; wxListMainWindow *m_owner; wxString m_startValue; public: wxListTextCtrl() {} wxListTextCtrl( wxWindow *parent, const wxWindowID id, bool *accept, wxString *res, wxListMainWindow *owner, const wxString &value = "", const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, int style = 0, const wxValidator& validator = wxDefaultValidator, const wxString &name = "listctrltextctrl" ); void OnChar( wxKeyEvent &event ); void OnKeyUp( wxKeyEvent &event ); void OnKillFocus( wxFocusEvent &event ); private: DECLARE_DYNAMIC_CLASS(wxListTextCtrl); DECLARE_EVENT_TABLE() }; //----------------------------------------------------------------------------- // wxListMainWindow (internal) //----------------------------------------------------------------------------- WX_DECLARE_LIST(wxListHeaderData, wxListHeaderDataList); #include "wx/listimpl.cpp" WX_DEFINE_LIST(wxListHeaderDataList); class WXDLLEXPORT wxListMainWindow : public wxScrolledWindow { public: wxListMainWindow(); wxListMainWindow( wxWindow *parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0, const wxString &name = _T("listctrlmainwindow") ); virtual ~wxListMainWindow(); bool HasFlag(int flag) const { return m_parent->HasFlag(flag); } // 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 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 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 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 ); // 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; void EditLabel( long item ); void OnRenameTimer(); void OnRenameAccept(); void OnMouse( wxMouseEvent &event ); void MoveToFocus(); // called to switch the selection from the current item to newCurrent, void OnArrowChar( size_t newCurrent, const wxKeyEvent& event ); void OnChar( wxKeyEvent &event ); void OnKeyDown( wxKeyEvent &event ); void OnSetFocus( wxFocusEvent &event ); void OnKillFocus( wxFocusEvent &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 ) const; int GetTextLength( const wxString &s ) const; void SetImageList( wxImageList *imageList, int which ); void SetItemSpacing( int spacing, bool isSmall = FALSE ); int GetItemSpacing( bool isSmall = FALSE ); void SetColumn( int col, wxListItem &item ); void SetColumnWidth( int col, int width ); void GetColumn( int col, wxListItem &item ) const; int GetColumnWidth( int col ) const; int GetColumnCount() const { return m_columns.GetCount(); } // returns the sum of the heights of all columns int GetHeaderWidth() const; int GetCountPerPage() const; void SetItem( wxListItem &item ); void GetItem( wxListItem &item ); void SetItemState( long item, long state, long stateMask ); int GetItemState( long item, long stateMask ); void GetItemRect( long index, wxRect &rect ); bool GetItemPosition( long item, wxPoint& pos ); int GetSelectedItemCount(); // set the scrollbars and update the positions of the items void RecalculatePositions(); // refresh the window and the header void RefreshAll(); long GetNextItem( long item, int geometry, int state ); void DeleteItem( long index ); void DeleteAllItems(); void DeleteColumn( int col ); void DeleteEverything(); void EnsureVisible( long index ); long FindItem( long start, const wxString& str, bool partial = FALSE ); long FindItem( long start, long data); long HitTest( int x, int y, int &flags ); void InsertItem( wxListItem &item ); void InsertColumn( long col, wxListItem &item ); void SortItems( wxListCtrlCompare fn, long data ); size_t GetItemCount() const; bool IsEmpty() const { return GetItemCount() == 0; } void SetItemCount(long count); void ResetCurrent() { m_current = (size_t)-1; } bool HasCurrent() const { return m_current != (size_t)-1; } // send out a wxListEvent void SendNotify( size_t line, wxEventType command, wxPoint point = wxDefaultPosition ); // 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 // get the backpointer to the list ctrl wxListCtrl *GetListCtrl() const { return wxStaticCast(GetParent(), wxListCtrl); } // get the height of all lines (assuming they all do have the same height) wxCoord GetLineHeight() const; // get the y position of the given line (only for report view) wxCoord GetLineY(size_t line) const; //protected: // the array of all line objects for a non virtual list control wxListLineDataArray m_lines; // the list of column objects wxListHeaderDataList m_columns; // currently focused item or -1 size_t m_current; // the item currently being edited or -1 size_t m_currentEdit; // the number of lines per page int m_linesPerPage; // this flag is set when something which should result in the window // redrawing happens (i.e. an item was added or deleted, or its appearance // changed) and OnPaint() doesn't redraw the window while it is set which // allows to minimize the number of repaintings when a lot of items are // being added. The real repainting occurs only after the next OnIdle() // call bool m_dirty; wxBrush *m_highlightBrush; wxColour *m_highlightColour; int m_xScroll, m_yScroll; wxImageList *m_small_image_list; wxImageList *m_normal_image_list; int m_small_spacing; int m_normal_spacing; bool m_hasFocus; bool m_lastOnSame; wxTimer *m_renameTimer; bool m_renameAccept; wxString m_renameRes; bool m_isCreated; int m_dragCount; wxPoint m_dragStart; // for double click logic size_t m_lineLastClicked, m_lineBeforeLastClicked; protected: // the total count of items in a virtual list control size_t m_countVirt; // the object maintaining the items selection state, only used in virtual // controls wxSelectionStore m_selStore; // common part of all ctors void Init(); // intiialize m_[xy]Scroll void InitScrolling(); // get the line data for the given index wxListLineData *GetLine(size_t n) const { wxASSERT_MSG( n != (size_t)-1, _T("invalid line index") ); if ( IsVirtual() ) { wxConstCast(this, wxListMainWindow)->CacheLineData(n); n = 0; } return &m_lines[n]; } // 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); // get the range of visible lines void GetVisibleLinesRange(size_t *from, size_t *to); // force us to recalculate the range of visible lines void ResetVisibleLinesRange() { m_lineFrom = (size_t)-1; } // get the colour to be used for drawing the rules wxColour GetRuleColour() const { #ifdef __WXMAC__ return *wxWHITE; #else return wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DLIGHT); #endif } private: // initialize the current item if needed void UpdateCurrent(); // called when an item is [un]focuded, i.e. becomes [not] current // // currently unused void OnFocusLine( size_t line ); void OnUnfocusLine( size_t line ); // the height of one line using the current font wxCoord m_lineHeight; // the total header width or 0 if not calculated yet wxCoord m_headerWidth; // the first and last lines being shown on screen right now (inclusive), // both may be -1 if they must be calculated so never access them directly: // use GetVisibleLinesRange() above instead size_t m_lineFrom, m_lineTo; DECLARE_DYNAMIC_CLASS(wxListMainWindow); DECLARE_EVENT_TABLE() }; // ============================================================================ // 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; } void wxSelectionStore::SelectRange(size_t itemFrom, size_t itemTo, bool select) { 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); } } 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-- ) { m_itemsSel.RemoveAt(i); } } } } else // "few" items change state { // just add the items to the selection for ( size_t item = itemFrom; item <= itemTo; item++ ) { SelectItem(item, select); } } } 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 //----------------------------------------------------------------------------- void wxListItemData::Init() { m_image = -1; m_data = 0; m_attr = NULL; } wxListItemData::wxListItemData(wxListMainWindow *owner) { Init(); m_owner = owner; if ( owner->HasFlag(wxLC_REPORT) ) { m_rect = NULL; } else { m_rect = new wxRect; } } void wxListItemData::SetItem( const wxListItem &info ) { if ( info.m_mask & wxLIST_MASK_TEXT ) SetText(info.m_text); if ( info.m_mask & wxLIST_MASK_IMAGE ) m_image = info.m_image; if ( info.m_mask & wxLIST_MASK_DATA ) m_data = info.m_data; if ( info.HasAttributes() ) { if ( m_attr ) *m_attr = *info.GetAttributes(); else m_attr = new wxListItemAttr(*info.GetAttributes()); } if ( m_rect ) { m_rect->x = m_rect->y = m_rect->height = 0; m_rect->width = info.m_width; } } void wxListItemData::SetPosition( int x, int y ) { wxCHECK_RET( m_rect, _T("unexpected SetPosition() call") ); m_rect->x = x; m_rect->y = y; } void wxListItemData::SetSize( int width, int height ) { wxCHECK_RET( m_rect, _T("unexpected SetSize() call") ); if ( width != -1 ) m_rect->width = width; if ( height != -1 ) m_rect->height = height; } bool wxListItemData::IsHit( int x, int y ) const { wxCHECK_MSG( m_rect, FALSE, _T("can't be called in this mode") ); return wxRect(GetX(), GetY(), GetWidth(), GetHeight()).Inside(x, y); } int wxListItemData::GetX() const { wxCHECK_MSG( m_rect, 0, _T("can't be called in this mode") ); return m_rect->x; } int wxListItemData::GetY() const { wxCHECK_MSG( m_rect, 0, _T("can't be called in this mode") ); return m_rect->y; } int wxListItemData::GetWidth() const { wxCHECK_MSG( m_rect, 0, _T("can't be called in this mode") ); return m_rect->width; } int wxListItemData::GetHeight() const { wxCHECK_MSG( m_rect, 0, _T("can't be called in this mode") ); return m_rect->height; } void wxListItemData::GetItem( wxListItem &info ) const { info.m_text = m_text; info.m_image = m_image; info.m_data = m_data; if ( m_attr ) { if ( m_attr->HasTextColour() ) info.SetTextColour(m_attr->GetTextColour()); if ( m_attr->HasBackgroundColour() ) info.SetBackgroundColour(m_attr->GetBackgroundColour()); if ( m_attr->HasFont() ) info.SetFont(m_attr->GetFont()); } } //----------------------------------------------------------------------------- // wxListHeaderData //----------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS(wxListHeaderData,wxObject); wxListHeaderData::wxListHeaderData() { m_mask = 0; m_image = 0; m_format = 0; m_width = 0; m_xpos = 0; m_ypos = 0; m_height = 0; } wxListHeaderData::wxListHeaderData( const wxListItem &item ) { 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); } void wxListHeaderData::SetPosition( int x, int y ) { m_xpos = x; m_ypos = y; } void wxListHeaderData::SetHeight( int h ) { m_height = h; } void wxListHeaderData::SetWidth( int w ) { m_width = w; if (m_width < 0) m_width = WIDTH_COL_DEFAULT; if (m_width < WIDTH_COL_MIN) m_width = WIDTH_COL_MIN; } void wxListHeaderData::SetFormat( int format ) { m_format = format; } bool wxListHeaderData::HasImage() const { return (m_image != 0); } 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 ) { item.m_mask = m_mask; item.m_text = m_text; item.m_image = m_image; item.m_format = m_format; item.m_width = m_width; } int wxListHeaderData::GetImage() const { return m_image; } int wxListHeaderData::GetWidth() const { return m_width; } int wxListHeaderData::GetFormat() const { return m_format; } //----------------------------------------------------------------------------- // wxListLineData //----------------------------------------------------------------------------- inline int wxListLineData::GetMode() const { return m_owner->GetListCtrl()->GetWindowStyleFlag() & wxLC_MASK_TYPE; } inline bool wxListLineData::InReportView() const { return m_owner->HasFlag(wxLC_REPORT); } inline bool wxListLineData::IsVirtual() const { return m_owner->IsVirtual(); } wxListLineData::wxListLineData( wxListMainWindow *owner ) { m_owner = owner; m_items.DeleteContents( TRUE ); if ( InReportView() ) { m_gi = NULL; } else // !report { m_gi = new GeometryInfo; } m_highlighted = FALSE; InitItems( GetMode() == wxLC_REPORT ? m_owner->GetColumnCount() : 1 ); } void wxListLineData::CalculateSize( wxDC *dc, int spacing ) { wxListItemDataList::Node *node = m_items.GetFirst(); wxCHECK_RET( node, _T("no subitems at all??") ); wxListItemData *item = node->GetData(); switch ( GetMode() ) { case wxLC_ICON: case wxLC_SMALL_ICON: { m_gi->m_rectAll.width = spacing; wxString s = item->GetText(); wxCoord lw, lh; if ( s.empty() ) { lh = m_gi->m_rectLabel.width = m_gi->m_rectLabel.height = 0; } else // has label { dc->GetTextExtent( s, &lw, &lh ); if (lh < SCROLL_UNIT_Y) lh = SCROLL_UNIT_Y; lw += EXTRA_WIDTH; lh += EXTRA_HEIGHT; m_gi->m_rectAll.height = spacing + lh; if (lw > spacing) m_gi->m_rectAll.width = lw; m_gi->m_rectLabel.width = lw; m_gi->m_rectLabel.height = lh; } if (item->HasImage()) { int w, h; m_owner->GetImageSize( item->GetImage(), w, h ); m_gi->m_rectIcon.width = w + 8; m_gi->m_rectIcon.height = h + 8; if ( m_gi->m_rectIcon.width > m_gi->m_rectAll.width ) m_gi->m_rectAll.width = m_gi->m_rectIcon.width; if ( m_gi->m_rectIcon.height + lh > m_gi->m_rectAll.height - 4 ) m_gi->m_rectAll.height = m_gi->m_rectIcon.height + lh + 4; } if ( item->HasText() ) { 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_rectHighlight.width = m_gi->m_rectIcon.width; m_gi->m_rectHighlight.height = m_gi->m_rectIcon.height; } } break; case wxLC_LIST: { wxString s = item->GetTextForMeasuring(); wxCoord lw,lh; dc->GetTextExtent( s, &lw, &lh ); if (lh < SCROLL_UNIT_Y) lh = SCROLL_UNIT_Y; lw += EXTRA_WIDTH; lh += EXTRA_HEIGHT; m_gi->m_rectLabel.width = lw; m_gi->m_rectLabel.height = lh; m_gi->m_rectAll.width = lw; m_gi->m_rectAll.height = lh; if (item->HasImage()) { int w, h; m_owner->GetImageSize( item->GetImage(), w, h ); m_gi->m_rectIcon.width = w; m_gi->m_rectIcon.height = h; m_gi->m_rectAll.width += 4 + w; if (h > m_gi->m_rectAll.height) m_gi->m_rectAll.height = h; } m_gi->m_rectHighlight.width = m_gi->m_rectAll.width; m_gi->m_rectHighlight.height = m_gi->m_rectAll.height; } break; case wxLC_REPORT: wxFAIL_MSG( _T("unexpected call to SetSize") ); break; default: wxFAIL_MSG( _T("unknown mode") ); } } void wxListLineData::SetPosition( int x, int y, int window_width, int spacing ) { wxListItemDataList::Node *node = m_items.GetFirst(); wxCHECK_RET( node, _T("no subitems at all??") ); wxListItemData *item = node->GetData(); switch ( GetMode() ) { case wxLC_ICON: case wxLC_SMALL_ICON: m_gi->m_rectAll.x = x; m_gi->m_rectAll.y = y; if ( item->HasImage() ) { m_gi->m_rectIcon.x = m_gi->m_rectAll.x + 4 + (spacing - m_gi->m_rectIcon.width)/2; m_gi->m_rectIcon.y = m_gi->m_rectAll.y + 4; } if ( item->HasText() ) { if (m_gi->m_rectAll.width > spacing) m_gi->m_rectLabel.x = m_gi->m_rectAll.x + 2; 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_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_rectHighlight.x = m_gi->m_rectIcon.x - 4; m_gi->m_rectHighlight.y = m_gi->m_rectIcon.y - 4; } break; case wxLC_LIST: m_gi->m_rectAll.x = x; m_gi->m_rectAll.y = 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()) { m_gi->m_rectIcon.x = m_gi->m_rectAll.x + 2; m_gi->m_rectIcon.y = m_gi->m_rectAll.y + 2; m_gi->m_rectLabel.x = m_gi->m_rectAll.x + 6 + m_gi->m_rectIcon.width; } else { m_gi->m_rectLabel.x = m_gi->m_rectAll.x + 2; } break; case wxLC_REPORT: wxFAIL_MSG( _T("unexpected call to SetPosition") ); break; default: wxFAIL_MSG( _T("unknown mode") ); } } void wxListLineData::InitItems( int num ) { for (int i = 0; i < num; i++) m_items.Append( new wxListItemData(m_owner) ); } void wxListLineData::SetItem( int index, const wxListItem &info ) { wxListItemDataList::Node *node = m_items.Item( index ); wxCHECK_RET( node, _T("invalid column index in SetItem") ); wxListItemData *item = node->GetData(); item->SetItem( info ); } void wxListLineData::GetItem( int index, wxListItem &info ) { wxListItemDataList::Node *node = m_items.Item( index ); if (node) { wxListItemData *item = node->GetData(); item->GetItem( info ); } } wxString wxListLineData::GetText(int index) const { wxString s; wxListItemDataList::Node *node = m_items.Item( index ); if (node) { wxListItemData *item = node->GetData(); s = item->GetText(); } return s; } void wxListLineData::SetText( int index, const wxString s ) { wxListItemDataList::Node *node = m_items.Item( index ); if (node) { wxListItemData *item = node->GetData(); item->SetText( s ); } } void wxListLineData::SetImage( int index, int image ) { wxListItemDataList::Node *node = m_items.Item( index ); wxCHECK_RET( node, _T("invalid column index in SetImage()") ); wxListItemData *item = node->GetData(); item->SetImage(image); } int wxListLineData::GetImage( int index ) const { wxListItemDataList::Node *node = m_items.Item( index ); wxCHECK_MSG( node, -1, _T("invalid column index in GetImage()") ); wxListItemData *item = node->GetData(); return item->GetImage(); } void wxListLineData::SetAttributes(wxDC *dc, const wxListItemAttr *attr, const wxColour& colText, const wxFont& font, bool highlight) { // 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 ( !highlight && attr && attr->HasTextColour() ) { dc->SetTextForeground(attr->GetTextColour()); } else { dc->SetTextForeground(colText); } if ( attr && attr->HasFont() ) { dc->SetFont(attr->GetFont()); } else { dc->SetFont(font); } } void wxListLineData::Draw( wxDC *dc ) { wxListItemDataList::Node *node = m_items.GetFirst(); wxCHECK_RET( node, _T("no subitems at all??") ); wxListItemData *item = node->GetData(); if (item->HasImage()) { wxRect rectIcon = m_gi->m_rectIcon; m_owner->DrawImage( item->GetImage(), dc, rectIcon.x, rectIcon.y ); } if (item->HasText()) { wxRect rectLabel = m_gi->m_rectLabel; dc->DrawText( item->GetText(), rectLabel.x, rectLabel.y ); } } void wxListLineData::DrawInReportMode( wxDC *dc, const wxRect& rect, const wxRect& rectHL, bool highlighted ) { // use our own flag if we maintain it if ( !IsVirtual() ) highlighted = m_highlighted; // default foreground colour wxWindow *listctrl = m_owner->GetParent(); wxColour colText; if ( highlighted ) { colText = wxSystemSettings::GetSystemColour( wxSYS_COLOUR_HIGHLIGHTTEXT ); } else { colText = listctrl->GetForegroundColour(); } // default font wxFont font = listctrl->GetFont(); // 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, highlighted); bool hasBgCol = attr && attr->HasBackgroundColour(); if ( highlighted || hasBgCol ) { if ( highlighted ) { dc->SetBrush( *m_owner->m_highlightBrush ); } else { if ( hasBgCol ) dc->SetBrush(wxBrush(attr->GetBackgroundColour(), wxSOLID)); else dc->SetBrush( * wxWHITE_BRUSH ); } dc->SetPen( * wxTRANSPARENT_PEN ); dc->DrawRectangle( rectHL ); } wxListItemDataList::Node *node = m_items.GetFirst(); wxCHECK_RET( node, _T("no subitems at all??") ); size_t col = 0; wxCoord x = rect.x + HEADER_OFFSET_X, y = rect.y + (LINE_SPACING + EXTRA_HEIGHT) / 2; while ( node ) { wxListItemData *item = node->GetData(); int xOld = x; 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"? } int width = m_owner->GetColumnWidth(col++); wxDCClipper clipper(*dc, x, y, width, rect.height); if ( item->HasText() ) { dc->DrawText( item->GetText(), x, y ); } x = xOld + width; node = node->GetNext(); } } bool wxListLineData::Highlight( bool on ) { wxCHECK_MSG( !m_owner->IsVirtual(), FALSE, _T("unexpected call to Highlight") ); if ( on == m_highlighted ) return FALSE; m_highlighted = on; return TRUE; } void wxListLineData::ReverseHighlight( void ) { Highlight(!IsHighlighted()); } //----------------------------------------------------------------------------- // wxListHeaderWindow //----------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS(wxListHeaderWindow,wxWindow); BEGIN_EVENT_TABLE(wxListHeaderWindow,wxWindow) EVT_PAINT (wxListHeaderWindow::OnPaint) EVT_MOUSE_EVENTS (wxListHeaderWindow::OnMouse) EVT_SET_FOCUS (wxListHeaderWindow::OnSetFocus) END_EVENT_TABLE() wxListHeaderWindow::wxListHeaderWindow( void ) { m_owner = (wxListMainWindow *) NULL; m_currentCursor = (wxCursor *) NULL; m_resizeCursor = (wxCursor *) NULL; m_isDragging = 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 ) { m_owner = owner; // m_currentCursor = wxSTANDARD_CURSOR; m_currentCursor = (wxCursor *) NULL; m_resizeCursor = new wxCursor( wxCURSOR_SIZEWE ); m_isDragging = FALSE; m_dirty = FALSE; SetBackgroundColour( wxSystemSettings::GetSystemColour( wxSYS_COLOUR_BTNFACE ) ); } wxListHeaderWindow::~wxListHeaderWindow( void ) { delete m_resizeCursor; } void wxListHeaderWindow::DoDrawRect( wxDC *dc, int x, int y, int w, int h ) { #ifdef __WXGTK__ 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); #elif defined( __WXMAC__ ) const int m_corner = 1; dc->SetBrush( *wxTRANSPARENT_BRUSH ); dc->SetPen( wxPen( wxSystemSettings::GetSystemColour( wxSYS_COLOUR_BTNSHADOW ) , 1 , wxSOLID ) ); dc->DrawLine( x+w-m_corner+1, y, x+w, y+h ); // right (outer) dc->DrawRectangle( x, y+h, w+1, 1 ); // bottom (outer) wxPen pen( wxColour( 0x88 , 0x88 , 0x88 ), 1, wxSOLID ); dc->SetPen( pen ); dc->DrawLine( x+w-m_corner, y, x+w-1, y+h ); // right (inner) dc->DrawRectangle( x+1, y+h-1, w-2, 1 ); // bottom (inner) dc->SetPen( *wxWHITE_PEN ); dc->DrawRectangle( x, y, w-m_corner+1, 1 ); // top (outer) dc->DrawRectangle( x, y, 1, h ); // left (outer) dc->DrawLine( x, y+h-1, x+1, y+h-1 ); dc->DrawLine( x+w-1, y, x+w-1, y+1 ); #else // !GTK, !Mac const int m_corner = 1; dc->SetBrush( *wxTRANSPARENT_BRUSH ); dc->SetPen( *wxBLACK_PEN ); 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 ); dc->SetPen( pen ); dc->DrawLine( x+w-m_corner, y, x+w-1, y+h ); // right (inner) dc->DrawRectangle( x+1, y+h-1, w-2, 1 ); // bottom (inner) dc->SetPen( *wxWHITE_PEN ); dc->DrawRectangle( x, y, w-m_corner+1, 1 ); // top (outer) dc->DrawRectangle( x, y, 1, h ); // left (outer) dc->DrawLine( x, y+h-1, x+1, y+h-1 ); dc->DrawLine( x+w-1, y, x+w-1, y+1 ); #endif } // shift the DC origin to match the position of the main window horz // scrollbar: this allows us to always use logical coords void wxListHeaderWindow::AdjustDC(wxDC& dc) { int xpix; m_owner->GetScrollPixelsPerUnit( &xpix, NULL ); int x; m_owner->GetViewStart( &x, NULL ); // account for the horz scrollbar offset dc.SetDeviceOrigin( -x * xpix, 0 ); } void wxListHeaderWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) { #ifdef __WXGTK__ wxClientDC dc( this ); #else wxPaintDC dc( this ); #endif PrepareDC( dc ); AdjustDC( dc ); dc.BeginDrawing(); dc.SetFont( GetFont() ); // width and height of the entire header window int w, h; GetClientSize( &w, &h ); m_owner->CalcUnscrolledPosition(w, 0, &w, NULL); dc.SetBackgroundMode(wxTRANSPARENT); // do *not* use the listctrl colour for headers - one day we will have a // function to set it separately //dc.SetTextForeground( *wxBLACK ); dc.SetTextForeground(wxSystemSettings:: GetSystemColour( wxSYS_COLOUR_WINDOWTEXT )); int x = HEADER_OFFSET_X; int numColumns = m_owner->GetColumnCount(); wxListItem item; for (int i = 0; i < numColumns; 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; 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; } dc.EndDrawing(); } void wxListHeaderWindow::DrawCurrent() { int x1 = m_currentX; int y1 = 0; ClientToScreen( &x1, &y1 ); int x2 = m_currentX-1; int y2 = 0; m_owner->GetClientSize( NULL, &y2 ); m_owner->ClientToScreen( &x2, &y2 ); wxScreenDC dc; dc.SetLogicalFunction( wxINVERT ); dc.SetPen( wxPen( *wxBLACK, 2, wxSOLID ) ); dc.SetBrush( *wxTRANSPARENT_BRUSH ); AdjustDC(dc); dc.DrawLine( x1, y1, x2, y2 ); dc.SetLogicalFunction( wxCOPY ); dc.SetPen( wxNullPen ); dc.SetBrush( wxNullBrush ); } void wxListHeaderWindow::OnMouse( wxMouseEvent &event ) { // we want to work with logical coords int x; m_owner->CalcUnscrolledPosition(event.GetX(), 0, &x, NULL); int y = event.GetY(); if (m_isDragging) { // we don't draw the line beyond our window, but we allow dragging it // there int w = 0; GetClientSize( &w, NULL ); m_owner->CalcUnscrolledPosition(w, 0, &w, NULL); w -= 6; // erase the line if it was drawn if ( m_currentX < w ) DrawCurrent(); if (event.ButtonUp()) { ReleaseMouse(); m_isDragging = FALSE; m_dirty = TRUE; m_owner->SetColumnWidth( m_column, m_currentX - m_minX ); } else { if (x > m_minX + 7) m_currentX = x; else m_currentX = m_minX + 7; // draw in the new location if ( m_currentX < w ) DrawCurrent(); } } else // not dragging { m_minX = 0; bool hit_border = FALSE; // end of the current column int xpos = 0; // find the column where this event occured int countCol = m_owner->GetColumnCount(); for (int col = 0; col < countCol; col++) { xpos += m_owner->GetColumnWidth( col ); m_column = col; if ( (abs(x-xpos) < 3) && (y < 22) ) { // near the column border hit_border = TRUE; break; } if ( x < xpos ) { // inside the column break; } m_minX = xpos; } if (event.LeftDown()) { if (hit_border) { m_isDragging = TRUE; m_currentX = x; DrawCurrent(); CaptureMouse(); } else { wxWindow *parent = GetParent(); wxListEvent le( wxEVT_COMMAND_LIST_COL_CLICK, parent->GetId() ); le.SetEventObject( parent ); le.m_col = m_column; parent->GetEventHandler()->ProcessEvent( le ); } } else if (event.Moving()) { bool setCursor; if (hit_border) { setCursor = m_currentCursor == wxSTANDARD_CURSOR; m_currentCursor = m_resizeCursor; } else { setCursor = m_currentCursor != wxSTANDARD_CURSOR; m_currentCursor = wxSTANDARD_CURSOR; } if ( setCursor ) SetCursor(*m_currentCursor); } } } void wxListHeaderWindow::OnSetFocus( wxFocusEvent &WXUNUSED(event) ) { m_owner->SetFocus(); } //----------------------------------------------------------------------------- // wxListRenameTimer (internal) //----------------------------------------------------------------------------- wxListRenameTimer::wxListRenameTimer( wxListMainWindow *owner ) { m_owner = owner; } void wxListRenameTimer::Notify() { m_owner->OnRenameTimer(); } //----------------------------------------------------------------------------- // wxListTextCtrl (internal) //----------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS(wxListTextCtrl,wxTextCtrl); BEGIN_EVENT_TABLE(wxListTextCtrl,wxTextCtrl) EVT_CHAR (wxListTextCtrl::OnChar) EVT_KEY_UP (wxListTextCtrl::OnKeyUp) EVT_KILL_FOCUS (wxListTextCtrl::OnKillFocus) END_EVENT_TABLE() wxListTextCtrl::wxListTextCtrl( wxWindow *parent, const wxWindowID id, bool *accept, wxString *res, wxListMainWindow *owner, const wxString &value, const wxPoint &pos, const wxSize &size, int style, const wxValidator& validator, const wxString &name ) : wxTextCtrl( parent, id, value, pos, size, style, validator, name ) { m_res = res; m_accept = accept; m_owner = owner; (*m_accept) = FALSE; (*m_res) = ""; m_startValue = value; } void wxListTextCtrl::OnChar( wxKeyEvent &event ) { if (event.m_keyCode == WXK_RETURN) { (*m_accept) = TRUE; (*m_res) = GetValue(); if (!wxPendingDelete.Member(this)) wxPendingDelete.Append(this); if ((*m_accept) && ((*m_res) != m_startValue)) m_owner->OnRenameAccept(); return; } if (event.m_keyCode == WXK_ESCAPE) { (*m_accept) = FALSE; (*m_res) = ""; if (!wxPendingDelete.Member(this)) wxPendingDelete.Append(this); return; } event.Skip(); } void wxListTextCtrl::OnKeyUp( wxKeyEvent &event ) { // 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?? if (myPos.x + sx > parentSize.x) sx = parentSize.x - myPos.x; if (mySize.x > sx) sx = mySize.x; SetSize(sx, -1); event.Skip(); } void wxListTextCtrl::OnKillFocus( wxFocusEvent &WXUNUSED(event) ) { if (!wxPendingDelete.Member(this)) wxPendingDelete.Append(this); if ((*m_accept) && ((*m_res) != m_startValue)) m_owner->OnRenameAccept(); } //----------------------------------------------------------------------------- // wxListMainWindow //----------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS(wxListMainWindow,wxScrolledWindow); BEGIN_EVENT_TABLE(wxListMainWindow,wxScrolledWindow) EVT_PAINT (wxListMainWindow::OnPaint) EVT_MOUSE_EVENTS (wxListMainWindow::OnMouse) EVT_CHAR (wxListMainWindow::OnChar) EVT_KEY_DOWN (wxListMainWindow::OnKeyDown) EVT_SET_FOCUS (wxListMainWindow::OnSetFocus) EVT_KILL_FOCUS (wxListMainWindow::OnKillFocus) EVT_SCROLLWIN (wxListMainWindow::OnScroll) END_EVENT_TABLE() void wxListMainWindow::Init() { m_columns.DeleteContents( TRUE ); m_dirty = TRUE; m_countVirt = 0; m_lineFrom = m_lineTo = (size_t)-1; m_linesPerPage = 0; m_headerWidth = m_lineHeight = 0; m_small_image_list = (wxImageList *) NULL; m_normal_image_list = (wxImageList *) NULL; m_small_spacing = 30; m_normal_spacing = 40; m_hasFocus = FALSE; m_dragCount = 0; m_isCreated = FALSE; m_lastOnSame = FALSE; m_renameTimer = new wxListRenameTimer( this ); m_renameAccept = FALSE; m_current = m_currentEdit = m_lineLastClicked = m_lineBeforeLastClicked = (size_t)-1; } void wxListMainWindow::InitScrolling() { if ( HasFlag(wxLC_REPORT) ) { m_xScroll = SCROLL_UNIT_X; m_yScroll = SCROLL_UNIT_Y; } else { m_xScroll = SCROLL_UNIT_Y; m_yScroll = 0; } } wxListMainWindow::wxListMainWindow() { Init(); m_highlightBrush = (wxBrush *) NULL; m_xScroll = m_yScroll = 0; } wxListMainWindow::wxListMainWindow( wxWindow *parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString &name ) : wxScrolledWindow( parent, id, pos, size, style | wxHSCROLL | wxVSCROLL, name ) { Init(); m_highlightBrush = new wxBrush( wxSystemSettings::GetSystemColour(wxSYS_COLOUR_HIGHLIGHT), wxSOLID ); wxSize sz = size; sz.y = 25; InitScrolling(); SetScrollbars( m_xScroll, m_yScroll, 0, 0, 0, 0 ); SetBackgroundColour( wxSystemSettings::GetSystemColour( wxSYS_COLOUR_LISTBOX ) ); } wxListMainWindow::~wxListMainWindow() { DeleteEverything(); delete m_highlightBrush; delete m_renameTimer; } void wxListMainWindow::CacheLineData(size_t line) { wxListCtrl *listctrl = GetListCtrl(); wxListLineData *ld = GetDummyLine(); size_t countCol = GetColumnCount(); for ( size_t col = 0; col < countCol; col++ ) { ld->SetText(col, listctrl->OnGetItemText(line, col)); } ld->SetImage(listctrl->OnGetItemImage(line)); } wxListLineData *wxListMainWindow::GetDummyLine() const { wxASSERT_MSG( !IsEmpty(), _T("invalid line index") ); if ( m_lines.IsEmpty() ) { // normal controls are supposed to have something in m_lines // already if it's not empty wxASSERT_MSG( IsVirtual(), _T("logic error") ); wxListMainWindow *self = wxConstCast(this, wxListMainWindow); wxListLineData *line = new wxListLineData(self); self->m_lines.Add(line); } return &m_lines[0]; } // ---------------------------------------------------------------------------- // line geometry (report mode only) // ---------------------------------------------------------------------------- wxCoord wxListMainWindow::GetLineHeight() const { wxASSERT_MSG( HasFlag(wxLC_REPORT), _T("only works in report mode") ); // we cache the line height as calling GetTextExtent() is slow if ( !m_lineHeight ) { wxListMainWindow *self = wxConstCast(this, wxListMainWindow); wxClientDC dc( self ); dc.SetFont( GetFont() ); wxCoord y; dc.GetTextExtent(_T("H"), NULL, &y); if ( y < SCROLL_UNIT_Y ) y = SCROLL_UNIT_Y; y += EXTRA_HEIGHT; self->m_lineHeight = y + LINE_SPACING; } return m_lineHeight; } wxCoord wxListMainWindow::GetLineY(size_t line) const { wxASSERT_MSG( HasFlag(wxLC_REPORT), _T("only works in report mode") ); return LINE_SPACING + line*GetLineHeight(); } wxRect wxListMainWindow::GetLineRect(size_t line) const { if ( !InReportView() ) return GetLine(line)->m_gi->m_rectAll; wxRect rect; rect.x = HEADER_OFFSET_X; rect.y = GetLineY(line); rect.width = GetHeaderWidth(); rect.height = GetLineHeight(); return rect; } wxRect wxListMainWindow::GetLineLabelRect(size_t line) const { if ( !InReportView() ) return GetLine(line)->m_gi->m_rectLabel; wxRect rect; rect.x = HEADER_OFFSET_X; rect.y = GetLineY(line); rect.width = GetColumnWidth(0); rect.height = GetLineHeight(); return rect; } wxRect wxListMainWindow::GetLineIconRect(size_t line) const { if ( !InReportView() ) return GetLine(line)->m_gi->m_rectIcon; wxListLineData *ld = GetLine(line); wxASSERT_MSG( ld->HasImage(), _T("should have an image") ); wxRect rect; rect.x = HEADER_OFFSET_X; rect.y = GetLineY(line); GetImageSize(ld->GetImage(), rect.width, rect.height); return rect; } wxRect wxListMainWindow::GetLineHighlightRect(size_t line) const { return InReportView() ? GetLineRect(line) : GetLine(line)->m_gi->m_rectHighlight; } long wxListMainWindow::HitTestLine(size_t line, int x, int y) const { wxListLineData *ld = GetLine(line); if ( ld->HasImage() && GetLineIconRect(line).Inside(x, y) ) return wxLIST_HITTEST_ONITEMICON; if ( ld->HasText() ) { wxRect rect = InReportView() ? GetLineRect(line) : GetLineLabelRect(line); if ( rect.Inside(x, y) ) return wxLIST_HITTEST_ONITEMLABEL; } return 0; } // ---------------------------------------------------------------------------- // highlight (selection) handling // ---------------------------------------------------------------------------- bool wxListMainWindow::IsHighlighted(size_t line) const { if ( IsVirtual() ) { return m_selStore.IsSelected(line); } else // !virtual { wxListLineData *ld = GetLine(line); wxCHECK_MSG( ld, FALSE, _T("invalid index in IsHighlighted") ); return ld->IsHighlighted(); } } void wxListMainWindow::HighlightLines( size_t lineFrom, size_t lineTo, bool highlight ) { if ( IsVirtual() ) { m_selStore.SelectRange(lineFrom, lineTo, highlight); RefreshLines(lineFrom, lineTo); } else { // do it the dumb way bool needsRefresh = FALSE; for ( size_t line = lineFrom; line <= lineTo; line++ ) { if ( HighlightLine(line, highlight) ) needsRefresh = TRUE; } if ( needsRefresh ) RefreshLines(lineFrom, lineTo); } } 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 IsHighlighted") ); changed = ld->Highlight(highlight); } if ( changed ) { SendNotify( line, highlight ? wxEVT_COMMAND_LIST_ITEM_SELECTED : wxEVT_COMMAND_LIST_ITEM_DESELECTED ); } return changed; } void wxListMainWindow::RefreshLine( size_t line ) { wxRect rect = GetLineRect(line); CalcScrolledPosition( rect.x, rect.y, &rect.x, &rect.y ); RefreshRect( rect ); } void wxListMainWindow::RefreshLines( size_t lineFrom, size_t lineTo ) { // we suppose that they are ordered by caller wxASSERT_MSG( lineFrom <= lineTo, _T("indices in disorder") ); wxASSERT_MSG( lineTo < GetItemCount(), _T("invalid line range") ); if ( HasFlag(wxLC_REPORT) ) { size_t visibleFrom, visibleTo; GetVisibleLinesRange(&visibleFrom, &visibleTo); if ( lineFrom < visibleFrom ) lineFrom = visibleFrom; if ( lineTo > visibleTo ) lineTo = visibleTo; wxRect rect; rect.x = 0; rect.y = GetLineY(lineFrom); rect.width = GetClientSize().x; rect.height = GetLineY(lineTo) - rect.y; CalcScrolledPosition( rect.x, rect.y, &rect.x, &rect.y ); RefreshRect( rect ); } else // !report { // TODO: this should be optimized... for ( size_t line = lineFrom; line <= lineTo; line++ ) { RefreshLine(line); } } } void wxListMainWindow::RefreshAfter( size_t lineFrom ) { 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::OnPaint( wxPaintEvent &WXUNUSED(event) ) { // Note: a wxPaintDC must be constructed even if no drawing is // done (a Windows requirement). wxPaintDC dc( this ); if ( IsEmpty() ) { // empty control. nothing to draw return; } if ( m_dirty ) { // delay the repainting until we calculate all the items positions return; } PrepareDC( dc ); int dev_x, dev_y; CalcScrolledPosition( 0, 0, &dev_x, &dev_y ); dc.BeginDrawing(); dc.SetFont( GetFont() ); if ( HasFlag(wxLC_REPORT) ) { int lineHeight = GetLineHeight(); size_t visibleFrom, visibleTo; GetVisibleLinesRange(&visibleFrom, &visibleTo); wxRect rectLine; wxCoord xOrig, yOrig; CalcUnscrolledPosition(0, 0, &xOrig, &yOrig); for ( size_t line = visibleFrom; line <= visibleTo; line++ ) { rectLine = GetLineRect(line); if ( !IsExposed(rectLine.x - xOrig, rectLine.y - yOrig, rectLine.width, rectLine.height) ) { // don't redraw unaffected lines to avoid flicker continue; } GetLine(line)->DrawInReportMode( &dc, rectLine, GetLineHighlightRect(line), IsHighlighted(line) ); } if ( HasFlag(wxLC_HRULES) ) { wxPen pen(GetRuleColour(), 1, wxSOLID); wxSize clientSize = GetClientSize(); for ( size_t i = visibleFrom; i <= visibleTo; i++ ) { dc.SetPen(pen); dc.SetBrush( *wxTRANSPARENT_BRUSH ); dc.DrawLine(0 - dev_x, i*lineHeight, clientSize.x - dev_x, i*lineHeight); } // Draw last horizontal rule if ( visibleTo > visibleFrom ) { dc.SetPen(pen); dc.SetBrush( *wxTRANSPARENT_BRUSH ); dc.DrawLine(0 - dev_x, m_lineTo*lineHeight, clientSize.x - dev_x , m_lineTo*lineHeight ); } } // Draw vertical rules if required if ( HasFlag(wxLC_VRULES) && !IsEmpty() ) { wxPen pen(GetRuleColour(), 1, wxSOLID); int col = 0; wxRect firstItemRect; wxRect lastItemRect; GetItemRect(0, firstItemRect); GetItemRect(GetItemCount() - 1, lastItemRect); int x = firstItemRect.GetX(); dc.SetPen(pen); dc.SetBrush(* wxTRANSPARENT_BRUSH); for (col = 0; col < GetColumnCount(); col++) { int colWidth = GetColumnWidth(col); x += colWidth; dc.DrawLine(x - dev_x, firstItemRect.GetY() - 1 - dev_y, x - dev_x, lastItemRect.GetBottom() + 1 - dev_y); } } } else // !report { size_t count = GetItemCount(); for ( size_t i = 0; i < count; i++ ) { GetLine(i)->Draw( &dc ); } } if ( HasCurrent() && m_hasFocus ) { #ifdef __WXMAC__ // no rect outline, we already have the background color #else dc.SetPen( *wxBLACK_PEN ); dc.SetBrush( *wxTRANSPARENT_BRUSH ); dc.DrawRectangle( GetLineHighlightRect(m_current) ); #endif } dc.EndDrawing(); } void wxListMainWindow::HighlightAll( bool on ) { if ( IsSingleSel() ) { 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) ) { HighlightLine(m_current, FALSE); RefreshLine(m_current); } } else // multi sel { HighlightLines(0, GetItemCount() - 1, on); } } void wxListMainWindow::SendNotify( size_t line, wxEventType command, wxPoint point ) { wxListEvent le( command, GetParent()->GetId() ); le.SetEventObject( GetParent() ); le.m_itemIndex = line; // set only for events which have position if ( point != wxDefaultPosition ) le.m_pointDrag = point; GetLine(line)->GetItem( 0, le.m_item ); GetParent()->GetEventHandler()->ProcessEvent( le ); } void wxListMainWindow::OnFocusLine( size_t WXUNUSED(line) ) { // SendNotify( line, wxEVT_COMMAND_LIST_ITEM_FOCUSSED ); } void wxListMainWindow::OnUnfocusLine( size_t WXUNUSED(line) ) { // SendNotify( line, wxEVT_COMMAND_LIST_ITEM_UNFOCUSSED ); } void wxListMainWindow::EditLabel( long item ) { wxCHECK_RET( (item >= 0) && ((size_t)item < GetItemCount()), wxT("wrong index in wxListCtrl::EditLabel()") ); m_currentEdit = (size_t)item; wxListEvent le( wxEVT_COMMAND_LIST_BEGIN_LABEL_EDIT, GetParent()->GetId() ); le.SetEventObject( GetParent() ); le.m_itemIndex = item; wxListLineData *data = GetLine(m_currentEdit); wxCHECK_RET( data, _T("invalid index in EditLabel()") ); data->GetItem( 0, le.m_item ); GetParent()->GetEventHandler()->ProcessEvent( le ); if (!le.IsAllowed()) return; // 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(); wxClientDC dc(this); PrepareDC( dc ); wxString s = data->GetText(0); wxRect rectLabel = GetLineLabelRect(m_currentEdit); rectLabel.x = dc.LogicalToDeviceX( rectLabel.x ); rectLabel.y = dc.LogicalToDeviceY( rectLabel.y ); wxListTextCtrl *text = new wxListTextCtrl ( this, -1, &m_renameAccept, &m_renameRes, this, s, wxPoint(rectLabel.x-4,rectLabel.y-4), wxSize(rectLabel.width+11,rectLabel.height+8) ); text->SetFocus(); } void wxListMainWindow::OnRenameTimer() { wxCHECK_RET( HasCurrent(), wxT("unexpected rename timer") ); EditLabel( m_current ); } void wxListMainWindow::OnRenameAccept() { wxListEvent le( wxEVT_COMMAND_LIST_END_LABEL_EDIT, GetParent()->GetId() ); le.SetEventObject( GetParent() ); le.m_itemIndex = m_currentEdit; wxListLineData *data = GetLine(m_currentEdit); wxCHECK_RET( data, _T("invalid index in OnRenameAccept()") ); data->GetItem( 0, le.m_item ); le.m_item.m_text = m_renameRes; GetParent()->GetEventHandler()->ProcessEvent( le ); if (!le.IsAllowed()) return; wxListItem info; info.m_mask = wxLIST_MASK_TEXT; info.m_itemId = le.m_itemIndex; info.m_text = m_renameRes; info.SetTextColour(le.m_item.GetTextColour()); SetItem( info ); } void wxListMainWindow::OnMouse( wxMouseEvent &event ) { event.SetEventObject( GetParent() ); if ( GetParent()->GetEventHandler()->ProcessEvent( event) ) return; if ( !HasCurrent() || IsEmpty() ) return; if (m_dirty) return; if ( !(event.Dragging() || event.ButtonDown() || event.LeftUp() || event.ButtonDClick()) ) return; int x = event.GetX(); int y = event.GetY(); CalcUnscrolledPosition( x, y, &x, &y ); // where did we hit it (if we did)? long hitResult = 0; size_t count = GetItemCount(), current; if ( HasFlag(wxLC_REPORT) ) { current = y / GetLineHeight(); if ( current < count ) hitResult = HitTestLine(current, x, y); } 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 ( current = 0; current < count; current++ ) { hitResult = HitTestLine(current, x, y); if ( hitResult ) break; } } if (event.Dragging()) { if (m_dragCount == 0) m_dragStart = wxPoint(x,y); m_dragCount++; if (m_dragCount != 3) return; int command = event.RightIsDown() ? wxEVT_COMMAND_LIST_BEGIN_RDRAG : wxEVT_COMMAND_LIST_BEGIN_DRAG; wxListEvent le( command, GetParent()->GetId() ); le.SetEventObject( GetParent() ); le.m_pointDrag = m_dragStart; GetParent()->GetEventHandler()->ProcessEvent( le ); return; } else { m_dragCount = 0; } if ( !hitResult ) { // outside of any item return; } bool forceClick = FALSE; if (event.ButtonDClick()) { m_renameTimer->Stop(); m_lastOnSame = FALSE; if ( current == m_lineBeforeLastClicked ) { SendNotify( current, wxEVT_COMMAND_LIST_ITEM_ACTIVATED ); return; } else { // the first click was on another item, so don't interpret this as // a double click, but as a simple click instead forceClick = TRUE; } } if (event.LeftUp() && m_lastOnSame) { if ((current == m_current) && (hitResult == wxLIST_HITTEST_ONITEMLABEL) && HasFlag(wxLC_EDIT_LABELS) ) { m_renameTimer->Start( 100, TRUE ); } m_lastOnSame = FALSE; } else if (event.RightDown()) { SendNotify( current, wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK, event.GetPosition() ); } else if (event.MiddleDown()) { SendNotify( current, wxEVT_COMMAND_LIST_ITEM_MIDDLE_CLICK ); } else if ( event.LeftDown() || forceClick ) { m_lineBeforeLastClicked = m_lineLastClicked; m_lineLastClicked = current; size_t oldCurrent = m_current; if ( IsSingleSel() || !(event.ControlDown() || event.ShiftDown()) ) { HighlightAll( FALSE ); m_current = current; ReverseHighlight(m_current); } else // multi sel & either ctrl or shift is down { if (event.ControlDown()) { m_current = current; ReverseHighlight(m_current); } else if (event.ShiftDown()) { m_current = current; size_t lineFrom = oldCurrent, lineTo = current; if ( lineTo < lineFrom ) { lineTo = lineFrom; lineFrom = m_current; } HighlightLines(lineFrom, lineTo); } else // !ctrl, !shift { // test in the enclosing if should make it impossible wxFAIL_MSG( _T("how did we get here?") ); } } if (m_current != oldCurrent) { RefreshLine( oldCurrent ); OnUnfocusLine( oldCurrent ); OnFocusLine( m_current ); } // forceClick is only set if the previous click was on another item m_lastOnSame = !forceClick && (m_current == oldCurrent); } } void wxListMainWindow::MoveToFocus() { if ( !HasCurrent() ) return; wxRect rect = GetLineRect(m_current); int client_w, client_h; GetClientSize( &client_w, &client_h ); int view_x = m_xScroll*GetScrollPos( wxHORIZONTAL ); int view_y = m_yScroll*GetScrollPos( wxVERTICAL ); 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 ); } else // !report { if (rect.x-view_x < 5) Scroll( (rect.x-5)/m_xScroll, -1 ); if (rect.x+rect.width-5 > view_x+client_w) Scroll( (rect.x+rect.width-client_w+SCROLL_UNIT_X)/m_xScroll, -1 ); } } // ---------------------------------------------------------------------------- // keyboard handling // ---------------------------------------------------------------------------- void wxListMainWindow::OnArrowChar(size_t newCurrent, const wxKeyEvent& event) { wxCHECK_RET( newCurrent < (size_t)GetItemCount(), _T("invalid item index in OnArrowChar()") ); size_t oldCurrent = m_current; // in single selection we just ignore Shift as we can't select several // items anyhow if ( event.ShiftDown() && !IsSingleSel() ) { m_current = newCurrent; // select all the items between the old and the new one if ( oldCurrent > newCurrent ) { newCurrent = oldCurrent; oldCurrent = m_current; } HighlightLines(oldCurrent, newCurrent); } else // !shift { // all previously selected items are unselected unless ctrl is held if ( !event.ControlDown() ) HighlightAll(FALSE); m_current = newCurrent; HighlightLine( oldCurrent, FALSE ); RefreshLine( oldCurrent ); if ( !event.ControlDown() ) { HighlightLine( m_current, TRUE ); } } OnUnfocusLine( oldCurrent ); OnFocusLine( m_current ); RefreshLine( m_current ); MoveToFocus(); } void wxListMainWindow::OnKeyDown( wxKeyEvent &event ) { wxWindow *parent = GetParent(); /* we propagate the key event up */ wxKeyEvent ke( wxEVT_KEY_DOWN ); ke.m_shiftDown = event.m_shiftDown; ke.m_controlDown = event.m_controlDown; ke.m_altDown = event.m_altDown; ke.m_metaDown = event.m_metaDown; ke.m_keyCode = event.m_keyCode; ke.m_x = event.m_x; ke.m_y = event.m_y; ke.SetEventObject( parent ); if (parent->GetEventHandler()->ProcessEvent( ke )) return; event.Skip(); } void wxListMainWindow::OnChar( wxKeyEvent &event ) { wxWindow *parent = GetParent(); /* we send a list_key event up */ if ( HasCurrent() ) { wxListEvent le( wxEVT_COMMAND_LIST_KEY_DOWN, GetParent()->GetId() ); le.m_itemIndex = m_current; GetLine(m_current)->GetItem( 0, le.m_item ); le.m_code = (int)event.KeyCode(); le.SetEventObject( parent ); parent->GetEventHandler()->ProcessEvent( le ); } /* we propagate the char event up */ wxKeyEvent ke( wxEVT_CHAR ); ke.m_shiftDown = event.m_shiftDown; ke.m_controlDown = event.m_controlDown; ke.m_altDown = event.m_altDown; ke.m_metaDown = event.m_metaDown; ke.m_keyCode = event.m_keyCode; ke.m_x = event.m_x; ke.m_y = event.m_y; ke.SetEventObject( parent ); if (parent->GetEventHandler()->ProcessEvent( ke )) return; if (event.KeyCode() == WXK_TAB) { wxNavigationKeyEvent nevent; nevent.SetWindowChange( event.ControlDown() ); nevent.SetDirection( !event.ShiftDown() ); nevent.SetEventObject( GetParent()->GetParent() ); nevent.SetCurrentFocus( m_parent ); if (GetParent()->GetParent()->GetEventHandler()->ProcessEvent( nevent )) return; } /* no item -> nothing to do */ if (!HasCurrent()) { event.Skip(); return; } switch (event.KeyCode()) { case WXK_UP: if ( m_current > 0 ) OnArrowChar( m_current - 1, event ); break; case WXK_DOWN: if ( m_current < (size_t)GetItemCount() - 1 ) OnArrowChar( m_current + 1, event ); break; case WXK_END: if (!IsEmpty()) OnArrowChar( GetItemCount() - 1, event ); break; case WXK_HOME: if (!IsEmpty()) OnArrowChar( 0, event ); break; case WXK_PRIOR: { int steps = 0; if ( HasFlag(wxLC_REPORT) ) { steps = m_linesPerPage - 1; } else { steps = m_current % m_linesPerPage; } int index = m_current - steps; if (index < 0) index = 0; OnArrowChar( index, event ); } break; case WXK_NEXT: { int steps = 0; if ( HasFlag(wxLC_REPORT) ) { steps = m_linesPerPage - 1; } else { steps = m_linesPerPage - (m_current % m_linesPerPage) - 1; } size_t index = m_current + steps; size_t count = GetItemCount(); if ( index >= count ) index = count - 1; OnArrowChar( index, event ); } break; case WXK_LEFT: if ( !HasFlag(wxLC_REPORT) ) { int index = m_current - m_linesPerPage; if (index < 0) index = 0; OnArrowChar( index, event ); } break; case WXK_RIGHT: if ( !HasFlag(wxLC_REPORT) ) { size_t index = m_current + m_linesPerPage; size_t count = GetItemCount(); if ( index >= count ) index = count - 1; OnArrowChar( index, event ); } break; case WXK_SPACE: if ( IsSingleSel() ) { 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 { 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 ); } break; default: event.Skip(); } } // ---------------------------------------------------------------------------- // focus handling // ---------------------------------------------------------------------------- #ifdef __WXGTK__ extern wxWindow *g_focusWindow; #endif void wxListMainWindow::OnSetFocus( wxFocusEvent &WXUNUSED(event) ) { m_hasFocus = TRUE; if ( HasCurrent() ) RefreshLine( m_current ); if (!GetParent()) return; #ifdef __WXGTK__ g_focusWindow = GetParent(); #endif wxFocusEvent event( wxEVT_SET_FOCUS, GetParent()->GetId() ); event.SetEventObject( GetParent() ); GetParent()->GetEventHandler()->ProcessEvent( event ); } void wxListMainWindow::OnKillFocus( wxFocusEvent &WXUNUSED(event) ) { m_hasFocus = FALSE; if ( HasCurrent() ) RefreshLine( m_current ); } void wxListMainWindow::DrawImage( int index, wxDC *dc, int x, int y ) { if ( HasFlag(wxLC_ICON) && (m_normal_image_list)) { m_normal_image_list->Draw( index, *dc, x, y, wxIMAGELIST_DRAW_TRANSPARENT ); } else if ( HasFlag(wxLC_SMALL_ICON) && (m_small_image_list)) { m_small_image_list->Draw( index, *dc, x, y, wxIMAGELIST_DRAW_TRANSPARENT ); } else if ( HasFlag(wxLC_LIST) && (m_small_image_list)) { m_small_image_list->Draw( index, *dc, x, y, wxIMAGELIST_DRAW_TRANSPARENT ); } else if ( HasFlag(wxLC_REPORT) && (m_small_image_list)) { m_small_image_list->Draw( index, *dc, x, y, wxIMAGELIST_DRAW_TRANSPARENT ); } } void wxListMainWindow::GetImageSize( int index, int &width, int &height ) const { if ( HasFlag(wxLC_ICON) && m_normal_image_list ) { m_normal_image_list->GetSize( index, width, height ); } else if ( HasFlag(wxLC_SMALL_ICON) && m_small_image_list ) { m_small_image_list->GetSize( index, width, height ); } else if ( HasFlag(wxLC_LIST) && m_small_image_list ) { m_small_image_list->GetSize( index, width, height ); } else if ( HasFlag(wxLC_REPORT) && m_small_image_list ) { m_small_image_list->GetSize( index, width, height ); } else { width = height = 0; } } int wxListMainWindow::GetTextLength( const wxString &s ) const { wxClientDC dc( wxConstCast(this, wxListMainWindow) ); dc.SetFont( GetFont() ); wxCoord lw; dc.GetTextExtent( s, &lw, NULL ); return lw + AUTOSIZE_COL_MARGIN; } void wxListMainWindow::SetImageList( wxImageList *imageList, int which ) { m_dirty = TRUE; // calc the spacing from the icon size int width = 0, height = 0; if ((imageList) && (imageList->GetImageCount()) ) { imageList->GetSize(0, width, height); } if (which == wxIMAGE_LIST_NORMAL) { m_normal_image_list = imageList; m_normal_spacing = width + 8; } if (which == wxIMAGE_LIST_SMALL) { m_small_image_list = imageList; m_small_spacing = width + 14; } } void wxListMainWindow::SetItemSpacing( int spacing, bool isSmall ) { m_dirty = TRUE; if (isSmall) { m_small_spacing = spacing; } else { m_normal_spacing = spacing; } } int wxListMainWindow::GetItemSpacing( bool isSmall ) { return isSmall ? m_small_spacing : m_normal_spacing; } // ---------------------------------------------------------------------------- // columns // ---------------------------------------------------------------------------- void wxListMainWindow::SetColumn( int col, wxListItem &item ) { wxListHeaderDataList::Node *node = m_columns.Item( col ); wxCHECK_RET( node, _T("invalid column index in SetColumn") ); if ( item.m_width == wxLIST_AUTOSIZE_USEHEADER ) item.m_width = GetTextLength( item.m_text ); wxListHeaderData *column = node->GetData(); column->SetItem( item ); wxListHeaderWindow *headerWin = GetListCtrl()->m_headerWin; if ( headerWin ) headerWin->m_dirty = TRUE; m_dirty = TRUE; // invalidate it as it has to be recalculated m_headerWidth = 0; } void wxListMainWindow::SetColumnWidth( int col, int width ) { wxCHECK_RET( col >= 0 && col < GetColumnCount(), _T("invalid column index") ); wxCHECK_RET( HasFlag(wxLC_REPORT), _T("SetColumnWidth() can only be called in report mode.") ); m_dirty = TRUE; wxListHeaderDataList::Node *node = m_columns.Item( col ); wxCHECK_RET( node, _T("no column?") ); wxListHeaderData *column = node->GetData(); size_t count = GetItemCount(); if (width == wxLIST_AUTOSIZE_USEHEADER) { width = GetTextLength(column->GetText()); } else if ( width == wxLIST_AUTOSIZE ) { if ( IsVirtual() ) { // TODO: determine the max width somehow... width = WIDTH_COL_DEFAULT; } else // !virtual { wxClientDC dc(this); dc.SetFont( GetFont() ); int max = AUTOSIZE_COL_MARGIN; for ( size_t i = 0; i < count; i++ ) { wxListLineData *line = GetLine(i); wxListItemDataList::Node *n = line->m_items.Item( col ); wxCHECK_RET( n, _T("no subitem?") ); wxListItemData *item = n->GetData(); int current = 0; if (item->HasImage()) { int ix, iy; GetImageSize( item->GetImage(), ix, iy ); current += ix + 5; } if (item->HasText()) { wxCoord w; dc.GetTextExtent( item->GetText(), &w, NULL ); current += w; } if (current > max) max = current; } width = max + AUTOSIZE_COL_MARGIN; } } column->SetWidth( width ); // invalidate it as it has to be recalculated m_headerWidth = 0; } int wxListMainWindow::GetHeaderWidth() const { if ( !m_headerWidth ) { wxListMainWindow *self = wxConstCast(this, wxListMainWindow); size_t count = GetColumnCount(); for ( size_t col = 0; col < count; col++ ) { self->m_headerWidth += GetColumnWidth(col); } } return m_headerWidth; } void wxListMainWindow::GetColumn( int col, wxListItem &item ) const { wxListHeaderDataList::Node *node = m_columns.Item( col ); wxCHECK_RET( node, _T("invalid column index in GetColumn") ); wxListHeaderData *column = node->GetData(); column->GetItem( item ); } int wxListMainWindow::GetColumnWidth( int col ) const { wxListHeaderDataList::Node *node = m_columns.Item( col ); wxCHECK_MSG( node, 0, _T("invalid column index") ); wxListHeaderData *column = node->GetData(); return column->GetWidth(); } // ---------------------------------------------------------------------------- // item state // ---------------------------------------------------------------------------- void wxListMainWindow::SetItem( wxListItem &item ) { long id = item.m_itemId; wxCHECK_RET( id >= 0 && (size_t)id < GetItemCount(), _T("invalid item index in SetItem") ); if ( IsVirtual() ) { // just refresh the line to show the new value of the text/image RefreshLine((size_t)id); } else // !virtual { 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 ); } } void wxListMainWindow::SetItemState( long litem, long state, long stateMask ) { wxCHECK_RET( litem >= 0 && (size_t)litem < GetItemCount(), _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 if ( stateMask & wxLIST_STATE_FOCUSED ) { if ( state & wxLIST_STATE_FOCUSED ) { // don't do anything if this item is already focused if ( item != m_current ) { OnUnfocusLine( m_current ); m_current = item; OnFocusLine( m_current ); if ( IsSingleSel() && (oldCurrent != (size_t)-1) ) { HighlightLine(oldCurrent, FALSE); RefreshLine(oldCurrent); } RefreshLine( m_current ); } } else // unfocus { // don't do anything if this item is not focused if ( item == m_current ) { OnUnfocusLine( m_current ); m_current = (size_t)-1; } } } if ( stateMask & wxLIST_STATE_SELECTED ) { bool on = (state & wxLIST_STATE_SELECTED) != 0; if ( IsSingleSel() ) { if ( on ) { // selecting the item also makes it the focused one in the // single sel mode if ( m_current != item ) { OnUnfocusLine( m_current ); m_current = item; OnFocusLine( m_current ); if ( oldCurrent != (size_t)-1 ) { HighlightLine( oldCurrent, FALSE ); RefreshLine( oldCurrent ); } } } else // off { // only the current item may be selected anyhow if ( item != m_current ) return; } } if ( HighlightLine(item, on) ) { RefreshLine(item); } } } int wxListMainWindow::GetItemState( long item, long stateMask ) { wxCHECK_MSG( item >= 0 && (size_t)item < GetItemCount(), 0, _T("invalid list ctrl item index in GetItemState()") ); int ret = wxLIST_STATE_DONTCARE; if ( stateMask & wxLIST_STATE_FOCUSED ) { if ( (size_t)item == m_current ) ret |= wxLIST_STATE_FOCUSED; } if ( stateMask & wxLIST_STATE_SELECTED ) { if ( IsHighlighted(item) ) ret |= wxLIST_STATE_SELECTED; } return ret; } void wxListMainWindow::GetItem( wxListItem &item ) { wxCHECK_RET( item.m_itemId >= 0 && (size_t)item.m_itemId < GetItemCount(), _T("invalid item index in GetItem") ); wxListLineData *line = GetLine((size_t)item.m_itemId); line->GetItem( item.m_col, item ); } // ---------------------------------------------------------------------------- // item count // ---------------------------------------------------------------------------- size_t wxListMainWindow::GetItemCount() const { return IsVirtual() ? m_countVirt : m_lines.GetCount(); } void wxListMainWindow::SetItemCount(long count) { m_selStore.SetItemCount(count); m_countVirt = count; Refresh(); } int wxListMainWindow::GetSelectedItemCount() { // deal with the quick case first if ( IsSingleSel() ) { return HasCurrent() ? IsHighlighted(m_current) : FALSE; } // virtual controls remmebers all its selections itself if ( IsVirtual() ) 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... size_t countSel = 0; size_t count = GetItemCount(); for ( size_t line = 0; line < count; line++ ) { if ( GetLine(line)->IsHighlighted() ) countSel++; } return countSel; } // ---------------------------------------------------------------------------- // item position/size // ---------------------------------------------------------------------------- void wxListMainWindow::GetItemRect( long index, wxRect &rect ) { wxCHECK_RET( index >= 0 && (size_t)index < GetItemCount(), _T("invalid index in GetItemRect") ); rect = GetLineRect((size_t)index); CalcScrolledPosition(rect.x, rect.y, &rect.x, &rect.y); } bool wxListMainWindow::GetItemPosition(long item, wxPoint& pos) { wxRect rect; GetItemRect(item, rect); pos.x = rect.x; pos.y = rect.y; return TRUE; } // ---------------------------------------------------------------------------- // geometry calculation // ---------------------------------------------------------------------------- void wxListMainWindow::RecalculatePositions() { if ( IsEmpty() ) return; wxClientDC dc( this ); dc.SetFont( GetFont() ); int iconSpacing; if ( HasFlag(wxLC_ICON) ) iconSpacing = m_normal_spacing; else if ( HasFlag(wxLC_SMALL_ICON) ) iconSpacing = m_small_spacing; else iconSpacing = 0; int clientWidth, clientHeight; GetClientSize( &clientWidth, &clientHeight ); if ( HasFlag(wxLC_REPORT) ) { // all lines have the same height int lineHeight = GetLineHeight(); // scroll one line per step m_yScroll = lineHeight; size_t lineCount = GetItemCount(); int entireHeight = lineCount*lineHeight + LINE_SPACING; m_linesPerPage = clientHeight / lineHeight; ResetVisibleLinesRange(); SetScrollbars( m_xScroll, m_yScroll, (GetHeaderWidth() + m_xScroll - 1)/m_xScroll, (entireHeight + m_yScroll - 1)/m_yScroll, GetScrollPos(wxHORIZONTAL), GetScrollPos(wxVERTICAL), TRUE ); } else // !report { // at first we try without any scrollbar. if the items don't // fit into the window, we recalculate after subtracting an // approximated 15 pt for the horizontal scrollbar clientHeight -= 4; // sunken frame int entireWidth = 0; for (int tries = 0; tries < 2; tries++) { entireWidth = 0; int x = 2; int y = 2; int maxWidth = 0; m_linesPerPage = 0; int currentlyVisibleLines = 0; size_t count = GetItemCount(); for (size_t i = 0; i < count; i++) { currentlyVisibleLines++; wxListLineData *line = GetLine(i); line->CalculateSize( &dc, iconSpacing ); line->SetPosition( x, y, clientWidth, iconSpacing ); wxSize sizeLine = GetLineSize(i); if ( maxWidth < sizeLine.x ) maxWidth = sizeLine.x; y += sizeLine.y; 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 ) { currentlyVisibleLines = 0; y = 2; x += maxWidth+6; entireWidth += maxWidth+6; maxWidth = 0; } if ( i == count - 1 ) entireWidth += maxWidth; if ((tries == 0) && (entireWidth > clientWidth)) { clientHeight -= 15; // scrollbar height m_linesPerPage = 0; currentlyVisibleLines = 0; break; } if ( i == count - 1 ) tries = 1; // everything fits, no second try required } } int scroll_pos = GetScrollPos( wxHORIZONTAL ); 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(); RefreshAll(); } void wxListMainWindow::RefreshAll() { m_dirty = FALSE; Refresh(); wxListHeaderWindow *headerWin = GetListCtrl()->m_headerWin; if ( headerWin ) { headerWin->m_dirty = FALSE; headerWin->Refresh(); } } void wxListMainWindow::UpdateCurrent() { if ( !HasCurrent() && !IsEmpty() ) { m_current = 0; } if ( m_current != (size_t)-1 ) { OnFocusLine( m_current ); } } long wxListMainWindow::GetNextItem( long item, int WXUNUSED(geometry), int state ) { long ret = item, max = GetItemCount(); wxCHECK_MSG( (ret == -1) || (ret < max), -1, _T("invalid listctrl index in GetNextItem()") ); // notice that we start with the next item (or the first one if item == -1) // and this is intentional to allow writing a simple loop to iterate over // all selected items ret++; if ( ret == max ) { // this is not an error because the index was ok initially, just no // such item return -1; } if ( !state ) { // any will do return (size_t)ret; } size_t count = GetItemCount(); for ( size_t line = (size_t)ret; line < count; line++ ) { if ( (state & wxLIST_STATE_FOCUSED) && (line == m_current) ) return line; if ( (state & wxLIST_STATE_SELECTED) && IsHighlighted(line) ) return line; } return -1; } // ---------------------------------------------------------------------------- // deleting stuff // ---------------------------------------------------------------------------- void wxListMainWindow::DeleteItem( long lindex ) { size_t count = GetItemCount(); wxCHECK_RET( (lindex >= 0) && ((size_t)lindex < count), _T("invalid item index in DeleteItem") ); size_t index = (size_t)lindex; m_dirty = TRUE; // select the next item when the selected one is deleted if ( m_current == index ) { // the last valid index after deleting the item will be count-2 if ( m_current == count - 1 ) { m_current--; } } SendNotify( index, wxEVT_COMMAND_LIST_DELETE_ITEM ); if ( InReportView() ) { ResetVisibleLinesRange(); } if ( IsVirtual() ) { m_countVirt--; m_selStore.OnItemDelete(index); } else { m_lines.RemoveAt( index ); } m_dirty = TRUE; RefreshAfter(index); } void wxListMainWindow::DeleteColumn( int col ) { wxListHeaderDataList::Node *node = m_columns.Item( col ); wxCHECK_RET( node, wxT("invalid column index in DeleteColumn()") ); m_dirty = TRUE; m_columns.DeleteNode( node ); } void wxListMainWindow::DeleteAllItems() { if ( IsEmpty() ) { // nothing to do - in particular, don't send the event return; } m_dirty = TRUE; 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 // for all of them: this is compatible with wxMSW and documented in // DeleteAllItems() description wxListEvent event( wxEVT_COMMAND_LIST_DELETE_ALL_ITEMS, GetParent()->GetId() ); event.SetEventObject( GetParent() ); GetParent()->GetEventHandler()->ProcessEvent( event ); if ( IsVirtual() ) { m_countVirt = 0; ResetVisibleLinesRange(); } if ( InReportView() ) { ResetVisibleLinesRange(); } m_lines.Clear(); m_selStore.Clear(); } void wxListMainWindow::DeleteEverything() { DeleteAllItems(); m_columns.Clear(); } // ---------------------------------------------------------------------------- // scanning for an item // ---------------------------------------------------------------------------- void wxListMainWindow::EnsureVisible( long index ) { wxCHECK_RET( index >= 0 && (size_t)index < GetItemCount(), _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(); size_t oldCurrent = m_current; m_current = (size_t)index; MoveToFocus(); m_current = oldCurrent; } long wxListMainWindow::FindItem(long start, const wxString& str, bool WXUNUSED(partial) ) { long pos = start; wxString tmp = str; if (pos < 0) pos = 0; size_t count = GetItemCount(); for ( size_t i = (size_t)pos; i < count; i++ ) { wxListLineData *line = GetLine(i); if ( line->GetText(0) == tmp ) return i; } return wxNOT_FOUND; } long wxListMainWindow::FindItem(long start, long data) { long pos = start; if (pos < 0) pos = 0; size_t count = GetItemCount(); for (size_t i = (size_t)pos; i < count; i++) { wxListLineData *line = GetLine(i); wxListItem item; line->GetItem( 0, item ); if (item.m_data == data) return i; } return wxNOT_FOUND; } long wxListMainWindow::HitTest( int x, int y, int &flags ) { CalcUnscrolledPosition( x, y, &x, &y ); if ( HasFlag(wxLC_REPORT) ) { size_t current = y / GetLineHeight(); flags = HitTestLine(current, x, y); if ( flags ) return current; } else // !report { // TODO: optimize it too! this is less simple than for report view but // enumerating all items is still not a way to do it!! size_t count = GetItemCount(); for ( size_t current = 0; current < count; current++ ) { flags = HitTestLine(current, x, y); if ( flags ) return current; } } return wxNOT_FOUND; } // ---------------------------------------------------------------------------- // adding stuff // ---------------------------------------------------------------------------- void wxListMainWindow::InsertItem( wxListItem &item ) { wxASSERT_MSG( !IsVirtual(), _T("can't be used with virtual control") ); size_t count = GetItemCount(); wxCHECK_RET( item.m_itemId >= 0 && (size_t)item.m_itemId <= count, _T("invalid item index") ); size_t id = item.m_itemId; m_dirty = TRUE; int mode = 0; if ( HasFlag(wxLC_REPORT) ) mode = wxLC_REPORT; else if ( HasFlag(wxLC_LIST) ) mode = wxLC_LIST; else if ( HasFlag(wxLC_ICON) ) mode = wxLC_ICON; else if ( HasFlag(wxLC_SMALL_ICON) ) mode = wxLC_ICON; // no typo else { wxFAIL_MSG( _T("unknown mode") ); } 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 ) { m_dirty = TRUE; if ( HasFlag(wxLC_REPORT) ) { if (item.m_width == wxLIST_AUTOSIZE_USEHEADER) item.m_width = GetTextLength( item.m_text ); wxListHeaderData *column = new wxListHeaderData( item ); if ((col >= 0) && (col < (int)m_columns.GetCount())) { wxListHeaderDataList::Node *node = m_columns.Item( col ); m_columns.Insert( node, column ); } else { m_columns.Append( column ); } } } // ---------------------------------------------------------------------------- // sorting // ---------------------------------------------------------------------------- wxListCtrlCompare list_ctrl_compare_func_2; long list_ctrl_compare_data; int LINKAGEMODE list_ctrl_compare_func_1( wxListLineData **arg1, wxListLineData **arg2 ) { wxListLineData *line1 = *arg1; wxListLineData *line2 = *arg2; wxListItem item; line1->GetItem( 0, item ); long data1 = item.m_data; line2->GetItem( 0, item ); long data2 = item.m_data; return list_ctrl_compare_func_2( data1, data2, list_ctrl_compare_data ); } void wxListMainWindow::SortItems( wxListCtrlCompare fn, long data ) { list_ctrl_compare_func_2 = fn; list_ctrl_compare_data = data; m_lines.Sort( list_ctrl_compare_func_1 ); m_dirty = TRUE; } // ---------------------------------------------------------------------------- // scrolling // ---------------------------------------------------------------------------- void wxListMainWindow::OnScroll(wxScrollWinEvent& event) { // update our idea of which lines are shown when we redraw the window the // next time ResetVisibleLinesRange(); // FIXME #if defined(__WXGTK__) && !defined(__WXUNIVERSAL__) wxScrolledWindow::OnScroll(event); #else HandleOnScroll( event ); #endif if ( event.GetOrientation() == wxHORIZONTAL && HasHeader() ) { wxListCtrl* lc = GetListCtrl(); wxCHECK_RET( lc, _T("no listctrl window?") ); lc->m_headerWin->Refresh() ; #ifdef __WXMAC__ lc->m_headerWin->MacUpdateImmediately() ; #endif } } int wxListMainWindow::GetCountPerPage() const { if ( !m_linesPerPage ) { wxConstCast(this, wxListMainWindow)-> m_linesPerPage = GetClientSize().y / GetLineHeight(); } return m_linesPerPage; } void wxListMainWindow::GetVisibleLinesRange(size_t *from, size_t *to) { wxASSERT_MSG( HasFlag(wxLC_REPORT), _T("this is for report mode only") ); if ( m_lineFrom == (size_t)-1 ) { size_t count = GetItemCount(); if ( count ) { m_lineFrom = GetScrollPos(wxVERTICAL); // this may happen if SetScrollbars() hadn't been called yet if ( m_lineFrom >= count ) m_lineFrom = count - 1; // 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; } // ------------------------------------------------------------------------------------- // wxListItem // ------------------------------------------------------------------------------------- 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; } void wxListItem::Clear() { 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_text = _T(""); ClearAttributes(); } void wxListItem::ClearAttributes() { if (m_attr) { delete m_attr; m_attr = NULL; } } // ------------------------------------------------------------------------------------- // 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) BEGIN_EVENT_TABLE(wxListCtrl,wxControl) EVT_SIZE(wxListCtrl::OnSize) EVT_IDLE(wxListCtrl::OnIdle) END_EVENT_TABLE() wxListCtrl::wxListCtrl() { m_imageListNormal = (wxImageList *) NULL; m_imageListSmall = (wxImageList *) NULL; m_imageListState = (wxImageList *) NULL; m_ownsImageListNormal = m_ownsImageListSmall = m_ownsImageListState = FALSE; m_mainWin = (wxListMainWindow*) NULL; m_headerWin = (wxListHeaderWindow*) NULL; } wxListCtrl::~wxListCtrl() { if ( m_mainWin ) m_mainWin->ResetCurrent(); if (m_ownsImageListNormal) delete m_imageListNormal; if (m_ownsImageListSmall) delete m_imageListSmall; if (m_ownsImageListState) delete m_imageListState; } void wxListCtrl::CreateHeaderWindow() { m_headerWin = new wxListHeaderWindow ( this, -1, m_mainWin, wxPoint(0, 0), wxSize(GetClientSize().x, HEADER_HEIGHT), wxTAB_TRAVERSAL ); } bool wxListCtrl::Create(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, const wxValidator &validator, const wxString &name) { m_imageListNormal = m_imageListSmall = m_imageListState = (wxImageList *) NULL; m_ownsImageListNormal = m_ownsImageListSmall = m_ownsImageListState = FALSE; m_mainWin = (wxListMainWindow*) NULL; m_headerWin = (wxListHeaderWindow*) NULL; if ( !(style & wxLC_MASK_TYPE) ) { style = style | wxLC_LIST; } if ( !wxControl::Create( parent, id, pos, size, style, validator, name ) ) return FALSE; // 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) ) { CreateHeaderWindow(); if ( HasFlag(wxLC_NO_HEADER) ) { // VZ: why do we create it at all then? m_headerWin->Show( FALSE ); } } return 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 | wxLC_VIRTUAL); if (style & wxLC_MASK_ALIGN) flag &= ~wxLC_MASK_ALIGN; if (style & wxLC_MASK_SORT) flag &= ~wxLC_MASK_SORT; } if (add) { flag |= style; } else { flag &= ~style; } SetWindowStyleFlag( flag ); } void wxListCtrl::SetWindowStyleFlag( long flag ) { if (m_mainWin) { m_mainWin->DeleteEverything(); int width = 0; int height = 0; GetClientSize( &width, &height ); if (flag & wxLC_REPORT) { if (!HasFlag(wxLC_REPORT)) { if (!m_headerWin) { CreateHeaderWindow(); if (HasFlag(wxLC_NO_HEADER)) m_headerWin->Show( FALSE ); } else { if (flag & wxLC_NO_HEADER) m_headerWin->Show( FALSE ); else m_headerWin->Show( TRUE ); } } } else // !report { if ( m_mainWin->HasHeader() ) { m_headerWin->Show( FALSE ); } } } wxWindow::SetWindowStyleFlag( flag ); } bool wxListCtrl::GetColumn(int col, wxListItem &item) const { m_mainWin->GetColumn( col, item ); return TRUE; } bool wxListCtrl::SetColumn( int col, wxListItem& item ) { m_mainWin->SetColumn( col, item ); return TRUE; } int wxListCtrl::GetColumnWidth( int col ) const { return m_mainWin->GetColumnWidth( col ); } bool wxListCtrl::SetColumnWidth( int col, int width ) { m_mainWin->SetColumnWidth( col, width ); return TRUE; } int wxListCtrl::GetCountPerPage() const { return m_mainWin->GetCountPerPage(); // different from Windows ? } bool wxListCtrl::GetItem( wxListItem &info ) const { m_mainWin->GetItem( info ); return TRUE; } bool wxListCtrl::SetItem( wxListItem &info ) { m_mainWin->SetItem( info ); return TRUE; } long wxListCtrl::SetItem( long index, int col, const wxString& label, int imageId ) { wxListItem info; info.m_text = label; info.m_mask = wxLIST_MASK_TEXT; info.m_itemId = index; info.m_col = col; if ( imageId > -1 ) { info.m_image = imageId; info.m_mask |= wxLIST_MASK_IMAGE; }; m_mainWin->SetItem(info); return TRUE; } int wxListCtrl::GetItemState( long item, long stateMask ) const { return m_mainWin->GetItemState( item, stateMask ); } bool wxListCtrl::SetItemState( long item, long state, long stateMask ) { m_mainWin->SetItemState( item, state, stateMask ); return TRUE; } bool wxListCtrl::SetItemImage( long item, int image, int WXUNUSED(selImage) ) { wxListItem info; info.m_image = image; info.m_mask = wxLIST_MASK_IMAGE; info.m_itemId = item; m_mainWin->SetItem( info ); return TRUE; } wxString wxListCtrl::GetItemText( long item ) const { wxListItem info; info.m_itemId = item; m_mainWin->GetItem( info ); return info.m_text; } void wxListCtrl::SetItemText( long item, const wxString &str ) { wxListItem info; info.m_mask = wxLIST_MASK_TEXT; info.m_itemId = item; info.m_text = str; m_mainWin->SetItem( info ); } long wxListCtrl::GetItemData( long item ) const { wxListItem info; info.m_itemId = item; m_mainWin->GetItem( info ); return info.m_data; } bool wxListCtrl::SetItemData( long item, long data ) { wxListItem info; info.m_mask = wxLIST_MASK_DATA; info.m_itemId = item; info.m_data = data; m_mainWin->SetItem( info ); return TRUE; } bool wxListCtrl::GetItemRect( long item, wxRect &rect, int WXUNUSED(code) ) const { m_mainWin->GetItemRect( item, rect ); return TRUE; } bool wxListCtrl::GetItemPosition( long item, wxPoint& pos ) const { m_mainWin->GetItemPosition( item, pos ); return TRUE; } bool wxListCtrl::SetItemPosition( long WXUNUSED(item), const wxPoint& WXUNUSED(pos) ) { return 0; } int wxListCtrl::GetItemCount() const { return m_mainWin->GetItemCount(); } int wxListCtrl::GetColumnCount() const { return m_mainWin->GetColumnCount(); } void wxListCtrl::SetItemSpacing( int spacing, bool isSmall ) { m_mainWin->SetItemSpacing( spacing, isSmall ); } int wxListCtrl::GetItemSpacing( bool isSmall ) const { return m_mainWin->GetItemSpacing( isSmall ); } int wxListCtrl::GetSelectedItemCount() const { return m_mainWin->GetSelectedItemCount(); } wxColour wxListCtrl::GetTextColour() const { return GetForegroundColour(); } void wxListCtrl::SetTextColour(const wxColour& col) { SetForegroundColour(col); } long wxListCtrl::GetTopItem() const { return 0; } long wxListCtrl::GetNextItem( long item, int geom, int state ) const { return m_mainWin->GetNextItem( item, geom, state ); } wxImageList *wxListCtrl::GetImageList(int which) const { if (which == wxIMAGE_LIST_NORMAL) { return m_imageListNormal; } else if (which == wxIMAGE_LIST_SMALL) { return m_imageListSmall; } else if (which == wxIMAGE_LIST_STATE) { return m_imageListState; } return (wxImageList *) NULL; } void wxListCtrl::SetImageList( wxImageList *imageList, int which ) { if ( which == wxIMAGE_LIST_NORMAL ) { if (m_ownsImageListNormal) delete m_imageListNormal; m_imageListNormal = imageList; m_ownsImageListNormal = FALSE; } else if ( which == wxIMAGE_LIST_SMALL ) { if (m_ownsImageListSmall) delete m_imageListSmall; m_imageListSmall = imageList; m_ownsImageListSmall = FALSE; } else if ( which == wxIMAGE_LIST_STATE ) { if (m_ownsImageListState) delete m_imageListState; m_imageListState = imageList; m_ownsImageListState = FALSE; } m_mainWin->SetImageList( imageList, which ); } void wxListCtrl::AssignImageList(wxImageList *imageList, int which) { SetImageList(imageList, which); if ( which == wxIMAGE_LIST_NORMAL ) m_ownsImageListNormal = TRUE; else if ( which == wxIMAGE_LIST_SMALL ) m_ownsImageListSmall = TRUE; else if ( which == wxIMAGE_LIST_STATE ) m_ownsImageListState = TRUE; } bool wxListCtrl::Arrange( int WXUNUSED(flag) ) { return 0; } bool wxListCtrl::DeleteItem( long item ) { m_mainWin->DeleteItem( item ); return TRUE; } bool wxListCtrl::DeleteAllItems() { m_mainWin->DeleteAllItems(); return TRUE; } bool wxListCtrl::DeleteAllColumns() { size_t count = m_mainWin->m_columns.GetCount(); for ( size_t n = 0; n < count; n++ ) DeleteColumn(n); return TRUE; } void wxListCtrl::ClearAll() { m_mainWin->DeleteEverything(); } bool wxListCtrl::DeleteColumn( int col ) { m_mainWin->DeleteColumn( col ); return TRUE; } void wxListCtrl::Edit( long item ) { m_mainWin->EditLabel( item ); } bool wxListCtrl::EnsureVisible( long item ) { m_mainWin->EnsureVisible( item ); return TRUE; } long wxListCtrl::FindItem( long start, const wxString& str, bool partial ) { return m_mainWin->FindItem( start, str, partial ); } long wxListCtrl::FindItem( long start, long data ) { return m_mainWin->FindItem( start, data ); } long wxListCtrl::FindItem( long WXUNUSED(start), const wxPoint& WXUNUSED(pt), int WXUNUSED(direction)) { return 0; } long wxListCtrl::HitTest( const wxPoint &point, int &flags ) { return m_mainWin->HitTest( (int)point.x, (int)point.y, flags ); } long wxListCtrl::InsertItem( wxListItem& info ) { m_mainWin->InsertItem( info ); return info.m_itemId; } long wxListCtrl::InsertItem( long index, const wxString &label ) { wxListItem info; info.m_text = label; info.m_mask = wxLIST_MASK_TEXT; info.m_itemId = index; return InsertItem( info ); } long wxListCtrl::InsertItem( long index, int imageIndex ) { wxListItem info; info.m_mask = wxLIST_MASK_IMAGE; info.m_image = imageIndex; info.m_itemId = index; return InsertItem( info ); } long wxListCtrl::InsertItem( long index, const wxString &label, int imageIndex ) { wxListItem info; info.m_text = label; info.m_image = imageIndex; info.m_mask = wxLIST_MASK_TEXT | wxLIST_MASK_IMAGE; info.m_itemId = index; return InsertItem( info ); } long wxListCtrl::InsertColumn( long col, wxListItem &item ) { wxASSERT( m_headerWin ); m_mainWin->InsertColumn( col, item ); m_headerWin->Refresh(); return 0; } long wxListCtrl::InsertColumn( long col, const wxString &heading, int format, int width ) { wxListItem item; item.m_mask = wxLIST_MASK_TEXT | wxLIST_MASK_FORMAT; item.m_text = heading; if (width >= -2) { item.m_mask |= wxLIST_MASK_WIDTH; item.m_width = width; } item.m_format = format; return InsertColumn( col, item ); } bool wxListCtrl::ScrollList( int WXUNUSED(dx), int WXUNUSED(dy) ) { return 0; } // Sort items. // fn is a function which takes 3 long arguments: item1, item2, data. // item1 is the long data associated with a first item (NOT the index). // item2 is the long data associated with a second item (NOT the index). // data is the same value as passed to SortItems. // The return value is a negative number if the first item should precede the second // item, a positive number of the second item should precede the first, // or zero if the two items are equivalent. // data is arbitrary data to be passed to the sort function. bool wxListCtrl::SortItems( wxListCtrlCompare fn, long data ) { m_mainWin->SortItems( fn, data ); return TRUE; } // ---------------------------------------------------------------------------- // event handlers // ---------------------------------------------------------------------------- void wxListCtrl::OnSize(wxSizeEvent& event) { if ( !m_mainWin ) return; int cw, ch; GetClientSize( &cw, &ch ); if ( m_mainWin->HasHeader() ) { m_headerWin->SetSize( 0, 0, cw, HEADER_HEIGHT ); m_mainWin->SetSize( 0, HEADER_HEIGHT + 1, cw, ch - HEADER_HEIGHT - 1 ); } else // no header window { m_mainWin->SetSize( 0, 0, cw, ch ); } m_mainWin->RecalculatePositions(); } void wxListCtrl::OnIdle( wxIdleEvent & event ) { event.Skip(); // do it only if needed if ( !m_mainWin->m_dirty ) return; m_mainWin->RecalculatePositions(); } // ---------------------------------------------------------------------------- // font/colours // ---------------------------------------------------------------------------- bool wxListCtrl::SetBackgroundColour( const wxColour &colour ) { if (m_mainWin) { m_mainWin->SetBackgroundColour( colour ); m_mainWin->m_dirty = TRUE; } return TRUE; } bool wxListCtrl::SetForegroundColour( const wxColour &colour ) { if ( !wxWindow::SetForegroundColour( colour ) ) return FALSE; if (m_mainWin) { m_mainWin->SetForegroundColour( colour ); m_mainWin->m_dirty = TRUE; } if (m_headerWin) { m_headerWin->SetForegroundColour( colour ); } return TRUE; } bool wxListCtrl::SetFont( const wxFont &font ) { if ( !wxWindow::SetFont( font ) ) return FALSE; if (m_mainWin) { m_mainWin->SetFont( font ); m_mainWin->m_dirty = TRUE; } if (m_headerWin) { m_headerWin->SetFont( font ); } return TRUE; } // ---------------------------------------------------------------------------- // methods forwarded to m_mainWin // ---------------------------------------------------------------------------- #if wxUSE_DRAG_AND_DROP void wxListCtrl::SetDropTarget( wxDropTarget *dropTarget ) { m_mainWin->SetDropTarget( dropTarget ); } wxDropTarget *wxListCtrl::GetDropTarget() const { return m_mainWin->GetDropTarget(); } #endif // wxUSE_DRAG_AND_DROP bool wxListCtrl::SetCursor( const wxCursor &cursor ) { return m_mainWin ? m_mainWin->wxWindow::SetCursor(cursor) : FALSE; } wxColour wxListCtrl::GetBackgroundColour() const { return m_mainWin ? m_mainWin->GetBackgroundColour() : wxColour(); } wxColour wxListCtrl::GetForegroundColour() const { return m_mainWin ? m_mainWin->GetForegroundColour() : wxColour(); } bool wxListCtrl::DoPopupMenu( wxMenu *menu, int x, int y ) { #if wxUSE_MENUS return m_mainWin->PopupMenu( menu, x, y ); #else return FALSE; #endif // wxUSE_MENUS } void wxListCtrl::SetFocus() { /* The test in window.cpp fails as we are a composite window, so it checks against "this", but not m_mainWin. */ if ( FindFocus() != this ) m_mainWin->SetFocus(); } // ---------------------------------------------------------------------------- // virtual list control support // ---------------------------------------------------------------------------- wxString wxListCtrl::OnGetItemText(long item, long col) const { // this is a pure virtual function, in fact - which is not really pure // because the controls which are not virtual don't need to implement it wxFAIL_MSG( _T("not supposed to be called") ); return wxEmptyString; } int wxListCtrl::OnGetItemImage(long item) const { // same as above wxFAIL_MSG( _T("not supposed to be called") ); return -1; } void wxListCtrl::SetItemCount(long count) { wxASSERT_MSG( IsVirtual(), _T("this is for virtual controls only") ); m_mainWin->SetItemCount(count); } #endif // wxUSE_LISTCTRL