diff --git a/include/wx/dataview.h b/include/wx/dataview.h index a0ed0dc1e3..bb831d0df8 100644 --- a/include/wx/dataview.h +++ b/include/wx/dataview.h @@ -905,8 +905,8 @@ public: int GetDragFlags() const { return m_dragFlags; } void SetDropEffect( wxDragResult effect ) { m_dropEffect = effect; } wxDragResult GetDropEffect() const { return m_dropEffect; } - // for plaforms (currently only OSX) that support Drag/Drop insertion of items, - // this is the proposed child index for the insertion + // For platforms (currently generic and OSX) that support Drag/Drop + // insertion of items, this is the proposed child index for the insertion. void SetProposedDropIndex(int index) { m_proposedDropIndex = index; } int GetProposedDropIndex() const { return m_proposedDropIndex;} #endif // wxUSE_DRAG_AND_DROP diff --git a/interface/wx/dataview.h b/interface/wx/dataview.h index 76aae207d3..a294c04e89 100644 --- a/interface/wx/dataview.h +++ b/interface/wx/dataview.h @@ -3919,6 +3919,19 @@ public: */ int GetCacheTo() const; + /** + Returns the index of the child item at which an item currently being + dragged would be dropped. + + This function can be used from wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE + handlers to determine the exact position of the item being dropped. + + Note that it currently always returns wxNOT_FOUND when using native GTK + implementation of this control. + + @since 3.1.2 + */ + int GetProposedDropIndex() const; /** Returns the item affected by the event. diff --git a/samples/dataview/dataview.cpp b/samples/dataview/dataview.cpp index c959eb5f52..e80295515c 100644 --- a/samples/dataview/dataview.cpp +++ b/samples/dataview/dataview.cpp @@ -1278,27 +1278,16 @@ void MyFrame::OnBeginDrag( wxDataViewEvent &event ) void MyFrame::OnDropPossible( wxDataViewEvent &event ) { - wxDataViewItem item( event.GetItem() ); - - // only allow drags for item or background, not containers - if ( item.IsOk() && m_music_model->IsContainer( item ) ) - event.Veto(); - if (event.GetDataFormat() != wxDF_UNICODETEXT) event.Veto(); + else + event.SetDropEffect(wxDragMove); // check 'move' drop effect } void MyFrame::OnDrop( wxDataViewEvent &event ) { wxDataViewItem item( event.GetItem() ); - // only allow drops for item, not containers - if ( item.IsOk() && m_music_model->IsContainer( item ) ) - { - event.Veto(); - return; - } - if (event.GetDataFormat() != wxDF_UNICODETEXT) { event.Veto(); @@ -1309,9 +1298,17 @@ void MyFrame::OnDrop( wxDataViewEvent &event ) obj.SetData( wxDF_UNICODETEXT, event.GetDataSize(), event.GetDataBuffer() ); if ( item.IsOk() ) - wxLogMessage( "Text dropped on item %s: %s", m_music_model->GetTitle( item ), obj.GetText() ); + { + if (m_music_model->IsContainer(item)) + { + wxLogMessage("Text '%s' dropped in container '%s' (proposed index = %i)", + obj.GetText(), m_music_model->GetTitle(item), event.GetProposedDropIndex()); + } + else + wxLogMessage("Text '%s' dropped on item '%s'", obj.GetText(), m_music_model->GetTitle(item)); + } else - wxLogMessage( "Text dropped on background: %s", obj.GetText() ); + wxLogMessage("Text '%s' dropped on background (proposed index = %i)", obj.GetText(), event.GetProposedDropIndex()); } #endif // wxUSE_DRAG_AND_DROP diff --git a/src/generic/datavgen.cpp b/src/generic/datavgen.cpp index 613c057a96..4c93ae83ea 100644 --- a/src/generic/datavgen.cpp +++ b/src/generic/datavgen.cpp @@ -875,10 +875,38 @@ public: bool HasChildren( unsigned int row ) const; #if wxUSE_DRAG_AND_DROP + enum DropHint + { + DropHint_None = 0, + DropHint_Inside, + DropHint_Below, + DropHint_Above + }; + struct DropItemInfo + { + unsigned int m_row; + DropHint m_hint; + + wxDataViewItem m_item; + int m_proposedDropIndex; + int m_indentLevel; + + DropItemInfo() + : m_row(static_cast(-1)) + , m_hint(DropHint_None) + , m_item(NULL) + , m_proposedDropIndex(-1) + , m_indentLevel(-1) + { + } + }; + bool EnableDragSource( const wxDataFormat &format ); bool EnableDropTarget( const wxDataFormat &format ); + void RefreshDropHint(); void RemoveDropHint(); + DropItemInfo GetDropItemInfo(const wxCoord x, const wxCoord y); wxDragResult OnDragOver( wxDataFormat format, wxCoord x, wxCoord y, wxDragResult def ); bool OnDrop( wxDataFormat format, wxCoord x, wxCoord y ); wxDragResult OnData( wxDataFormat format, wxCoord x, wxCoord y, wxDragResult def ); @@ -983,8 +1011,7 @@ private: bool m_dropEnabled; wxDataFormat m_dropFormat; - bool m_dropHint; - unsigned int m_dropHintLine; + DropItemInfo m_dropItemInfo; #endif // wxUSE_DRAG_AND_DROP // for double click logic @@ -2026,8 +2053,7 @@ wxDataViewMainWindow::wxDataViewMainWindow( wxDataViewCtrl *parent, wxWindowID i m_dragEnabled = false; m_dropEnabled = false; - m_dropHint = false; - m_dropHintLine = (unsigned int) -1; + m_dropItemInfo = DropItemInfo(); #endif // wxUSE_DRAG_AND_DROP m_lineLastClicked = (unsigned int) -1; @@ -2093,69 +2119,245 @@ bool wxDataViewMainWindow::EnableDropTarget( const wxDataFormat &format ) return true; } -void wxDataViewMainWindow::RemoveDropHint() +void wxDataViewMainWindow::RefreshDropHint() { - if (m_dropHint) + const unsigned row = m_dropItemInfo.m_row; + + switch (m_dropItemInfo.m_hint) { - m_dropHint = false; - RefreshRow( m_dropHintLine ); - m_dropHintLine = (unsigned int) -1; + case DropHint_None: + break; + + case DropHint_Inside: + RefreshRow(row); + break; + + case DropHint_Above: + RefreshRows(row == 0 ? 0 : row - 1, row); + break; + + case DropHint_Below: + // It's not a problem here if row+1 is out of range, RefreshRows() + // allows this. + RefreshRows(row, row + 1); + break; } } +void wxDataViewMainWindow::RemoveDropHint() +{ + RefreshDropHint(); + + m_dropItemInfo = DropItemInfo(); +} + +wxDataViewMainWindow::DropItemInfo wxDataViewMainWindow::GetDropItemInfo(const wxCoord x, const wxCoord y) +{ + DropItemInfo dropItemInfo; + + int xx = x; + int yy = y; + m_owner->CalcUnscrolledPosition( xx, yy, &xx, &yy ); + + unsigned int row = GetLineAt(yy); + dropItemInfo.m_row = row; + + if (row >= GetRowCount() || xx > GetEndOfLastCol()) + return dropItemInfo; + + if (IsVirtualList()) + { + dropItemInfo.m_item = GetItemByRow(row); + + if (dropItemInfo.m_item.IsOk()) + dropItemInfo.m_hint = DropHint_Inside; + } + else + { + wxDataViewTreeNode* node = GetTreeNodeByRow(row); + if (!node) + return dropItemInfo; + + dropItemInfo.m_item = node->GetItem(); + + const int itemStart = GetLineStart(row); + const int itemHeight = GetLineHeight(row); + + // 15% is an arbitrarily chosen threshold here, which could be changed + // or made configurable if really needed. + static const double UPPER_ITEM_PART = 0.15; + + bool insertAbove = yy - itemStart < itemHeight*UPPER_ITEM_PART; + if (insertAbove) + { + // Can be treated as 'insertBelow" with the small difference: + node = GetTreeNodeByRow(row - 1); // We need the node from the previous row + + dropItemInfo.m_hint = DropHint_Above; + + if (!node) + { + // Seems to be dropped in the root node + + dropItemInfo.m_indentLevel = 0; + dropItemInfo.m_proposedDropIndex = 0; + dropItemInfo.m_item = wxDataViewItem(); + + return dropItemInfo; + } + + } + + bool insertBelow = yy - itemStart > itemHeight*(1.0 - UPPER_ITEM_PART); + if (insertBelow) + dropItemInfo.m_hint = DropHint_Below; + + if (insertBelow || insertAbove) + { + // Insert inside the 'item' or after (below) it. Depends on: + // 1 - the 'item' is a container + // 2 - item's child index in it's parent (the last in the parent or not) + // 3 - expanded (opened) or not + // 4 - mouse x position + + int xStart = 0; // Expander column x position start + wxDataViewColumn* const expander = GetExpanderColumnOrFirstOne(GetOwner()); + for (unsigned int i = 0; i < GetOwner()->GetColumnCount(); i++) + { + wxDataViewColumn* col = GetOwner()->GetColumnAt(i); + if (col->IsHidden()) + continue; // skip it! + + if (col == expander) + break; + + xStart += col->GetWidth(); + } + + const int expanderWidth = wxRendererNative::Get().GetExpanderSize(this).GetWidth(); + + int level = node->GetIndentLevel(); + + wxDataViewTreeNode* prevAscendNode = node; + wxDataViewTreeNode* ascendNode = node; + while (ascendNode != NULL) + { + dropItemInfo.m_indentLevel = level + 1; + + if (m_owner->GetModel()->IsContainer(ascendNode->GetItem())) + { + // Item can be inserted + dropItemInfo.m_item = ascendNode->GetItem(); + + int itemPosition = ascendNode->FindChildByItem(prevAscendNode->GetItem()); + if ( itemPosition == wxNOT_FOUND ) + itemPosition = 0; + else + itemPosition++; + + dropItemInfo.m_proposedDropIndex = itemPosition; + + // We must break the loop if the applied node is expanded + // (opened) and the proposed drop position is not the last + // in this node. + if ( ascendNode->IsOpen() ) + { + const size_t lastPos = ascendNode->GetChildNodes().size(); + if ( static_cast(itemPosition) != lastPos ) + break; + } + + int indent = GetOwner()->GetIndent()*level + expanderWidth; + + if (xx >= xStart + indent) + break; + } + + prevAscendNode = ascendNode; + ascendNode = ascendNode->GetParent(); + --level; + } + } + else + { + dropItemInfo.m_hint = DropHint_Inside; + } + } + + return dropItemInfo; +} + + wxDragResult wxDataViewMainWindow::OnDragOver( wxDataFormat format, wxCoord x, wxCoord y, wxDragResult def ) { - int xx = x; - int yy = y; - m_owner->CalcUnscrolledPosition( xx, yy, &xx, &yy ); - unsigned int row = GetLineAt( yy ); + DropItemInfo nextDropItemInfo = GetDropItemInfo(x, y); - wxDataViewItem item; - - if ( row < GetRowCount() && xx <= GetEndOfLastCol() ) - item = GetItemByRow( row ); - - wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, m_owner, item); + wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, m_owner, nextDropItemInfo.m_item); + event.SetProposedDropIndex(nextDropItemInfo.m_proposedDropIndex); event.SetDataFormat( format ); event.SetDropEffect( def ); - if ( !m_owner->HandleWindowEvent( event ) || !event.IsAllowed() ) + + wxDragResult result = def; + + if (m_owner->HandleWindowEvent(event) && event.IsAllowed()) + { + // Processing handled event + + result = event.GetDropEffect(); + switch (result) + { + case wxDragCopy: + case wxDragMove: + case wxDragLink: + break; + + case wxDragNone: + case wxDragCancel: + case wxDragError: + { + RemoveDropHint(); + return result; + } + } + } + else { RemoveDropHint(); return wxDragNone; } - if ( item.IsOk() ) + if (nextDropItemInfo.m_hint != DropHint_None) { - if (m_dropHint && (row != m_dropHintLine)) - RefreshRow( m_dropHintLine ); - m_dropHint = true; - m_dropHintLine = row; - RefreshRow( row ); + if (m_dropItemInfo.m_hint != nextDropItemInfo.m_hint || + m_dropItemInfo.m_row != nextDropItemInfo.m_row) + { + RefreshDropHint(); // refresh previous rows + } + + m_dropItemInfo.m_hint = nextDropItemInfo.m_hint; + m_dropItemInfo.m_row = nextDropItemInfo.m_row; + + RefreshDropHint(); } else { RemoveDropHint(); } - return def; + m_dropItemInfo = nextDropItemInfo; + + return result; } bool wxDataViewMainWindow::OnDrop( wxDataFormat format, wxCoord x, wxCoord y ) { RemoveDropHint(); - int xx = x; - int yy = y; - m_owner->CalcUnscrolledPosition( xx, yy, &xx, &yy ); - unsigned int row = GetLineAt( yy ); + DropItemInfo dropItemInfo = GetDropItemInfo(x, y); - wxDataViewItem item; - - if ( row < GetRowCount() && xx <= GetEndOfLastCol()) - item = GetItemByRow( row ); - - wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, m_owner, item); + wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, m_owner, dropItemInfo.m_item); + event.SetProposedDropIndex(dropItemInfo.m_proposedDropIndex); event.SetDataFormat( format ); if (!m_owner->HandleWindowEvent( event ) || !event.IsAllowed()) return false; @@ -2166,19 +2368,12 @@ bool wxDataViewMainWindow::OnDrop( wxDataFormat format, wxCoord x, wxCoord y ) wxDragResult wxDataViewMainWindow::OnData( wxDataFormat format, wxCoord x, wxCoord y, wxDragResult def ) { - int xx = x; - int yy = y; - m_owner->CalcUnscrolledPosition( xx, yy, &xx, &yy ); - unsigned int row = GetLineAt( yy ); - - wxDataViewItem item; - - if ( row < GetRowCount() && xx <= GetEndOfLastCol() ) - item = GetItemByRow( row ); + DropItemInfo dropItemInfo = GetDropItemInfo(x, y); wxCustomDataObject *obj = (wxCustomDataObject *) GetDropTarget()->GetDataObject(); - wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_DROP, m_owner, item); + wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_DROP, m_owner, dropItemInfo.m_item); + event.SetProposedDropIndex(dropItemInfo.m_proposedDropIndex); event.SetDataFormat( format ); event.SetDataSize( obj->GetSize() ); event.SetDataBuffer( obj->GetData() ); @@ -2548,13 +2743,15 @@ void wxDataViewMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) } #if wxUSE_DRAG_AND_DROP - if (m_dropHint) + wxRect dropItemRect; + + if (m_dropItemInfo.m_hint == DropHint_Inside) { - wxRect rect( x_start, GetLineStart( m_dropHintLine ), - x_last - x_start, GetLineHeight( m_dropHintLine ) ); - dc.SetPen( *wxBLACK_PEN ); - dc.SetBrush( *wxTRANSPARENT_BRUSH ); - dc.DrawRectangle( rect ); + int rect_y = GetLineStart(m_dropItemInfo.m_row); + int rect_h = GetLineHeight(m_dropItemInfo.m_row); + wxRect rect(x_start, rect_y, x_last - x_start, rect_h); + + wxRendererNative::Get().DrawItemSelectionRect(this, dc, rect, wxCONTROL_SELECTED | wxCONTROL_FOCUSED); } #endif // wxUSE_DRAG_AND_DROP @@ -2658,6 +2855,21 @@ void wxDataViewMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) // force the expander column to left-center align cell->SetAlignment( wxALIGN_CENTER_VERTICAL ); + +#if wxUSE_DRAG_AND_DROP + if (item == m_dropItemInfo.m_row) + { + dropItemRect = cell_rect; + dropItemRect.x += expSize.GetWidth(); + dropItemRect.width -= expSize.GetWidth(); + if (m_dropItemInfo.m_indentLevel >= 0) + { + int hintIndent = GetOwner()->GetIndent()*m_dropItemInfo.m_indentLevel; + dropItemRect.x += hintIndent; + dropItemRect.width -= hintIndent; + } + } +#endif } wxRect item_rect = cell_rect; @@ -2689,6 +2901,20 @@ void wxDataViewMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) cell_rect.x += cell_rect.width; } + +#if wxUSE_DRAG_AND_DROP + if (m_dropItemInfo.m_hint == DropHint_Below || m_dropItemInfo.m_hint == DropHint_Above) + { + const int insertLineHeight = 2; // TODO: setup (should be even) + + int rect_y = dropItemRect.y - insertLineHeight/2; // top insert + if (m_dropItemInfo.m_hint == DropHint_Below) + rect_y += dropItemRect.height; // bottom insert + + wxRect rect(dropItemRect.x, rect_y, dropItemRect.width, insertLineHeight); + wxRendererNative::Get().DrawItemSelectionRect(this, dc, rect, wxCONTROL_SELECTED); + } +#endif // wxUSE_DRAG_AND_DROP }