From 835d6edfecda53b9573908177e270e08cd9d1b3f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 12 Sep 2024 16:03:50 +0200 Subject: [PATCH 1/2] Enable drag/drop. Fixes #232 --- src/AvaloniaEdit.Demo/MainWindow.xaml.cs | 1 + src/AvaloniaEdit.Demo/Program.cs | 4 +- .../Editing/EditingCommandHandler.cs | 19 + .../Editing/RectangleSelection.cs | 32 +- src/AvaloniaEdit/Editing/Selection.cs | 61 +-- .../Editing/SelectionMouseHandler.cs | 411 ++++++++++-------- 6 files changed, 294 insertions(+), 234 deletions(-) diff --git a/src/AvaloniaEdit.Demo/MainWindow.xaml.cs b/src/AvaloniaEdit.Demo/MainWindow.xaml.cs index 600d6bdc..de648ca8 100644 --- a/src/AvaloniaEdit.Demo/MainWindow.xaml.cs +++ b/src/AvaloniaEdit.Demo/MainWindow.xaml.cs @@ -55,6 +55,7 @@ public MainWindow() _textEditor.TextArea.TextEntered += textEditor_TextArea_TextEntered; _textEditor.TextArea.TextEntering += textEditor_TextArea_TextEntering; _textEditor.Options.AllowToggleOverstrikeMode = true; + _textEditor.Options.EnableTextDragDrop = true; _textEditor.Options.ShowBoxForControlCharacters = true; _textEditor.Options.ColumnRulerPositions = new List() { 80, 100 }; _textEditor.TextArea.IndentationStrategy = new Indentation.CSharp.CSharpIndentationStrategy(_textEditor.Options); diff --git a/src/AvaloniaEdit.Demo/Program.cs b/src/AvaloniaEdit.Demo/Program.cs index 6cf5095b..4aaaeac1 100644 --- a/src/AvaloniaEdit.Demo/Program.cs +++ b/src/AvaloniaEdit.Demo/Program.cs @@ -1,4 +1,5 @@ -using Avalonia; +using System; +using Avalonia; namespace AvaloniaEdit.Demo { @@ -9,6 +10,7 @@ public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure().UsePlatformDetect(); // The entry point. Things aren't ready yet + [STAThread] public static int Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); } diff --git a/src/AvaloniaEdit/Editing/EditingCommandHandler.cs b/src/AvaloniaEdit/Editing/EditingCommandHandler.cs index 50940a74..dcb8c327 100644 --- a/src/AvaloniaEdit/Editing/EditingCommandHandler.cs +++ b/src/AvaloniaEdit/Editing/EditingCommandHandler.cs @@ -25,6 +25,7 @@ using Avalonia.Input; using AvaloniaEdit.Utils; using Avalonia.Controls; +using static System.Net.Mime.MediaTypeNames; namespace AvaloniaEdit.Editing { @@ -422,6 +423,14 @@ private static bool CopySelectedText(TextArea textArea) return true; } + public static bool ConfirmDataFormat(TextArea textArea, DataObject dataObject, string format) + { + return true; + ////var e = new DataObjectSettingDataEventArgs(dataObject, format); + ////textArea.RaiseEvent(e); + ////return !e.CommandCancelled; + } + private static void SetClipboardText(string text, Visual visual) { try @@ -526,6 +535,16 @@ private static async void OnPaste(object target, ExecutedRoutedEventArgs args) } } + internal static string GetTextToPaste(IDataObject dataObject, TextArea textArea) + { + if (dataObject.Contains(DataFormats.Text)) + { + return GetTextToPaste((string)dataObject.Get(DataFormats.Text), textArea); + } + + return null; + } + internal static string GetTextToPaste(string text, TextArea textArea) { try diff --git a/src/AvaloniaEdit/Editing/RectangleSelection.cs b/src/AvaloniaEdit/Editing/RectangleSelection.cs index 0c53fa2a..5877a4a1 100644 --- a/src/AvaloniaEdit/Editing/RectangleSelection.cs +++ b/src/AvaloniaEdit/Editing/RectangleSelection.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using Avalonia; @@ -383,24 +384,23 @@ public static bool PerformRectangularPaste(TextArea textArea, TextViewPosition s return false; } - // TODO: clipboard - - ///// - ///// Gets the name of the entry in the DataObject that signals rectangle selections. - ///// - //public const string RectangularSelectionDataType = "AvalonEditRectangularSelection"; + /// + /// Gets the name of the entry in the DataObject that signals rectangle selections. + /// + public const string RectangularSelectionDataType = "AvalonEditRectangularSelection"; - //public override System.Windows.DataObject CreateDataObject(TextArea textArea) - //{ - // var data = base.CreateDataObject(textArea); + public override Avalonia.Input.DataObject CreateDataObject(TextArea textArea) + { + var data = base.CreateDataObject(textArea); - // if (EditingCommandHandler.ConfirmDataFormat(textArea, data, RectangularSelectionDataType)) { - // MemoryStream isRectangle = new MemoryStream(1); - // isRectangle.WriteByte(1); - // data.SetData(RectangularSelectionDataType, isRectangle, false); - // } - // return data; - //} + if (EditingCommandHandler.ConfirmDataFormat(textArea, data, RectangularSelectionDataType)) + { + MemoryStream isRectangle = new MemoryStream(1); + isRectangle.WriteByte(1); + data.Set(RectangularSelectionDataType, isRectangle); + } + return data; + } /// public override string ToString() diff --git a/src/AvaloniaEdit/Editing/Selection.cs b/src/AvaloniaEdit/Editing/Selection.cs index 6ed4ebda..c11c8c22 100644 --- a/src/AvaloniaEdit/Editing/Selection.cs +++ b/src/AvaloniaEdit/Editing/Selection.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Avalonia.Input; using AvaloniaEdit.Document; using AvaloniaEdit.Utils; @@ -262,37 +263,39 @@ public virtual bool Contains(int offset) Segments.Any(s => s.Contains(offset, 0)); } - // TODO: clipboard - ///// - ///// Creates a data object containing the selection's text. - ///// - //public virtual DataObject CreateDataObject(TextArea textArea) - //{ - // DataObject data = new DataObject(); - - // // Ensure we use the appropriate newline sequence for the OS - // string text = TextUtilities.NormalizeNewLines(GetText(), Environment.NewLine); + /// + /// Creates a data object containing the selection's text. + /// + public virtual DataObject CreateDataObject(TextArea textArea) + { + DataObject data = new DataObject(); - // // Enable drag/drop to Word, Notepad++ and others - // if (EditingCommandHandler.ConfirmDataFormat(textArea, data, DataFormats.UnicodeText)) { - // data.SetText(text); - // } + // Ensure we use the appropriate newline sequence for the OS + string text = TextUtilities.NormalizeNewLines(GetText(), Environment.NewLine); - // // Enable drag/drop to SciTe: - // // We cannot use SetText, thus we need to use typeof(string).FullName as data format. - // // new DataObject(object) calls SetData(object), which in turn calls SetData(Type, data), - // // which then uses Type.FullName as format. - // // We immitate that behavior here as well: - // if (EditingCommandHandler.ConfirmDataFormat(textArea, data, typeof(string).FullName)) { - // data.SetData(typeof(string).FullName, text); - // } + // Enable drag/drop to Word, Notepad++ and others + if (EditingCommandHandler.ConfirmDataFormat(textArea, data, DataFormats.Text)) + { + data.Set(DataFormats.Text, text); + } - // // Also copy text in HTML format to clipboard - good for pasting text into Word - // // or to the SharpDevelop forums. - // if (EditingCommandHandler.ConfirmDataFormat(textArea, data, DataFormats.Html)) { - // HtmlClipboard.SetHtml(data, CreateHtmlFragment(new HtmlOptions(textArea.Options))); - // } - // return data; - //} + // Enable drag/drop to SciTe: + // We cannot use SetText, thus we need to use typeof(string).FullName as data format. + // new DataObject(object) calls SetData(object), which in turn calls SetData(Type, data), + // which then uses Type.FullName as format. + // We immitate that behavior here as well: + ////if (EditingCommandHandler.ConfirmDataFormat(textArea, data, typeof(string).FullName)) + ////{ + //// data.SetData(typeof(string).FullName, text); + ////} + + // Also copy text in HTML format to clipboard - good for pasting text into Word + // or to the SharpDevelop forums. + ////if (EditingCommandHandler.ConfirmDataFormat(textArea, data, DataFormats.Html)) + ////{ + //// HtmlClipboard.SetHtml(data, CreateHtmlFragment(new HtmlOptions(textArea.Options))); + ////} + return data; + } } } diff --git a/src/AvaloniaEdit/Editing/SelectionMouseHandler.cs b/src/AvaloniaEdit/Editing/SelectionMouseHandler.cs index ed5d76f1..8fda6024 100644 --- a/src/AvaloniaEdit/Editing/SelectionMouseHandler.cs +++ b/src/AvaloniaEdit/Editing/SelectionMouseHandler.cs @@ -16,16 +16,16 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; using Avalonia; using Avalonia.Input; - +using Avalonia.Threading; using AvaloniaEdit.Document; using AvaloniaEdit.Utils; -using System; -using System.ComponentModel; -using System.Linq; - namespace AvaloniaEdit.Editing { /// @@ -112,24 +112,20 @@ public void Detach() private void AttachDragDrop() { - //textArea.AllowDrop = true; - //textArea.GiveFeedback += textArea_GiveFeedback; - //textArea.QueryContinueDrag += textArea_QueryContinueDrag; - //textArea.DragEnter += textArea_DragEnter; - //textArea.DragOver += textArea_DragOver; - //textArea.DragLeave += textArea_DragLeave; - //textArea.Drop += textArea_Drop; + DragDrop.SetAllowDrop(TextArea, true); + TextArea.AddHandler(DragDrop.DragEnterEvent, textArea_DragEnter); + TextArea.AddHandler(DragDrop.DragOverEvent, textArea_DragOver); + TextArea.AddHandler(DragDrop.DragLeaveEvent, textArea_DragLeave); + TextArea.AddHandler(DragDrop.DropEvent, textArea_Drop); } private void DetachDragDrop() { - //textArea.AllowDrop = false; - //textArea.GiveFeedback -= textArea_GiveFeedback; - //textArea.QueryContinueDrag -= textArea_QueryContinueDrag; - //textArea.DragEnter -= textArea_DragEnter; - //textArea.DragOver -= textArea_DragOver; - //textArea.DragLeave -= textArea_DragLeave; - //textArea.Drop -= textArea_Drop; + DragDrop.SetAllowDrop(TextArea, false); + TextArea.RemoveHandler(DragDrop.DragEnterEvent, textArea_DragEnter); + TextArea.RemoveHandler(DragDrop.DragOverEvent, textArea_DragOver); + TextArea.RemoveHandler(DragDrop.DragLeaveEvent, textArea_DragLeave); + TextArea.RemoveHandler(DragDrop.DropEvent, textArea_Drop); } private bool _enableTextDragDrop; @@ -149,120 +145,148 @@ private void TextArea_OptionChanged(object sender, PropertyChangedEventArgs e) #endregion #region Dropping text - //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] - //void textArea_DragEnter(object sender, DragEventArgs e) - //{ - // try { - // e.Effects = GetEffect(e); - // textArea.Caret.Show(); - // } catch (Exception ex) { - // OnDragException(ex); - // } - //} + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + void textArea_DragEnter(object sender, DragEventArgs e) + { + try + { + e.DragEffects = GetEffect(e); + TextArea.Caret.Show(); + } + catch (Exception ex) + { + OnDragException(ex); + } + } - //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] - //void textArea_DragOver(object sender, DragEventArgs e) - //{ - // try { - // e.Effects = GetEffect(e); - // } catch (Exception ex) { - // OnDragException(ex); - // } - //} + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + void textArea_DragOver(object sender, DragEventArgs e) + { + try + { + e.DragEffects = GetEffect(e); + } + catch (Exception ex) + { + OnDragException(ex); + } + } - //DragDropEffects GetEffect(DragEventArgs e) - //{ - // if (e.Data.GetDataPresent(DataFormats.UnicodeText, true)) { - // e.Handled = true; - // int visualColumn; - // bool isAtEndOfLine; - // int offset = GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn, out isAtEndOfLine); - // if (offset >= 0) { - // textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn) { IsAtEndOfLine = isAtEndOfLine }; - // textArea.Caret.DesiredXPos = double.NaN; - // if (textArea.ReadOnlySectionProvider.CanInsert(offset)) { - // if ((e.AllowedEffects & DragDropEffects.Move) == DragDropEffects.Move - // && (e.KeyStates & DragDropKeyStates.ControlKey) != DragDropKeyStates.ControlKey) - // { - // return DragDropEffects.Move; - // } else { - // return e.AllowedEffects & DragDropEffects.Copy; - // } - // } - // } - // } - // return DragDropEffects.None; - //} + DragDropEffects GetEffect(DragEventArgs e) + { + if (e.Data.Contains(DataFormats.Text)) + { + e.Handled = true; + int visualColumn; + bool isAtEndOfLine; + int offset = GetOffsetFromMousePosition(e.GetPosition(TextArea.TextView), out visualColumn, out isAtEndOfLine); + if (offset >= 0) + { + TextArea.Caret.Position = new TextViewPosition(TextArea.Document.GetLocation(offset), visualColumn) { IsAtEndOfLine = isAtEndOfLine }; + TextArea.Caret.DesiredXPos = double.NaN; + if (TextArea.ReadOnlySectionProvider.CanInsert(offset)) + { + if ((e.DragEffects & DragDropEffects.Move) == DragDropEffects.Move + && (e.KeyModifiers & KeyModifiers.Control) != KeyModifiers.Control) + { + return DragDropEffects.Move; + } + else + { + return e.DragEffects & DragDropEffects.Copy; + } + } + } + } + return DragDropEffects.None; + } - //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] - //void textArea_DragLeave(object sender, DragEventArgs e) - //{ - // try { - // e.Handled = true; - // if (!textArea.IsKeyboardFocusWithin) - // textArea.Caret.Hide(); - // } catch (Exception ex) { - // OnDragException(ex); - // } - //} + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + void textArea_DragLeave(object sender, DragEventArgs e) + { + try + { + e.Handled = true; + if (!TextArea.IsKeyboardFocusWithin) + TextArea.Caret.Hide(); + } + catch (Exception ex) + { + OnDragException(ex); + } + } - //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] - //void textArea_Drop(object sender, DragEventArgs e) - //{ - // try { - // DragDropEffects effect = GetEffect(e); - // e.Effects = effect; - // if (effect != DragDropEffects.None) { - // int start = textArea.Caret.Offset; - // if (mode == SelectionMode.Drag && textArea.Selection.Contains(start)) { - // Debug.WriteLine("Drop: did not drop: drop target is inside selection"); - // e.Effects = DragDropEffects.None; - // } else { - // Debug.WriteLine("Drop: insert at " + start); - - // var pastingEventArgs = new DataObjectPastingEventArgs(e.Data, true, DataFormats.UnicodeText); - // textArea.RaiseEvent(pastingEventArgs); - // if (pastingEventArgs.CommandCancelled) - // return; - - // string text = EditingCommandHandler.GetTextToPaste(pastingEventArgs, textArea); - // if (text == null) - // return; - // bool rectangular = pastingEventArgs.DataObject.GetDataPresent(RectangleSelection.RectangularSelectionDataType); - - // // Mark the undo group with the currentDragDescriptor, if the drag - // // is originating from the same control. This allows combining - // // the undo groups when text is moved. - // textArea.Document.UndoStack.StartUndoGroup(this.currentDragDescriptor); - // try { - // if (rectangular && RectangleSelection.PerformRectangularPaste(textArea, textArea.Caret.Position, text, true)) { - - // } else { - // textArea.Document.Insert(start, text); - // textArea.Selection = Selection.Create(textArea, start, start + text.Length); - // } - // } finally { - // textArea.Document.UndoStack.EndUndoGroup(); - // } - // } - // e.Handled = true; - // } - // } catch (Exception ex) { - // OnDragException(ex); - // } - //} + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + void textArea_Drop(object sender, DragEventArgs e) + { + try + { + DragDropEffects effect = GetEffect(e); + e.DragEffects = effect; + if (effect != DragDropEffects.None) + { + int start = TextArea.Caret.Offset; + if (_mode == SelectionMode.Drag && TextArea.Selection.Contains(start)) + { + Debug.WriteLine("Drop: did not drop: drop target is inside selection"); + e.DragEffects = DragDropEffects.None; + } + else + { + Debug.WriteLine("Drop: insert at " + start); - //void OnDragException(Exception ex) - //{ - // // swallows exceptions during drag'n'drop or reports them incorrectly, so - // // we re-throw them later to allow the application's unhandled exception handler - // // to catch them - // textArea.Dispatcher.BeginInvoke( - // DispatcherPriority.Send, - // new Action(delegate { - // throw new DragDropException("Exception during drag'n'drop", ex); - // })); - //} + ////var pastingEventArgs = new DataObjectPastingEventArgs(e.Data, true, DataFormats.UnicodeText); + ////TextArea.RaiseEvent(pastingEventArgs); + ////if (pastingEventArgs.CommandCancelled) + //// return; + + string text = EditingCommandHandler.GetTextToPaste(e.Data, TextArea); + if (text == null) + return; + bool rectangular = e.Data.Contains(RectangleSelection.RectangularSelectionDataType); + + // Mark the undo group with the currentDragDescriptor, if the drag + // is originating from the same control. This allows combining + // the undo groups when text is moved. + TextArea.Document.UndoStack.StartUndoGroup(this.currentDragDescriptor); + try + { + if (rectangular && RectangleSelection.PerformRectangularPaste(TextArea, TextArea.Caret.Position, text, true)) + { + + } + else + { + TextArea.Document.Insert(start, text); + TextArea.Selection = Selection.Create(TextArea, start, start + text.Length); + } + } + finally + { + TextArea.Document.UndoStack.EndUndoGroup(); + } + } + e.Handled = true; + } + } + catch (Exception ex) + { + OnDragException(ex); + } + } + + void OnDragException(Exception ex) + { + // swallows exceptions during drag'n'drop or reports them incorrectly, so + // we re-throw them later to allow the application's unhandled exception handler + // to catch them + Dispatcher.UIThread.Post( + new Action(delegate + { + throw new DragDropException("Exception during drag'n'drop", ex); + }), + DispatcherPriority.Send); + } //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] //void textArea_GiveFeedback(object sender, GiveFeedbackEventArgs e) @@ -294,71 +318,83 @@ private void TextArea_OptionChanged(object sender, PropertyChangedEventArgs e) #endregion #region Start Drag - //object currentDragDescriptor; + object currentDragDescriptor; - //void StartDrag() - //{ - // // prevent nested StartDrag calls - // mode = SelectionMode.Drag; + async void StartDrag(PointerEventArgs e) + { + // prevent nested StartDrag calls + _mode = SelectionMode.Drag; - // // mouse capture and Drag'n'Drop doesn't mix - // textArea.ReleaseMouseCapture(); + // mouse capture and Drag'n'Drop doesn't mix + e.Pointer.Capture(null); - // DataObject dataObject = textArea.Selection.CreateDataObject(textArea); + DataObject dataObject = TextArea.Selection.CreateDataObject(TextArea); - // DragDropEffects allowedEffects = DragDropEffects.All; - // var deleteOnMove = textArea.Selection.Segments.Select(s => new AnchorSegment(textArea.Document, s)).ToList(); - // foreach (ISegment s in deleteOnMove) { - // ISegment[] result = textArea.GetDeletableSegments(s); - // if (result.Length != 1 || result[0].Offset != s.Offset || result[0].EndOffset != s.EndOffset) { - // allowedEffects &= ~DragDropEffects.Move; - // } - // } + DragDropEffects allowedEffects = DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link; + var deleteOnMove = TextArea.Selection.Segments.Select(s => new AnchorSegment(TextArea.Document, s)).ToList(); + foreach (ISegment s in deleteOnMove) + { + ISegment[] result = TextArea.GetDeletableSegments(s); + if (result.Length != 1 || result[0].Offset != s.Offset || result[0].EndOffset != s.EndOffset) + { + allowedEffects &= ~DragDropEffects.Move; + } + } - // var copyingEventArgs = new DataObjectCopyingEventArgs(dataObject, true); - // textArea.RaiseEvent(copyingEventArgs); - // if (copyingEventArgs.CommandCancelled) - // return; - - // object dragDescriptor = new object(); - // this.currentDragDescriptor = dragDescriptor; - - // DragDropEffects resultEffect; - // using (textArea.AllowCaretOutsideSelection()) { - // var oldCaretPosition = textArea.Caret.Position; - // try { - // Debug.WriteLine("DoDragDrop with allowedEffects=" + allowedEffects); - // resultEffect = DragDrop.DoDragDrop(textArea, dataObject, allowedEffects); - // Debug.WriteLine("DoDragDrop done, resultEffect=" + resultEffect); - // } catch (COMException ex) { - // // ignore COM errors - don't crash on badly implemented drop targets - // Debug.WriteLine("DoDragDrop failed: " + ex.ToString()); - // return; - // } - // if (resultEffect == DragDropEffects.None) { - // // reset caret if drag was aborted - // textArea.Caret.Position = oldCaretPosition; - // } - // } + //var copyingEventArgs = new DataObjectCopyingEventArgs(dataObject, true); + //TextArea.RaiseEvent(copyingEventArgs); + //if (copyingEventArgs.CommandCancelled) + // return; - // this.currentDragDescriptor = null; + object dragDescriptor = new object(); + this.currentDragDescriptor = dragDescriptor; - // if (deleteOnMove != null && resultEffect == DragDropEffects.Move && (allowedEffects & DragDropEffects.Move) == DragDropEffects.Move) { - // bool draggedInsideSingleDocument = (dragDescriptor == textArea.Document.UndoStack.LastGroupDescriptor); - // if (draggedInsideSingleDocument) - // textArea.Document.UndoStack.StartContinuedUndoGroup(null); - // textArea.Document.BeginUpdate(); - // try { - // foreach (ISegment s in deleteOnMove) { - // textArea.Document.Remove(s.Offset, s.Length); - // } - // } finally { - // textArea.Document.EndUpdate(); - // if (draggedInsideSingleDocument) - // textArea.Document.UndoStack.EndUndoGroup(); - // } - // } - //} + DragDropEffects resultEffect; + using (TextArea.AllowCaretOutsideSelection()) + { + var oldCaretPosition = TextArea.Caret.Position; + try + { + Debug.WriteLine("DoDragDrop with allowedEffects=" + allowedEffects); + resultEffect = await DragDrop.DoDragDrop(e, dataObject, allowedEffects); + Debug.WriteLine("DoDragDrop done, resultEffect=" + resultEffect); + } + catch (Exception ex) + { + // ignore COM errors - don't crash on badly implemented drop targets + Debug.WriteLine("DoDragDrop failed: " + ex.ToString()); + return; + } + if (resultEffect == DragDropEffects.None) + { + // reset caret if drag was aborted + TextArea.Caret.Position = oldCaretPosition; + } + } + + this.currentDragDescriptor = null; + + if (deleteOnMove != null && resultEffect == DragDropEffects.Move && (allowedEffects & DragDropEffects.Move) == DragDropEffects.Move) + { + bool draggedInsideSingleDocument = (dragDescriptor == TextArea.Document.UndoStack.LastGroupDescriptor); + if (draggedInsideSingleDocument) + TextArea.Document.UndoStack.StartContinuedUndoGroup(null); + TextArea.Document.BeginUpdate(); + try + { + foreach (ISegment s in deleteOnMove) + { + TextArea.Document.Remove(s.Offset, s.Length); + } + } + finally + { + TextArea.Document.EndUpdate(); + if (draggedInsideSingleDocument) + TextArea.Document.UndoStack.EndUndoGroup(); + } + } + } #endregion #region QueryCursor @@ -641,8 +677,7 @@ private void TextArea_MouseMove(object sender, PointerEventArgs e) if (Math.Abs(mouseMovement.X) > MinimumHorizontalDragDistance || Math.Abs(mouseMovement.Y) > MinimumVerticalDragDistance) { - // TODO: drag - //StartDrag(); + StartDrag(e); } } } From c7cc54123240b01db0524b7202b6429e9db6384a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 12 Sep 2024 16:23:26 +0200 Subject: [PATCH 2/2] Added DataObjectEx.CopyingEvent. Avalonia doesn't have this event, but it's needed in order to customize drag/drop operations. --- .../Document/DataObjectCopyingEventArgs.cs | 14 ++++++++++++++ .../Editing/SelectionMouseHandler.cs | 8 ++++---- src/AvaloniaEdit/Utils/DataObjectEx.cs | 16 ++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 src/AvaloniaEdit/Document/DataObjectCopyingEventArgs.cs create mode 100644 src/AvaloniaEdit/Utils/DataObjectEx.cs diff --git a/src/AvaloniaEdit/Document/DataObjectCopyingEventArgs.cs b/src/AvaloniaEdit/Document/DataObjectCopyingEventArgs.cs new file mode 100644 index 00000000..520024e7 --- /dev/null +++ b/src/AvaloniaEdit/Document/DataObjectCopyingEventArgs.cs @@ -0,0 +1,14 @@ +using Avalonia.Input; +using Avalonia.Interactivity; +using AvaloniaEdit.Utils; + +namespace AvaloniaEdit.Document; + +public class DataObjectCopyingEventArgs(IDataObject dataObject, bool isDragDrop) : + RoutedEventArgs(DataObjectEx.DataObjectCopyingEvent) +{ + public bool CommandCancelled { get; private set; } + public IDataObject DataObject { get; } = dataObject; + public bool IsDragDrop { get; } = isDragDrop; + public void CancelCommand() => CommandCancelled = true; +} diff --git a/src/AvaloniaEdit/Editing/SelectionMouseHandler.cs b/src/AvaloniaEdit/Editing/SelectionMouseHandler.cs index 8fda6024..955e590d 100644 --- a/src/AvaloniaEdit/Editing/SelectionMouseHandler.cs +++ b/src/AvaloniaEdit/Editing/SelectionMouseHandler.cs @@ -341,10 +341,10 @@ async void StartDrag(PointerEventArgs e) } } - //var copyingEventArgs = new DataObjectCopyingEventArgs(dataObject, true); - //TextArea.RaiseEvent(copyingEventArgs); - //if (copyingEventArgs.CommandCancelled) - // return; + var copyingEventArgs = new DataObjectCopyingEventArgs(dataObject, true); + TextArea.RaiseEvent(copyingEventArgs); + if (copyingEventArgs.CommandCancelled) + return; object dragDescriptor = new object(); this.currentDragDescriptor = dragDescriptor; diff --git a/src/AvaloniaEdit/Utils/DataObjectEx.cs b/src/AvaloniaEdit/Utils/DataObjectEx.cs new file mode 100644 index 00000000..47fdc238 --- /dev/null +++ b/src/AvaloniaEdit/Utils/DataObjectEx.cs @@ -0,0 +1,16 @@ +using Avalonia.Interactivity; +using AvaloniaEdit.Document; + +namespace AvaloniaEdit.Utils; + +public static class DataObjectEx +{ + /// + /// Shim for WPF's DataObject.CopyingEvent which is not available in Avalonia. + /// + public static readonly RoutedEvent DataObjectCopyingEvent = + RoutedEvent.Register( + nameof(DataObjectCopyingEvent), + RoutingStrategies.Bubble, + typeof(DataObjectEx)); +}