PageRenderTime 57ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/O2_SharpDevelop/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindowBase.cs

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