X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/70541533c8e108249a82fbe2bc05ccfb73bf028d..5a8f04e382a2b5ebeb50e8719889910f813b9d11:/src/generic/listctrl.cpp?ds=inline diff --git a/src/generic/listctrl.cpp b/src/generic/listctrl.cpp index 5609fdd997..35d5d712b8 100644 --- a/src/generic/listctrl.cpp +++ b/src/generic/listctrl.cpp @@ -12,7 +12,7 @@ TODO 1. we need to implement searching/sorting for virtual controls somehow - 2. when changing selection the lines are refreshed twice + ?2. when changing selection the lines are refreshed twice */ // ============================================================================ @@ -65,6 +65,10 @@ DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_DESELECTED) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_KEY_DOWN) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_INSERT_ITEM) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_COL_CLICK) +DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_COL_RIGHT_CLICK) +DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_COL_BEGIN_DRAG) +DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_COL_DRAGGING) +DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_COL_END_DRAG) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_MIDDLE_CLICK) DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_ACTIVATED) @@ -99,6 +103,9 @@ static const int AUTOSIZE_COL_MARGIN = 10; static const int WIDTH_COL_DEFAULT = 80; static const int WIDTH_COL_MIN = 10; +// the space between the image and the text in the report mode +static const int IMAGE_MARGIN_IN_REPORT_MODE = 5; + // ============================================================================ // private classes // ============================================================================ @@ -257,16 +264,6 @@ protected: class WXDLLEXPORT wxListHeaderData : public wxObject { -protected: - long m_mask; - int m_image; - wxString m_text; - int m_format; - int m_width; - int m_xpos, - m_ypos; - int m_height; - public: wxListHeaderData(); wxListHeaderData( const wxListItem &info ); @@ -288,8 +285,18 @@ public: int GetWidth() const; int GetFormat() const; +protected: + long m_mask; + int m_image; + wxString m_text; + int m_format; + int m_width; + int m_xpos, + m_ypos; + int m_height; + private: - DECLARE_DYNAMIC_CLASS(wxListHeaderData); + void Init(); }; //----------------------------------------------------------------------------- @@ -432,7 +439,6 @@ protected: public: wxListHeaderWindow(); - virtual ~wxListHeaderWindow(); wxListHeaderWindow( wxWindow *win, wxWindowID id, @@ -442,6 +448,8 @@ public: long style = 0, const wxString &name = "wxlistctrlcolumntitles" ); + virtual ~wxListHeaderWindow(); + void DoDrawRect( wxDC *dc, int x, int y, int w, int h ); void DrawCurrent(); void AdjustDC(wxDC& dc); @@ -454,6 +462,9 @@ public: bool m_dirty; private: + // common part of all ctors + void Init(); + DECLARE_DYNAMIC_CLASS(wxListHeaderWindow) DECLARE_EVENT_TABLE() }; @@ -562,6 +573,9 @@ public: void RefreshLine( size_t line ); void RefreshLines( size_t lineFrom, size_t lineTo ); + // refresh all selected items + void RefreshSelected(); + // refresh all lines below the given one: the difference with // RefreshLines() is that the index here might not be a valid one (happens // when the last line is deleted) @@ -697,6 +711,12 @@ public: // get the y position of the given line (only for report view) wxCoord GetLineY(size_t line) const; + // get the brush to use for the item highlighting + wxBrush *GetHighlightBrush() const + { + return m_hasFocus ? m_highlightBrush : m_highlightUnfocusedBrush; + } + //protected: // the array of all line objects for a non virtual list control wxListLineDataArray m_lines; @@ -721,7 +741,6 @@ public: // call bool m_dirty; - wxBrush *m_highlightBrush; wxColour *m_highlightColour; int m_xScroll, m_yScroll; @@ -820,6 +839,10 @@ private: size_t m_lineFrom, m_lineTo; + // the brushes to use for item highlighting when we do/don't have focus + wxBrush *m_highlightBrush, + *m_highlightUnfocusedBrush; + DECLARE_DYNAMIC_CLASS(wxListMainWindow); DECLARE_EVENT_TABLE() }; @@ -1143,12 +1166,10 @@ void wxListItemData::GetItem( wxListItem &info ) const // wxListHeaderData //----------------------------------------------------------------------------- -IMPLEMENT_DYNAMIC_CLASS(wxListHeaderData,wxObject); - -wxListHeaderData::wxListHeaderData() +void wxListHeaderData::Init() { m_mask = 0; - m_image = 0; + m_image = -1; m_format = 0; m_width = 0; m_xpos = 0; @@ -1156,22 +1177,33 @@ wxListHeaderData::wxListHeaderData() m_height = 0; } +wxListHeaderData::wxListHeaderData() +{ + Init(); +} + wxListHeaderData::wxListHeaderData( const wxListItem &item ) { + Init(); + SetItem( item ); - m_xpos = 0; - m_ypos = 0; - m_height = 0; } void wxListHeaderData::SetItem( const wxListItem &item ) { m_mask = item.m_mask; - m_text = item.m_text; - m_image = item.m_image; - m_format = item.m_format; - SetWidth(item.m_width); + if ( m_mask & wxLIST_MASK_TEXT ) + m_text = item.m_text; + + if ( m_mask & wxLIST_MASK_IMAGE ) + m_image = item.m_image; + + if ( m_mask & wxLIST_MASK_FORMAT ) + m_format = item.m_format; + + if ( m_mask & wxLIST_MASK_WIDTH ) + SetWidth(item.m_width); } void wxListHeaderData::SetPosition( int x, int y ) @@ -1190,7 +1222,7 @@ void wxListHeaderData::SetWidth( int w ) m_width = w; if (m_width < 0) m_width = WIDTH_COL_DEFAULT; - if (m_width < WIDTH_COL_MIN) + else if (m_width < WIDTH_COL_MIN) m_width = WIDTH_COL_MIN; } @@ -1201,7 +1233,7 @@ void wxListHeaderData::SetFormat( int format ) bool wxListHeaderData::HasImage() const { - return (m_image != 0); + return m_image != -1; } bool wxListHeaderData::IsHit( int x, int y ) const @@ -1209,7 +1241,7 @@ bool wxListHeaderData::IsHit( int x, int y ) const return ((x >= m_xpos) && (x <= m_xpos+m_width) && (y >= m_ypos) && (y <= m_ypos+m_height)); } -void wxListHeaderData::GetItem( wxListItem &item ) +void wxListHeaderData::GetItem( wxListItem& item ) { item.m_mask = m_mask; item.m_text = m_text; @@ -1582,7 +1614,7 @@ bool wxListLineData::SetAttributes(wxDC *dc, { if ( highlighted ) { - dc->SetBrush( *m_owner->m_highlightBrush ); + dc->SetBrush( *m_owner->GetHighlightBrush() ); } else { @@ -1622,6 +1654,8 @@ void wxListLineData::Draw( wxDC *dc ) if (item->HasText()) { wxRect rectLabel = m_gi->m_rectLabel; + + wxDCClipper clipper(*dc, rectLabel); dc->DrawText( item->GetText(), rectLabel.x, rectLabel.y ); } } @@ -1651,27 +1685,29 @@ void wxListLineData::DrawInReportMode( wxDC *dc, { wxListItemData *item = node->GetData(); + int width = m_owner->GetColumnWidth(col++); int xOld = x; + x += width; if ( item->HasImage() ) { int ix, iy; - m_owner->DrawImage( item->GetImage(), dc, x, y ); + m_owner->DrawImage( item->GetImage(), dc, xOld, y ); m_owner->GetImageSize( item->GetImage(), ix, iy ); - x += ix + 5; // FIXME: what is "5"? - } - int width = m_owner->GetColumnWidth(col++); + ix += IMAGE_MARGIN_IN_REPORT_MODE; + + xOld += ix; + width -= ix; + } - wxDCClipper clipper(*dc, x, y, width, rect.height); + wxDCClipper clipper(*dc, xOld, y, width, rect.height); if ( item->HasText() ) { - dc->DrawText( item->GetText(), x, y ); + dc->DrawText( item->GetText(), xOld, y ); } - x = xOld + width; - node = node->GetNext(); } } @@ -1705,30 +1741,39 @@ BEGIN_EVENT_TABLE(wxListHeaderWindow,wxWindow) EVT_SET_FOCUS (wxListHeaderWindow::OnSetFocus) END_EVENT_TABLE() -wxListHeaderWindow::wxListHeaderWindow( void ) +void wxListHeaderWindow::Init() { - m_owner = (wxListMainWindow *) NULL; m_currentCursor = (wxCursor *) NULL; - m_resizeCursor = (wxCursor *) NULL; m_isDragging = FALSE; + m_dirty = FALSE; } -wxListHeaderWindow::wxListHeaderWindow( wxWindow *win, wxWindowID id, wxListMainWindow *owner, - const wxPoint &pos, const wxSize &size, - long style, const wxString &name ) : - wxWindow( win, id, pos, size, style, name ) +wxListHeaderWindow::wxListHeaderWindow() { + Init(); + + m_owner = (wxListMainWindow *) NULL; + m_resizeCursor = (wxCursor *) NULL; +} + +wxListHeaderWindow::wxListHeaderWindow( wxWindow *win, + wxWindowID id, + wxListMainWindow *owner, + const wxPoint& pos, + const wxSize& size, + long style, + const wxString &name ) + : wxWindow( win, id, pos, size, style, name ) +{ + Init(); + m_owner = owner; -// m_currentCursor = wxSTANDARD_CURSOR; - m_currentCursor = (wxCursor *) NULL; m_resizeCursor = new wxCursor( wxCURSOR_SIZEWE ); - m_isDragging = FALSE; - m_dirty = FALSE; SetBackgroundColour( wxSystemSettings::GetSystemColour( wxSYS_COLOUR_BTNFACE ) ); } -wxListHeaderWindow::~wxListHeaderWindow( void ) +wxListHeaderWindow::~wxListHeaderWindow() { delete m_resizeCursor; } @@ -1834,25 +1879,53 @@ void wxListHeaderWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) int numColumns = m_owner->GetColumnCount(); wxListItem item; - for (int i = 0; i < numColumns; i++) + for ( int i = 0; i < numColumns && x < w; i++ ) { m_owner->GetColumn( i, item ); int wCol = item.m_width; - int cw = wCol - 2; // the width of the rect to draw - int xEnd = x + wCol; + // the width of the rect to draw: make it smaller to fit entirely + // inside the column rect + int cw = wCol - 2; dc.SetPen( *wxWHITE_PEN ); DoDrawRect( &dc, x, HEADER_OFFSET_Y, cw, h-2 ); - dc.SetClippingRegion( x, HEADER_OFFSET_Y, cw-5, h-4 ); - dc.DrawText( item.GetText(), x + EXTRA_WIDTH, HEADER_OFFSET_Y + EXTRA_HEIGHT ); - dc.DestroyClippingRegion(); - x += wCol; - if (xEnd > w+5) - break; + // if we have an image, draw it on the right of the label + int image = item.m_image; + if ( image != -1 ) + { + wxImageList *imageList = m_owner->m_small_image_list; + if ( imageList ) + { + int ix, iy; + imageList->GetSize(image, ix, iy); + + imageList->Draw + ( + image, + dc, + x + cw - ix - 1, + HEADER_OFFSET_Y + (h - 4 - iy)/2, + wxIMAGELIST_DRAW_TRANSPARENT + ); + + cw -= ix + 2; + } + //else: ignore the column image + } + + // draw the text clipping it so that it doesn't overwrite the column + // boundary + wxDCClipper clipper(dc, x, HEADER_OFFSET_Y, cw, h - 4 ); + + dc.DrawText( item.GetText(), + x + EXTRA_WIDTH, HEADER_OFFSET_Y + EXTRA_HEIGHT ); + + x += wCol; } + dc.EndDrawing(); } @@ -1952,20 +2025,31 @@ void wxListHeaderWindow::OnMouse( wxMouseEvent &event ) m_minX = xpos; } - if (event.LeftDown()) + if (event.LeftDown() || event.RightUp()) { - if (hit_border) + if (hit_border && event.LeftDown()) { m_isDragging = TRUE; m_currentX = x; DrawCurrent(); CaptureMouse(); } - else + else // click on a column { wxWindow *parent = GetParent(); - wxListEvent le( wxEVT_COMMAND_LIST_COL_CLICK, parent->GetId() ); + wxListEvent le( event.LeftDown() + ? wxEVT_COMMAND_LIST_COL_CLICK + : wxEVT_COMMAND_LIST_COL_RIGHT_CLICK, + parent->GetId() ); le.SetEventObject( parent ); + le.m_pointDrag = event.GetPosition(); + + // the position should be relative to the parent window, not + // this one for compatibility with MSW and common sense: the + // user code doesn't know anything at all about this header + // window, so why should it get positions relative to it? + le.m_pointDrag.y -= GetSize().y; + le.m_col = m_column; parent->GetEventHandler()->ProcessEvent( le ); } @@ -2163,7 +2247,8 @@ wxListMainWindow::wxListMainWindow() { Init(); - m_highlightBrush = (wxBrush *) NULL; + m_highlightBrush = + m_highlightUnfocusedBrush = (wxBrush *) NULL; m_xScroll = m_yScroll = 0; @@ -2180,7 +2265,24 @@ wxListMainWindow::wxListMainWindow( wxWindow *parent, { Init(); - m_highlightBrush = new wxBrush( wxSystemSettings::GetSystemColour(wxSYS_COLOUR_HIGHLIGHT), wxSOLID ); + m_highlightBrush = new wxBrush + ( + wxSystemSettings::GetSystemColour + ( + wxSYS_COLOUR_HIGHLIGHT + ), + wxSOLID + ); + + m_highlightUnfocusedBrush = new wxBrush + ( + wxSystemSettings::GetSystemColour + ( + wxSYS_COLOUR_BTNSHADOW + ), + wxSOLID + ); + wxSize sz = size; sz.y = 25; @@ -2195,6 +2297,7 @@ wxListMainWindow::~wxListMainWindow() DoDeleteAllItems(); delete m_highlightBrush; + delete m_highlightUnfocusedBrush; delete m_renameTimer; } @@ -2321,6 +2424,8 @@ wxRect wxListMainWindow::GetLineHighlightRect(size_t line) const long wxListMainWindow::HitTestLine(size_t line, int x, int y) const { + wxASSERT_MSG( line < GetItemCount(), _T("invalid line in HitTestLine") ); + wxListLineData *ld = GetLine(line); if ( ld->HasImage() && GetLineIconRect(line).Inside(x, y) ) @@ -2402,7 +2507,7 @@ bool wxListMainWindow::HighlightLine( size_t line, bool highlight ) else // !virtual { wxListLineData *ld = GetLine(line); - wxCHECK_MSG( ld, FALSE, _T("invalid index in IsHighlighted") ); + wxCHECK_MSG( ld, FALSE, _T("invalid index in HighlightLine") ); changed = ld->Highlight(highlight); } @@ -2418,11 +2523,14 @@ bool wxListMainWindow::HighlightLine( size_t line, bool highlight ) void wxListMainWindow::RefreshLine( size_t line ) { - size_t visibleFrom, visibleTo; - GetVisibleLinesRange(&visibleFrom, &visibleTo); + if ( HasFlag(wxLC_REPORT) ) + { + size_t visibleFrom, visibleTo; + GetVisibleLinesRange(&visibleFrom, &visibleTo); - if ( line < visibleFrom || line > visibleTo ) - return; + if ( line < visibleFrom || line > visibleTo ) + return; + } wxRect rect = GetLineRect(line); @@ -2495,6 +2603,65 @@ void wxListMainWindow::RefreshAfter( size_t lineFrom ) } } +void wxListMainWindow::RefreshSelected() +{ + if ( IsEmpty() ) + return; + + size_t from, to; + if ( InReportView() ) + { + GetVisibleLinesRange(&from, &to); + } + else // !virtual + { + from = 0; + to = GetItemCount() - 1; + } + + // VZ: this code would work fine if wxGTK wxWindow::Refresh() were + // reasonable, i.e. if it only generated one expose event for + // several calls to it - as it is, each Refresh() results in a + // repaint which provokes flicker too horrible to be seen + // + // when/if wxGTK is fixed, this code should be restored as normally it + // should generate _less_ flicker than the version below +#ifndef __WXGTK__ + if ( HasCurrent() && m_current >= from && m_current <= to ) + { + RefreshLine(m_current); + } + + for ( size_t line = from; line <= to; line++ ) + { + // NB: the test works as expected even if m_current == -1 + if ( line != m_current && IsHighlighted(line) ) + { + RefreshLine(line); + } + } +#else // __WXGTK__ + size_t selMin = (size_t)-1, + selMax = 0; + + for ( size_t line = from; line <= to; line++ ) + { + if ( IsHighlighted(line) || (line == m_current) ) + { + if ( line < selMin ) + selMin = line; + if ( line > selMax ) + selMax = line; + } + } + + if ( selMin != (size_t)-1 ) + { + RefreshLines(selMin, selMax); + } +#endif // !__WXGTK__/__WXGTK__ +} + void wxListMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) { // Note: a wxPaintDC must be constructed even if no drawing is @@ -2558,7 +2725,7 @@ void wxListMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) GetLine(line)->DrawInReportMode( &dc, rectLine, GetLineHighlightRect(line), - m_hasFocus && IsHighlighted(line) ); + IsHighlighted(line) ); } if ( HasFlag(wxLC_HRULES) ) @@ -2618,10 +2785,13 @@ void wxListMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) if ( HasCurrent() ) { // don't draw rect outline under Max if we already have the background - // color + // color but under other platforms only draw it if we do: it is a bit + // silly to draw "focus rect" if we don't have focus! #ifdef __WXMAC__ if ( !m_hasFocus ) -#endif // !__WXMAC__ +#else // !__WXMAC__ + if ( m_hasFocus ) +#endif // __WXMAC__/!__WXMAC__ { dc.SetPen( *wxBLACK_PEN ); dc.SetBrush( *wxTRANSPARENT_BRUSH ); @@ -2808,7 +2978,12 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event ) if (event.Dragging()) { if (m_dragCount == 0) - m_dragStart = wxPoint(x,y); + { + // we have to report the raw, physical coords as we want to be + // able to call HitTest(event.m_pointDrag) from the user code to + // get the item being dragged + m_dragStart = event.GetPosition(); + } m_dragCount++; @@ -3212,12 +3387,18 @@ extern wxWindow *g_focusWindow; void wxListMainWindow::OnSetFocus( wxFocusEvent &WXUNUSED(event) ) { - m_hasFocus = TRUE; + // wxGTK sends us EVT_SET_FOCUS events even if we had never got + // EVT_KILL_FOCUS before which means that we finish by redrawing the items + // which are already drawn correctly resulting in horrible flicker - avoid + // it + if ( !m_hasFocus ) + { + m_hasFocus = TRUE; - if ( HasCurrent() ) - RefreshLine( m_current ); + RefreshSelected(); + } - if (!GetParent()) + if ( !GetParent() ) return; #ifdef __WXGTK__ @@ -3233,8 +3414,7 @@ void wxListMainWindow::OnKillFocus( wxFocusEvent &WXUNUSED(event) ) { m_hasFocus = FALSE; - if ( HasCurrent() ) - RefreshLine( m_current ); + RefreshSelected(); } void wxListMainWindow::DrawImage( int index, wxDC *dc, int x, int y ) @@ -3371,6 +3551,9 @@ void wxListMainWindow::SetColumnWidth( int col, int width ) _T("SetColumnWidth() can only be called in report mode.") ); m_dirty = TRUE; + wxListHeaderWindow *headerWin = GetListCtrl()->m_headerWin; + if ( headerWin ) + headerWin->m_dirty = TRUE; wxListHeaderDataList::Node *node = m_columns.Item( col ); wxCHECK_RET( node, _T("no column?") ); @@ -3983,8 +4166,6 @@ void wxListMainWindow::EnsureVisible( long index ) // been added and its position is not known yet if ( m_dirty ) { - m_dirty = FALSE; - RecalculatePositions(TRUE /* no refresh */); } @@ -4032,18 +4213,22 @@ long wxListMainWindow::HitTest( int x, int y, int &flags ) { CalcUnscrolledPosition( x, y, &x, &y ); + size_t count = GetItemCount(); + if ( HasFlag(wxLC_REPORT) ) { size_t current = y / GetLineHeight(); - flags = HitTestLine(current, x, y); - if ( flags ) - return current; + if ( current < count ) + { + flags = HitTestLine(current, x, y); + if ( flags ) + return current; + } } else // !report { // TODO: optimize it too! this is less simple than for report view but // enumerating all items is still not a way to do it!! - size_t count = GetItemCount(); for ( size_t current = 0; current < count; current++ ) { flags = HitTestLine(current, x, y); @@ -4228,17 +4413,9 @@ IMPLEMENT_DYNAMIC_CLASS(wxListItem, wxObject) wxListItem::wxListItem() { - m_mask = 0; - m_itemId = 0; - m_col = 0; - m_state = 0; - m_stateMask = 0; - m_image = 0; - m_data = 0; - m_format = wxLIST_FORMAT_CENTRE; - m_width = 0; - m_attr = NULL; + + Clear(); } void wxListItem::Clear() @@ -4248,11 +4425,11 @@ void wxListItem::Clear() m_col = 0; m_state = 0; m_stateMask = 0; - m_image = 0; + m_image = -1; m_data = 0; m_format = wxLIST_FORMAT_CENTRE; m_width = 0; - m_text = _T(""); + m_text.clear(); ClearAttributes(); } @@ -4725,7 +4902,7 @@ bool wxListCtrl::DeleteAllColumns() { size_t count = m_mainWin->m_columns.GetCount(); for ( size_t n = 0; n < count; n++ ) - DeleteColumn(n); + DeleteColumn(0); return TRUE; }