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
This commit is contained in:
Julian Smart 2012-01-03 14:09:34 +00:00
parent 606e096810
commit 0c0e063e66
3 changed files with 367 additions and 22 deletions

View File

@ -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
/**
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; }
#if wxUSE_DATETIME
/**
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; }
#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

View File

@ -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;

View File

@ -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
// ----------------------------------------------------------------------------