// 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)); } } } } }