From 0c0e063e66f80ce608d8bb9447b33609858ba6be Mon Sep 17 00:00:00 2001 From: Julian Smart Date: Tue, 3 Jan 2012 14:09:34 +0000 Subject: [PATCH] Applied #13819: wxRTC drag and drop, by dghart, with tweaks git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@70253 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/richtext/richtextctrl.h | 104 +++++++++++- interface/wx/richtext/richtextctrl.h | 49 ++++-- src/richtext/richtextctrl.cpp | 236 ++++++++++++++++++++++++++- 3 files changed, 367 insertions(+), 22 deletions(-) diff --git a/include/wx/richtext/richtextctrl.h b/include/wx/richtext/richtextctrl.h index 89ef2a71a3..81f7a3e3ce 100644 --- a/include/wx/richtext/richtextctrl.h +++ b/include/wx/richtext/richtextctrl.h @@ -21,6 +21,10 @@ #include "wx/textctrl.h" +#if wxUSE_DRAG_AND_DROP +#include "wx/dnd.h" +#endif + #if !defined(__WXGTK__) && !defined(__WXMAC__) #define wxRICHTEXT_BUFFERED_PAINTING 1 #else @@ -428,15 +432,40 @@ public: */ void SetDragging(bool dragging) { m_dragging = dragging; } +#if wxUSE_DRAG_AND_DROP + /** + Are we trying to start Drag'n'Drop? + */ + bool GetPreDrag() const { return m_preDrag; } + + /** + Set if we're trying to start Drag'n'Drop + */ + void SetPreDrag(bool pd) { m_preDrag = pd; } + + /** + Get the possible Drag'n'Drop start point + */ + const wxPoint GetDragStartPoint() const { return m_dragStartPoint; } + /** - Returns the drag start position. + Set the possible Drag'n'Drop start point */ - const wxPoint& GetDragStart() const { return m_dragStart; } + void SetDragStartPoint(wxPoint sp) { m_dragStartPoint = sp; } +#if wxUSE_DATETIME /** - Sets the drag start position. + Get the possible Drag'n'Drop start time */ - void SetDragStart(const wxPoint& pt) { m_dragStart = pt; } + const wxDateTime GetDragStartTime() const { return m_dragStartTime; } + + /** + Set the possible Drag'n'Drop start time + */ + void SetDragStartTime(wxDateTime st) { m_dragStartTime = st; } +#endif // wxUSE_DATETIME + +#endif // wxUSE_DRAG_AND_DROP #if wxRICHTEXT_BUFFERED_PAINTING //@{ @@ -498,6 +527,11 @@ public: */ wxRichTextParagraphLayoutBox* GetFocusObject() const { return m_focusObject; } + /** + Sets m_focusObject without making any alterations. + */ + void StoreFocusObject(wxRichTextParagraphLayoutBox* obj) { m_focusObject = obj; } + /** Sets the wxRichTextObject object that currently has the editing focus. */ @@ -840,8 +874,20 @@ public: virtual wxTextCtrlHitTestResult HitTest(const wxPoint& pt, wxTextCoord *col, wxTextCoord *row) const; + + /** + Finds the container at the given point, which is in screen coordinates. + */ + wxRichTextParagraphLayoutBox* FindContainerAtPoint(const wxPoint pt, long& position, int& hit, wxRichTextObject* hitObj, int flags = 0); //@} +#if wxUSE_DRAG_AND_DROP + /** + Does the 'drop' of Drag'n'Drop. + */ + void OnDrop(wxCoord WXUNUSED(x), wxCoord WXUNUSED(y), wxDragResult def, wxDataObject* DataObj); +#endif + // Clipboard operations /** @@ -2018,7 +2064,7 @@ protected: // Data members -private: +protected: #if wxRICHTEXT_BUFFERED_PAINTING /// Buffer bitmap wxBitmap m_bufferBitmap; @@ -2057,11 +2103,21 @@ private: /// instead of at the end of the previous one? bool m_caretAtLineStart; - /// Are we dragging a selection? + /// Are we dragging (i.e. extending) a selection? bool m_dragging; - /// Start position for drag - wxPoint m_dragStart; +#if wxUSE_DRAG_AND_DROP + /// Are we trying to start Drag'n'Drop? + bool m_preDrag; + + /// Initial position when starting Drag'n'Drop + wxPoint m_dragStartPoint; + +#if wxUSE_DATETIME + /// Initial time when starting Drag'n'Drop + wxDateTime m_dragStartTime; +#endif // wxUSE_DATETIME +#endif // wxUSE_DRAG_AND_DROP /// Do we need full layout in idle? bool m_fullLayoutRequired; @@ -2083,6 +2139,38 @@ private: wxRichTextParagraphLayoutBox* m_focusObject; }; +#if wxUSE_DRAG_AND_DROP +class WXDLLIMPEXP_RICHTEXT wxRichTextDropSource : public wxDropSource +{ +public: + wxRichTextDropSource(wxDataObject& data, wxRichTextCtrl* tc) + : wxDropSource(data, tc), m_rtc(tc) {} + +protected: + bool GiveFeedback(wxDragResult effect); + + wxRichTextCtrl* m_rtc; +}; + +class WXDLLIMPEXP_RICHTEXT wxRichTextDropTarget : public wxDropTarget +{ +public: + wxRichTextDropTarget(wxRichTextCtrl* tc) + : wxDropTarget(new wxRichTextBufferDataObject(new wxRichTextBuffer)), m_rtc(tc) {} + + virtual wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def) + { + if ( !GetData() ) + return wxDragNone; + m_rtc->OnDrop(x, y, def, m_dataObject); + return def; + } + +protected: + wxRichTextCtrl* m_rtc; +}; +#endif // wxUSE_DRAG_AND_DROP + /** @class wxRichTextEvent diff --git a/interface/wx/richtext/richtextctrl.h b/interface/wx/richtext/richtextctrl.h index 7188dbfda3..831b184965 100644 --- a/interface/wx/richtext/richtextctrl.h +++ b/interface/wx/richtext/richtextctrl.h @@ -392,24 +392,44 @@ public: void SetCaretAtLineStart(bool atStart) { m_caretAtLineStart = atStart; } /** - Returns @true if we are dragging a selection. + Returns @true if we are extending a selection. */ bool GetDragging() const { return m_dragging; } /** - Sets a flag to remember if we are dragging a selection. + Sets a flag to remember if we are extending a selection. */ void SetDragging(bool dragging) { m_dragging = dragging; } /** - Returns the drag start position. + Are we trying to start Drag'n'Drop? */ - const wxPoint& GetDragStart() const { return m_dragStart; } + bool GetPreDrag() const { return m_preDrag; } /** - Sets the drag start position. + Set if we're trying to start Drag'n'Drop */ - void SetDragStart(const wxPoint& pt) { m_dragStart = pt; } + void SetPreDrag(bool pd) { m_preDrag = pd; } + + /** + Get the possible Drag'n'Drop start point + */ + const wxPoint GetDragStartPoint() const { return m_dragStartPoint; } + + /** + Set the possible Drag'n'Drop start point + */ + void SetDragStartPoint(wxPoint sp) { m_dragStartPoint = sp; } + + /** + Get the possible Drag'n'Drop start time + */ + const wxDateTime GetDragStartTime() const { return m_dragStartTime; } + + /** + Set the possible Drag'n'Drop start time + */ + void SetDragStartTime(wxDateTime st) { m_dragStartTime = st; } #if wxRICHTEXT_BUFFERED_PAINTING //@{ @@ -471,8 +491,15 @@ public: */ wxRichTextParagraphLayoutBox* GetFocusObject() const { return m_focusObject; } + /** + Setter for m_focusObject. + */ + void StoreFocusObject(wxRichTextParagraphLayoutBox* obj); + /** Sets the wxRichTextObject object that currently has the editing focus. + @param setCaretPosition + Optionally set the caret position. */ bool SetFocusObject(wxRichTextParagraphLayoutBox* obj, bool setCaretPosition = true); @@ -813,6 +840,11 @@ public: virtual wxTextCtrlHitTestResult HitTest(const wxPoint& pt, wxTextCoord *col, wxTextCoord *row) const; + + /** + Finds the container at the given point, which is assumed to be in client coordinates. + */ + wxRichTextParagraphLayoutBox* FindContainerAtPoint(const wxPoint pt, long& position, int& hit, wxRichTextObject* hitObj, int flags = 0); //@} // Clipboard operations @@ -1991,7 +2023,7 @@ protected: // Data members -private: +protected: #if wxRICHTEXT_BUFFERED_PAINTING /// Buffer bitmap wxBitmap m_bufferBitmap; @@ -2033,9 +2065,6 @@ private: /// Are we dragging a selection? bool m_dragging; - /// Start position for drag - wxPoint m_dragStart; - /// Do we need full layout in idle? bool m_fullLayoutRequired; wxLongLong m_fullLayoutTime; diff --git a/src/richtext/richtextctrl.cpp b/src/richtext/richtextctrl.cpp index a06d393f0c..05b8142837 100644 --- a/src/richtext/richtextctrl.cpp +++ b/src/richtext/richtextctrl.cpp @@ -227,6 +227,8 @@ wxRichTextCtrl::wxRichTextCtrl(wxWindow* parent, { Init(); Create(parent, id, value, pos, size, style, validator, name); + + SetDropTarget(new wxRichTextDropTarget(this)); } /// Creation @@ -349,6 +351,7 @@ void wxRichTextCtrl::Init() m_editable = true; m_caretAtLineStart = false; m_dragging = false; + m_preDrag = false; m_fullLayoutRequired = false; m_fullLayoutTime = 0; m_fullLayoutSavedPosition = 0; @@ -559,6 +562,26 @@ void wxRichTextCtrl::OnLeftClick(wxMouseEvent& event) wxRichTextObject* contextObj = NULL; int hit = GetBuffer().HitTest(dc, event.GetLogicalPosition(dc), position, & hitObj, & contextObj); +#if wxUSE_DRAG_AND_DROP + // If there's no selection, or we're not inside it, this isn't an attempt to initiate Drag'n'Drop + if (HasSelection() && GetSelectionRange().ToInternal().Contains(position)) + { + // This might be an attempt at initiating Drag'n'Drop. So set the time & flags + m_preDrag = true; + m_dragStartPoint = event.GetPosition(); // No need to worry about logical positions etc, we only care about the distance from the original pt + +#if wxUSE_DATETIME + m_dragStartTime = wxDateTime::UNow(); +#endif // wxUSE_DATETIME + + // Preserve behaviour of clicking on an object within the selection + if (hit != wxRICHTEXT_HITTEST_NONE && hitObj) + m_dragging = true; + + return; // Don't skip the event, else the selection will be lost + } +#endif // wxUSE_DRAG_AND_DROP + if (hit != wxRICHTEXT_HITTEST_NONE && hitObj) { wxRichTextParagraphLayoutBox* oldFocusObject = GetFocusObject(); @@ -568,7 +591,6 @@ void wxRichTextCtrl::OnLeftClick(wxMouseEvent& event) SetFocusObject(container, false /* don't set caret position yet */); } - m_dragStart = event.GetLogicalPosition(dc); m_dragging = true; CaptureMouse(); @@ -607,6 +629,36 @@ void wxRichTextCtrl::OnLeftUp(wxMouseEvent& event) // Only get objects at this level, not nested, because otherwise we couldn't swipe text at a single level. int hit = GetFocusObject()->HitTest(dc, logicalPt, position, & hitObj, & contextObj, wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS); +#if wxUSE_DRAG_AND_DROP + if (m_preDrag) + { + // Preserve the behaviour that would have happened without drag-and-drop detection, in OnLeftClick + m_preDrag = false; // Tell DnD not to happen now: we are processing Left Up ourselves. + + // Do the actions that would have been done in OnLeftClick if we hadn't tried to drag + long position = 0; + wxRichTextObject* hitObj = NULL; + wxRichTextObject* contextObj = NULL; + int hit = GetBuffer().HitTest(dc, event.GetLogicalPosition(dc), position, & hitObj, & contextObj); + wxRichTextParagraphLayoutBox* oldFocusObject = GetFocusObject(); + wxRichTextParagraphLayoutBox* container = wxDynamicCast(contextObj, wxRichTextParagraphLayoutBox); + if (container && container != GetFocusObject() && container->AcceptsFocus()) + { + SetFocusObject(container, false /* don't set caret position yet */); + } + + long oldCaretPos = m_caretPosition; + + SetCaretPositionAfterClick(container, position, hit); + + // For now, don't handle shift-click when we're selecting multiple objects. + if (event.ShiftDown() && GetFocusObject() == oldFocusObject && m_selectionState == wxRichTextCtrlSelectionState_Normal) + ExtendSelection(oldCaretPos, m_caretPosition, wxRICHTEXT_SHIFT_DOWN); + else + SelectNone(); + } +#endif + if ((hit != wxRICHTEXT_HITTEST_NONE) && !(hit & wxRICHTEXT_HITTEST_OUTSIDE)) { wxRichTextEvent cmdEvent( @@ -650,6 +702,10 @@ void wxRichTextCtrl::OnLeftUp(wxMouseEvent& event) } } +#if wxUSE_DRAG_AND_DROP + m_preDrag = false; +#endif // wxUSE_DRAG_AND_DROP + #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ && wxHAVE_PRIMARY_SELECTION if (HasSelection() && GetFocusObject() && GetFocusObject()->GetBuffer()) { @@ -664,9 +720,77 @@ void wxRichTextCtrl::OnLeftUp(wxMouseEvent& event) #endif } -/// Left-click +/// Mouse-movements void wxRichTextCtrl::OnMoveMouse(wxMouseEvent& event) { +#if wxUSE_DRAG_AND_DROP + // See if we're starting Drag'n'Drop + if (m_preDrag) + { + int x = m_dragStartPoint.x - event.GetPosition().x; + int y = m_dragStartPoint.y - event.GetPosition().y; + size_t distance = abs(x) + abs(y); +#if wxUSE_DATETIME + wxTimeSpan diff = wxDateTime::UNow() - m_dragStartTime; +#endif + if ((distance > 10) +#if wxUSE_DATETIME + && (diff.GetMilliseconds() > 100) +#endif + ) + { + m_dragging = false; + + // Start drag'n'drop + wxRichTextRange range = GetInternalSelectionRange(); + if (range == wxRICHTEXT_NONE) + { + // Don't try to drag an empty range + m_preDrag = false; + return; + } + + // Cache the current situation, to be restored if Drag'n'Drop is cancelled + long oldPos = GetCaretPosition(); + wxRichTextParagraphLayoutBox* oldFocus = GetFocusObject(); + + wxDataObjectComposite* compositeObject = new wxDataObjectComposite(); + wxString text = GetFocusObject()->GetTextForRange(range); +#ifdef __WXMSW__ + text = wxTextFile::Translate(text, wxTextFileType_Dos); +#endif + compositeObject->Add(new wxTextDataObject(text), false /* not preferred */); + + wxRichTextBuffer* richTextBuf = new wxRichTextBuffer; + GetFocusObject()->CopyFragment(range, *richTextBuf); + compositeObject->Add(new wxRichTextBufferDataObject(richTextBuf), true /* preferred */); + + wxRichTextDropSource source(*compositeObject, this); + // Use wxDrag_DefaultMove, not because it's the likelier choice but because pressing Ctrl for Copy obeys the principle of least surprise + // The alternative, wxDrag_DefaultCopy, requires the user to know that Move needs the Shift key pressed + BeginBatchUndo(_("Drag")); + switch (source.DoDragDrop(wxDrag_AllowMove | wxDrag_DefaultMove)) + { + case wxDragMove: + case wxDragCopy: break; + + case wxDragError: + wxLogError(wxT("An error occurred during drag and drop operation")); + case wxDragNone: + case wxDragCancel: + Refresh(); // This is needed in wxMSW, otherwise resetting the position doesn't 'take' + SetCaretPosition(oldPos); + SetFocusObject(oldFocus, false); + default: break; + } + EndBatchUndo(); + + m_preDrag = false; + return; + } + } +#endif // wxUSE_DRAG_AND_DROP + wxClientDC dc(this); PrepareDC(dc); dc.SetFont(GetFont()); @@ -717,7 +841,11 @@ void wxRichTextCtrl::OnMoveMouse(wxMouseEvent& event) return; } - if (m_dragging) + if (m_dragging +#if wxUSE_DRAG_AND_DROP + && !m_preDrag +#endif + ) { wxRichTextParagraphLayoutBox* commonAncestor = NULL; wxRichTextParagraphLayoutBox* otherContainer = NULL; @@ -790,7 +918,11 @@ void wxRichTextCtrl::OnMoveMouse(wxMouseEvent& event) } } - if (hitObj && m_dragging && hit != wxRICHTEXT_HITTEST_NONE && m_selectionState == wxRichTextCtrlSelectionState_Normal) + if (hitObj && m_dragging && hit != wxRICHTEXT_HITTEST_NONE && m_selectionState == wxRichTextCtrlSelectionState_Normal +#if wxUSE_DRAG_AND_DROP + && !m_preDrag +#endif + ) { // TODO: test closeness SetCaretPositionAfterClick(container, position, hit, true /* extend selection */); @@ -2592,6 +2724,23 @@ wxRichTextCtrl::HitTest(const wxPoint& pt, return wxTE_HT_UNKNOWN; } +wxRichTextParagraphLayoutBox* +wxRichTextCtrl::FindContainerAtPoint(const wxPoint pt, long& position, int& hit, wxRichTextObject* hitObj, int flags/* = 0*/) +{ + wxClientDC dc(this); + PrepareDC(dc); + dc.SetFont(GetFont()); + + wxPoint logicalPt = GetLogicalPoint(pt); + + wxRichTextObject* contextObj = NULL; + hit = GetBuffer().HitTest(dc, logicalPt, position, &hitObj, &contextObj, flags); + wxRichTextParagraphLayoutBox* container = wxDynamicCast(contextObj, wxRichTextParagraphLayoutBox); + + return container; +} + + // ---------------------------------------------------------------------------- // set/get the controls text // ---------------------------------------------------------------------------- @@ -4176,6 +4325,85 @@ bool wxRichTextCtrl::SetFocusObject(wxRichTextParagraphLayoutBox* obj, bool setC return true; } +#if wxUSE_DRAG_AND_DROP +void wxRichTextCtrl::OnDrop(wxCoord WXUNUSED(x), wxCoord WXUNUSED(y), wxDragResult def, wxDataObject* DataObj) +{ + m_preDrag = false; + + if ((def != wxDragCopy) && (def != wxDragMove)) + { + return; + } + + if (!GetSelection().IsValid()) + { + return; + } + + wxRichTextParagraphLayoutBox* originContainer = GetSelection().GetContainer(); + wxRichTextParagraphLayoutBox* destContainer = GetFocusObject(); // This will be the drop container, not necessarily the same as the origin one + + + wxRichTextBuffer* richTextBuffer = ((wxRichTextBufferDataObject*)DataObj)->GetRichTextBuffer(); + if (richTextBuffer) + { + long position = GetCaretPosition(); + wxRichTextRange selectionrange = GetInternalSelectionRange(); + if (selectionrange.Contains(position) && (def == wxDragMove)) + { + // It doesn't make sense to move onto itself + return; + } + + // If we're moving, and the data is being moved forward, we need to drop first, then delete the selection + // If moving backwards, we need to delete then drop. If we're copying (or doing nothing) we don't delete anyway + bool DeleteAfter = (def == wxDragMove) && (position > selectionrange.GetEnd()); + if ((def == wxDragMove) && !DeleteAfter) + { + // We can't use e.g. DeleteSelectedContent() as it uses the focus container + originContainer->DeleteRangeWithUndo(selectionrange, this, &GetBuffer()); + } + + destContainer->InsertParagraphsWithUndo(position+1, *richTextBuffer, this, &GetBuffer(), 0); + ShowPosition(position + richTextBuffer->GetOwnRange().GetEnd()); + + delete richTextBuffer; + + if (DeleteAfter) + { + // We can't use e.g. DeleteSelectedContent() as it uses the focus container + originContainer->DeleteRangeWithUndo(selectionrange, this, &GetBuffer()); + } + + + SelectNone(); + Refresh(); + } +} +#endif // wxUSE_DRAG_AND_DROP + + +#if wxUSE_DRAG_AND_DROP +bool wxRichTextDropSource::GiveFeedback(wxDragResult WXUNUSED(effect)) +{ + wxCHECK_MSG(m_rtc, false, wxT("NULL m_rtc")); + + long position = 0; + int hit = 0; + wxRichTextObject* hitObj = NULL; + wxRichTextParagraphLayoutBox* container = m_rtc->FindContainerAtPoint(m_rtc->ScreenToClient(wxGetMousePosition()), position, hit, hitObj); + + if (!(hit & wxRICHTEXT_HITTEST_NONE) && container && container->AcceptsFocus()) + { + m_rtc->StoreFocusObject(container); + m_rtc->SetCaretPositionAfterClick(container, position, hit); + } + + return false; // so that the base-class sets a cursor +} +#endif // wxUSE_DRAG_AND_DROP + + #if wxRICHTEXT_USE_OWN_CARET // ---------------------------------------------------------------------------- -- 2.45.2