// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Threading;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
namespace ICSharpCode.AvalonEdit.CodeCompletion
{
///
/// The code completion window.
///
public class CompletionWindow : CompletionWindowBase
{
readonly CompletionList completionList = new CompletionList();
ToolTip toolTip = new ToolTip();
///
/// Gets the completion list used in this completion window.
///
public CompletionList CompletionList {
get { return completionList; }
}
///
/// Creates a new code completion window.
///
public CompletionWindow(TextArea textArea) : base(textArea)
{
// keep height automatic
this.CloseAutomatically = true;
this.SizeToContent = SizeToContent.Height;
this.MaxHeight = 300;
this.Width = 175;
this.Content = completionList;
// prevent user from resizing window to 0x0
this.MinHeight = 15;
this.MinWidth = 30;
toolTip.PlacementTarget = this;
toolTip.Placement = PlacementMode.Right;
toolTip.Closed += toolTip_Closed;
AttachEvents();
}
#region ToolTip handling
void toolTip_Closed(object sender, RoutedEventArgs e)
{
// Clear content after tooltip is closed.
// We cannot clear is immediately when setting IsOpen=false
// because the tooltip uses an animation for closing.
if (toolTip != null)
toolTip.Content = null;
}
void completionList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var item = completionList.SelectedItem;
if (item == null)
return;
object description = item.Description;
if (description != null) {
string descriptionText = description as string;
if (descriptionText != null) {
toolTip.Content = new TextBlock {
Text = descriptionText,
TextWrapping = TextWrapping.Wrap
};
} else {
toolTip.Content = description;
}
toolTip.IsOpen = true;
} else {
toolTip.IsOpen = false;
}
}
#endregion
void completionList_InsertionRequested(object sender, EventArgs e)
{
Close();
// The window must close before Complete() is called.
// If the Complete callback pushes stacked input handlers, we don't want to pop those when the CC window closes.
var item = completionList.SelectedItem;
if (item != null)
item.Complete(this.TextArea, new AnchorSegment(this.TextArea.Document, this.StartOffset, this.EndOffset - this.StartOffset), e);
}
void AttachEvents()
{
this.completionList.InsertionRequested += completionList_InsertionRequested;
this.completionList.SelectionChanged += completionList_SelectionChanged;
this.TextArea.Caret.PositionChanged += CaretPositionChanged;
this.TextArea.MouseWheel += textArea_MouseWheel;
this.TextArea.PreviewTextInput += textArea_PreviewTextInput;
}
///
protected override void DetachEvents()
{
this.completionList.InsertionRequested -= completionList_InsertionRequested;
this.completionList.SelectionChanged -= completionList_SelectionChanged;
this.TextArea.Caret.PositionChanged -= CaretPositionChanged;
this.TextArea.MouseWheel -= textArea_MouseWheel;
this.TextArea.PreviewTextInput -= textArea_PreviewTextInput;
base.DetachEvents();
}
///
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
if (toolTip != null) {
toolTip.IsOpen = false;
toolTip = null;
}
}
///
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (!e.Handled) {
completionList.HandleKey(e);
}
}
void textArea_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
e.Handled = RaiseEventPair(this, PreviewTextInputEvent, TextInputEvent,
new TextCompositionEventArgs(e.Device, e.TextComposition));
}
void textArea_MouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = RaiseEventPair(GetScrollEventTarget(),
PreviewMouseWheelEvent, MouseWheelEvent,
new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta));
}
UIElement GetScrollEventTarget()
{
if (completionList == null)
return this;
return completionList.ScrollViewer ?? completionList.ListBox ?? (UIElement)completionList;
}
///
/// Gets/Sets whether the completion window should close automatically.
/// The default value is true.
///
public bool CloseAutomatically { get; set; }
///
protected override bool CloseOnFocusLost {
get { return this.CloseAutomatically; }
}
///
/// When this flag is set, code completion closes if the caret moves to the
/// beginning of the allowed range. This is useful in Ctrl+Space and "complete when typing",
/// but not in dot-completion.
/// Has no effect if CloseAutomatically is false.
///
public bool CloseWhenCaretAtBeginning { get; set; }
void CaretPositionChanged(object sender, EventArgs e)
{
int offset = this.TextArea.Caret.Offset;
if (offset == this.StartOffset) {
if (CloseAutomatically && CloseWhenCaretAtBeginning) {
Close();
} else {
completionList.SelectItem(string.Empty);
}
return;
}
if (offset < this.StartOffset || offset > this.EndOffset) {
if (CloseAutomatically) {
Close();
}
} else {
TextDocument document = this.TextArea.Document;
if (document != null) {
completionList.SelectItem(document.GetText(this.StartOffset, offset - this.StartOffset));
}
}
}
}
}