~halega/+junk/sharpdevelop

« back to all changes in this revision

Viewing changes to src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindowBase.cs

  • Committer: sk
  • Date: 2011-09-10 05:17:57 UTC
  • Revision ID: halega@halega.com-20110910051757-qfouz1llya9m6boy
4.1.0.7915 Release Candidate 1

Show diffs side-by-side

added added

removed removed

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