// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media.TextFormatting;
using System.Windows.Threading;
using Tango.Scripting.Editors.Document;
using Tango.Scripting.Editors.Rendering;
using Tango.Scripting.Editors.Utils;
namespace Tango.Scripting.Editors.Editing
{
///
/// Handles selection of text using the mouse.
///
sealed class SelectionMouseHandler : ITextAreaInputHandler
{
#region enum SelectionMode
enum SelectionMode
{
///
/// no selection (no mouse button down)
///
None,
///
/// left mouse button down on selection, might be normal click
/// or might be drag'n'drop
///
PossibleDragStart,
///
/// dragging text
///
Drag,
///
/// normal selection (click+drag)
///
Normal,
///
/// whole-word selection (double click+drag or ctrl+click+drag)
///
WholeWord,
///
/// whole-line selection (triple click+drag)
///
WholeLine,
///
/// rectangular selection (alt+click+drag)
///
Rectangular
}
#endregion
readonly TextArea textArea;
SelectionMode mode;
AnchorSegment startWord;
Point possibleDragStartMousePos;
#region Constructor + Attach + Detach
public SelectionMouseHandler(TextArea textArea)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
this.textArea = textArea;
}
public TextArea TextArea {
get { return textArea; }
}
public void Attach()
{
textArea.MouseLeftButtonDown += textArea_MouseLeftButtonDown;
textArea.MouseMove += textArea_MouseMove;
textArea.MouseLeftButtonUp += textArea_MouseLeftButtonUp;
textArea.QueryCursor += textArea_QueryCursor;
textArea.OptionChanged += textArea_OptionChanged;
enableTextDragDrop = textArea.Options.EnableTextDragDrop;
if (enableTextDragDrop) {
AttachDragDrop();
}
}
public void Detach()
{
mode = SelectionMode.None;
textArea.MouseLeftButtonDown -= textArea_MouseLeftButtonDown;
textArea.MouseMove -= textArea_MouseMove;
textArea.MouseLeftButtonUp -= textArea_MouseLeftButtonUp;
textArea.QueryCursor -= textArea_QueryCursor;
textArea.OptionChanged -= textArea_OptionChanged;
if (enableTextDragDrop) {
DetachDragDrop();
}
}
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;
}
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;
}
bool enableTextDragDrop;
void textArea_OptionChanged(object sender, PropertyChangedEventArgs e)
{
bool newEnableTextDragDrop = textArea.Options.EnableTextDragDrop;
if (newEnableTextDragDrop != enableTextDragDrop) {
enableTextDragDrop = newEnableTextDragDrop;
if (newEnableTextDragDrop)
AttachDragDrop();
else
DetachDragDrop();
}
}
#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_DragOver(object sender, DragEventArgs e)
{
try {
e.Effects = GetEffect(e);
} catch (Exception ex) {
OnDragException(ex);
}
}
DragDropEffects GetEffect(DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.UnicodeText, true)) {
e.Handled = true;
int visualColumn;
int offset = GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn);
if (offset >= 0) {
textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn);
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;
}
[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) {
string text = e.Data.GetData(DataFormats.UnicodeText, true) as string;
if (text != null) {
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);
bool rectangular = e.Data.GetDataPresent(RectangleSelection.RectangularSelectionDataType);
string newLine = TextUtilities.GetNewLineFromDocument(textArea.Document, textArea.Caret.Line);
text = TextUtilities.NormalizeNewLines(text, newLine);
// 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)
{
// WPF 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.Normal,
new Action(delegate {
throw new DragDropException("Exception during drag'n'drop", ex);
}));
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
void textArea_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
try {
e.UseDefaultCursors = true;
e.Handled = true;
} catch (Exception ex) {
OnDragException(ex);
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
void textArea_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
{
try {
if (e.EscapePressed) {
e.Action = DragAction.Cancel;
} else if ((e.KeyStates & DragDropKeyStates.LeftMouseButton) != DragDropKeyStates.LeftMouseButton) {
e.Action = DragAction.Drop;
} else {
e.Action = DragAction.Continue;
}
e.Handled = true;
} catch (Exception ex) {
OnDragException(ex);
}
}
#endregion
#region Start Drag
object currentDragDescriptor;
void StartDrag()
{
// prevent nested StartDrag calls
mode = SelectionMode.Drag;
// mouse capture and Drag'n'Drop doesn't mix
textArea.ReleaseMouseCapture();
DataObject dataObject = textArea.Selection.CreateDataO
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using Tango.Scripting.Editors.Utils;
namespace Tango.Scripting.Editors.Document
{
/// <summary>
/// Contains weak event managers for the TextDocument events.
/// </summary>
public static class TextDocumentWeakEventManager
{
/// <summary>
/// Weak event manager for the <see cref="TextDocument.UpdateStarted"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public sealed class UpdateStarted : WeakEventManagerBase<UpdateStarted, TextDocument>
{
/// <inheritdoc/>
protected override void StartListening(TextDocument source)
{
source.UpdateStarted += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(TextDocument source)
{
source.UpdateStarted -= DeliverEvent;
}
}
/// <summary>
/// Weak event manager for the <see cref="TextDocument.UpdateFinished"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public sealed class UpdateFinished : WeakEventManagerBase<UpdateFinished, TextDocument>
{
/// <inheritdoc/>
protected override void StartListening(TextDocument source)
{
source.UpdateFinished += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(TextDocument source)
{
source.UpdateFinished -= DeliverEvent;
}
}
/// <summary>
/// Weak event manager for the <see cref="TextDocument.Changing"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public sealed class Changing : WeakEventManagerBase<Changing, TextDocument>
{
/// <inheritdoc/>
protected override void StartListening(TextDocument source)
{
source.Changing += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(TextDocument source)
{
source.Changing -= DeliverEvent;
}
}
/// <summary>
/// Weak event manager for the <see cref="TextDocument.Changed"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public sealed class Changed : WeakEventManagerBase<Changed, TextDocument>
{
/// <inheritdoc/>
protected override void StartListening(TextDocument source)
{
source.Changed += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(TextDocument source)
{
source.Changed -= DeliverEvent;
}
}
/// <summary>
/// Weak event manager for the <see cref="TextDocument.LineCountChanged"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
[Obsolete("The TextDocument.LineCountChanged event will be removed in a future version. Use PropertyChangedEventManager instead.")]
public sealed class LineCountChanged : WeakEventManagerBase<LineCountChanged, TextDocument>
{
/// <inheritdoc/>
protected override void StartListening(TextDocument source)
{
source.LineCountChanged += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(TextDocument source)
{
source.LineCountChanged -= DeliverEvent;
}
}
/// <summary>
/// Weak event manager for the <see cref="TextDocument.TextLengthChanged"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
[Obsolete("The TextDocument.TextLengthChanged event will be removed in a future version. Use PropertyChangedEventManager instead.")]
public sealed class TextLengthChanged : WeakEventManagerBase<TextLengthChanged, TextDocument>
{
/// <inheritdoc/>
protected override void StartListening(TextDocument source)
{
source.TextLengthChanged += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(TextDocument source)
{
source.TextLengthChanged -= DeliverEvent;
}
}
/// <summary>
/// Weak event manager for the <see cref="TextDocument.TextChanged"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public sealed class TextChanged : WeakEventManagerBase<TextChanged, TextDocument>
{
/// <inheritdoc/>
protected override void StartListening(TextDocument source)
{
source.TextChanged += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(TextDocument source)
{
source.TextChanged -= DeliverEvent;
}
}
}
}