Free cookie consent management tool by TermsFeed Policy Generator

source: branches/CodeEditor/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/CodeCompletion/CompletionWindowBase.cs @ 11700

Last change on this file since 11700 was 11700, checked in by jkarder, 9 years ago

#2077: created branch and added first version

File size: 13.4 KB
Line 
1// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19using System;
20using System.Diagnostics;
21using System.Linq;
22using System.Windows;
23using System.Windows.Controls.Primitives;
24using System.Windows.Input;
25using System.Windows.Threading;
26
27using ICSharpCode.AvalonEdit.Document;
28using ICSharpCode.AvalonEdit.Editing;
29using ICSharpCode.AvalonEdit.Rendering;
30using ICSharpCode.AvalonEdit.Utils;
31using ICSharpCode.NRefactory.Editor;
32
33namespace ICSharpCode.AvalonEdit.CodeCompletion
34{
35  /// <summary>
36  /// Base class for completion windows. Handles positioning the window at the caret.
37  /// </summary>
38  public class CompletionWindowBase : Window
39  {
40    static CompletionWindowBase()
41    {
42      WindowStyleProperty.OverrideMetadata(typeof(CompletionWindowBase), new FrameworkPropertyMetadata(WindowStyle.None));
43      ShowActivatedProperty.OverrideMetadata(typeof(CompletionWindowBase), new FrameworkPropertyMetadata(Boxes.False));
44      ShowInTaskbarProperty.OverrideMetadata(typeof(CompletionWindowBase), new FrameworkPropertyMetadata(Boxes.False));
45    }
46   
47    /// <summary>
48    /// Gets the parent TextArea.
49    /// </summary>
50    public TextArea TextArea { get; private set; }
51   
52    Window parentWindow;
53    TextDocument document;
54   
55    /// <summary>
56    /// Gets/Sets the start of the text range in which the completion window stays open.
57    /// This text portion is used to determine the text used to select an entry in the completion list by typing.
58    /// </summary>
59    public int StartOffset { get; set; }
60   
61    /// <summary>
62    /// Gets/Sets the end of the text range in which the completion window stays open.
63    /// This text portion is used to determine the text used to select an entry in the completion list by typing.
64    /// </summary>
65    public int EndOffset { get; set; }
66   
67    /// <summary>
68    /// Gets whether the window was opened above the current line.
69    /// </summary>
70    protected bool IsUp { get; private set; }
71   
72    /// <summary>
73    /// Creates a new CompletionWindowBase.
74    /// </summary>
75    public CompletionWindowBase(TextArea textArea)
76    {
77      if (textArea == null)
78        throw new ArgumentNullException("textArea");
79      this.TextArea = textArea;
80      parentWindow = Window.GetWindow(textArea);
81      this.Owner = parentWindow;
82      this.AddHandler(MouseUpEvent, new MouseButtonEventHandler(OnMouseUp), true);
83     
84      StartOffset = EndOffset = this.TextArea.Caret.Offset;
85     
86      AttachEvents();
87    }
88   
89    #region Event Handlers
90    void AttachEvents()
91    {
92      document = this.TextArea.Document;
93      if (document != null) {
94        document.Changing += textArea_Document_Changing;
95      }
96      // LostKeyboardFocus seems to be more reliable than PreviewLostKeyboardFocus - see SD-1729
97      this.TextArea.LostKeyboardFocus += TextAreaLostFocus;
98      this.TextArea.TextView.ScrollOffsetChanged += TextViewScrollOffsetChanged;
99      this.TextArea.DocumentChanged += TextAreaDocumentChanged;
100      if (parentWindow != null) {
101        parentWindow.LocationChanged += parentWindow_LocationChanged;
102      }
103     
104      // close previous completion windows of same type
105      foreach (InputHandler x in this.TextArea.StackedInputHandlers.OfType<InputHandler>()) {
106        if (x.window.GetType() == this.GetType())
107          this.TextArea.PopStackedInputHandler(x);
108      }
109     
110      myInputHandler = new InputHandler(this);
111      this.TextArea.PushStackedInputHandler(myInputHandler);
112    }
113   
114    /// <summary>
115    /// Detaches events from the text area.
116    /// </summary>
117    protected virtual void DetachEvents()
118    {
119      if (document != null) {
120        document.Changing -= textArea_Document_Changing;
121      }
122      this.TextArea.LostKeyboardFocus -= TextAreaLostFocus;
123      this.TextArea.TextView.ScrollOffsetChanged -= TextViewScrollOffsetChanged;
124      this.TextArea.DocumentChanged -= TextAreaDocumentChanged;
125      if (parentWindow != null) {
126        parentWindow.LocationChanged -= parentWindow_LocationChanged;
127      }
128      this.TextArea.PopStackedInputHandler(myInputHandler);
129    }
130   
131    #region InputHandler
132    InputHandler myInputHandler;
133   
134    /// <summary>
135    /// A dummy input handler (that justs invokes the default input handler).
136    /// This is used to ensure the completion window closes when any other input handler
137    /// becomes active.
138    /// </summary>
139    sealed class InputHandler : TextAreaStackedInputHandler
140    {
141      internal readonly CompletionWindowBase window;
142     
143      public InputHandler(CompletionWindowBase window)
144        : base(window.TextArea)
145      {
146        Debug.Assert(window != null);
147        this.window = window;
148      }
149     
150      public override void Detach()
151      {
152        base.Detach();
153        window.Close();
154      }
155     
156      const Key KeyDeadCharProcessed = (Key)0xac; // Key.DeadCharProcessed; // new in .NET 4
157     
158      public override void OnPreviewKeyDown(KeyEventArgs e)
159      {
160        // prevents crash when typing deadchar while CC window is open
161        if (e.Key == KeyDeadCharProcessed)
162          return;
163        e.Handled = RaiseEventPair(window, PreviewKeyDownEvent, KeyDownEvent,
164                                   new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, e.Key));
165      }
166     
167      public override void OnPreviewKeyUp(KeyEventArgs e)
168      {
169        if (e.Key == KeyDeadCharProcessed)
170          return;
171        e.Handled = RaiseEventPair(window, PreviewKeyUpEvent, KeyUpEvent,
172                                   new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, e.Key));
173      }
174    }
175    #endregion
176   
177    void TextViewScrollOffsetChanged(object sender, EventArgs e)
178    {
179      // Workaround for crash #1580 (reproduction steps unknown):
180      // NullReferenceException in System.Windows.Window.CreateSourceWindow()
181      if (!sourceIsInitialized)
182        return;
183     
184      IScrollInfo scrollInfo = this.TextArea.TextView;
185      Rect visibleRect = new Rect(scrollInfo.HorizontalOffset, scrollInfo.VerticalOffset, scrollInfo.ViewportWidth, scrollInfo.ViewportHeight);
186      // close completion window when the user scrolls so far that the anchor position is leaving the visible area
187      if (visibleRect.Contains(visualLocation) || visibleRect.Contains(visualLocationTop))
188        UpdatePosition();
189      else
190        Close();
191    }
192   
193    void TextAreaDocumentChanged(object sender, EventArgs e)
194    {
195      Close();
196    }
197   
198    void TextAreaLostFocus(object sender, RoutedEventArgs e)
199    {
200      Dispatcher.BeginInvoke(new Action(CloseIfFocusLost), DispatcherPriority.Background);
201    }
202   
203    void parentWindow_LocationChanged(object sender, EventArgs e)
204    {
205      UpdatePosition();
206    }
207   
208    /// <inheritdoc/>
209    protected override void OnDeactivated(EventArgs e)
210    {
211      base.OnDeactivated(e);
212      Dispatcher.BeginInvoke(new Action(CloseIfFocusLost), DispatcherPriority.Background);
213    }
214    #endregion
215   
216    /// <summary>
217    /// Raises a tunnel/bubble event pair for a WPF control.
218    /// </summary>
219    /// <param name="target">The WPF control for which the event should be raised.</param>
220    /// <param name="previewEvent">The tunneling event.</param>
221    /// <param name="event">The bubbling event.</param>
222    /// <param name="args">The event args to use.</param>
223    /// <returns>The <see cref="RoutedEventArgs.Handled"/> value of the event args.</returns>
224    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
225    protected static bool RaiseEventPair(UIElement target, RoutedEvent previewEvent, RoutedEvent @event, RoutedEventArgs args)
226    {
227      if (target == null)
228        throw new ArgumentNullException("target");
229      if (previewEvent == null)
230        throw new ArgumentNullException("previewEvent");
231      if (@event == null)
232        throw new ArgumentNullException("event");
233      if (args == null)
234        throw new ArgumentNullException("args");
235      args.RoutedEvent = previewEvent;
236      target.RaiseEvent(args);
237      args.RoutedEvent = @event;
238      target.RaiseEvent(args);
239      return args.Handled;
240    }
241   
242    // Special handler: handledEventsToo
243    void OnMouseUp(object sender, MouseButtonEventArgs e)
244    {
245      ActivateParentWindow();
246    }
247   
248    /// <summary>
249    /// Activates the parent window.
250    /// </summary>
251    protected virtual void ActivateParentWindow()
252    {
253      if (parentWindow != null)
254        parentWindow.Activate();
255    }
256   
257    void CloseIfFocusLost()
258    {
259      if (CloseOnFocusLost) {
260        Debug.WriteLine("CloseIfFocusLost: this.IsActive=" + this.IsActive + " IsTextAreaFocused=" + IsTextAreaFocused);
261        if (!this.IsActive && !IsTextAreaFocused) {
262          Close();
263        }
264      }
265    }
266   
267    /// <summary>
268    /// Gets whether the completion window should automatically close when the text editor looses focus.
269    /// </summary>
270    protected virtual bool CloseOnFocusLost {
271      get { return true; }
272    }
273   
274    bool IsTextAreaFocused {
275      get {
276        if (parentWindow != null && !parentWindow.IsActive)
277          return false;
278        return this.TextArea.IsKeyboardFocused;
279      }
280    }
281   
282    bool sourceIsInitialized;
283   
284    /// <inheritdoc/>
285    protected override void OnSourceInitialized(EventArgs e)
286    {
287      base.OnSourceInitialized(e);
288     
289      if (document != null && this.StartOffset != this.TextArea.Caret.Offset) {
290        SetPosition(new TextViewPosition(document.GetLocation(this.StartOffset)));
291      } else {
292        SetPosition(this.TextArea.Caret.Position);
293      }
294      sourceIsInitialized = true;
295    }
296   
297    /// <inheritdoc/>
298    protected override void OnClosed(EventArgs e)
299    {
300      base.OnClosed(e);
301      DetachEvents();
302    }
303   
304    /// <inheritdoc/>
305    protected override void OnKeyDown(KeyEventArgs e)
306    {
307      base.OnKeyDown(e);
308      if (!e.Handled && e.Key == Key.Escape) {
309        e.Handled = true;
310        Close();
311      }
312    }
313   
314    Point visualLocation, visualLocationTop;
315   
316    /// <summary>
317    /// Positions the completion window at the specified position.
318    /// </summary>
319    protected void SetPosition(TextViewPosition position)
320    {
321      TextView textView = this.TextArea.TextView;
322     
323      visualLocation = textView.GetVisualPosition(position, VisualYPosition.LineBottom);
324      visualLocationTop = textView.GetVisualPosition(position, VisualYPosition.LineTop);
325      UpdatePosition();
326    }
327   
328    /// <summary>
329    /// Updates the position of the CompletionWindow based on the parent TextView position and the screen working area.
330    /// It ensures that the CompletionWindow is completely visible on the screen.
331    /// </summary>
332    protected void UpdatePosition()
333    {
334      TextView textView = this.TextArea.TextView;
335      // PointToScreen returns device dependent units (physical pixels)
336      Point location = textView.PointToScreen(visualLocation - textView.ScrollOffset);
337      Point locationTop = textView.PointToScreen(visualLocationTop - textView.ScrollOffset);
338     
339      // Let's use device dependent units for everything
340      Size completionWindowSize = new Size(this.ActualWidth, this.ActualHeight).TransformToDevice(textView);
341      Rect bounds = new Rect(location, completionWindowSize);
342      Rect workingScreen = System.Windows.Forms.Screen.GetWorkingArea(location.ToSystemDrawing()).ToWpf();
343      if (!workingScreen.Contains(bounds)) {
344        if (bounds.Left < workingScreen.Left) {
345          bounds.X = workingScreen.Left;
346        } else if (bounds.Right > workingScreen.Right) {
347          bounds.X = workingScreen.Right - bounds.Width;
348        }
349        if (bounds.Bottom > workingScreen.Bottom) {
350          bounds.Y = locationTop.Y - bounds.Height;
351          IsUp = true;
352        } else {
353          IsUp = false;
354        }
355        if (bounds.Y < workingScreen.Top) {
356          bounds.Y = workingScreen.Top;
357        }
358      }
359      // Convert the window bounds to device independent units
360      bounds = bounds.TransformFromDevice(textView);
361      this.Left = bounds.X;
362      this.Top = bounds.Y;
363    }
364   
365    /// <inheritdoc/>
366    protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
367    {
368      base.OnRenderSizeChanged(sizeInfo);
369      if (sizeInfo.HeightChanged && IsUp) {
370        this.Top += sizeInfo.PreviousSize.Height - sizeInfo.NewSize.Height;
371      }
372    }
373   
374    /// <summary>
375    /// Gets/sets whether the completion window should expect text insertion at the start offset,
376    /// which not go into the completion region, but before it.
377    /// </summary>
378    /// <remarks>This property allows only a single insertion, it is reset to false
379    /// when that insertion has occurred.</remarks>
380    public bool ExpectInsertionBeforeStart { get; set; }
381   
382    void textArea_Document_Changing(object sender, DocumentChangeEventArgs e)
383    {
384      if (e.Offset + e.RemovalLength == this.StartOffset && e.RemovalLength > 0) {
385        Close(); // removal immediately in front of completion segment: close the window
386        // this is necessary when pressing backspace after dot-completion
387      }
388      if (e.Offset == StartOffset && e.RemovalLength == 0 && ExpectInsertionBeforeStart) {
389        StartOffset = e.GetNewOffset(StartOffset, AnchorMovementType.AfterInsertion);
390        this.ExpectInsertionBeforeStart = false;
391      } else {
392        StartOffset = e.GetNewOffset(StartOffset, AnchorMovementType.BeforeInsertion);
393      }
394      EndOffset = e.GetNewOffset(EndOffset, AnchorMovementType.AfterInsertion);
395    }
396  }
397}
Note: See TracBrowser for help on using the repository browser.