// ParameterInformationWindowManager.cs // // Author: // Lluis Sanchez Gual // // Copyright (c) 2007 Novell, Inc (http://www.novell.com) // // 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.Collections; using System.Collections.Generic; using Gtk; using Gdk; using MonoDevelop.Ide.Gui.Content; using MonoDevelop.Ide.Editor.Extension; namespace MonoDevelop.Ide.CodeCompletion { public class ParameterInformationWindowManager { static List methods = new List (); static ParameterInformationWindow window; public static bool IsWindowVisible { get { return methods.Count > 0; } } static ParameterInformationWindowManager () { if (IdeApp.Workbench != null) IdeApp.Workbench.RootWindow.Destroyed += (sender, e) => DestroyWindow (); } static void DestroyWindow () { if (window != null) { window.Destroy (); window = null; } } // Called when a key is pressed in the editor. // Returns false if the key press has to continue normal processing. internal static bool ProcessKeyEvent (CompletionTextEditorExtension ext, ICompletionWidget widget, KeyDescriptor descriptor) { if (methods.Count == 0) return false; MethodData cmd = methods [methods.Count - 1]; if (descriptor.SpecialKey == SpecialKey.Down) { if (cmd.MethodProvider.Count <= 1) return false; if (cmd.CurrentOverload < cmd.MethodProvider.Count - 1) cmd.CurrentOverload ++; else cmd.CurrentOverload = 0; window.ChangeOverload (); UpdateWindow (ext, widget); return true; } else if (descriptor.SpecialKey == SpecialKey.Up) { if (cmd.MethodProvider.Count <= 1) return false; if (cmd.CurrentOverload > 0) cmd.CurrentOverload --; else cmd.CurrentOverload = cmd.MethodProvider.Count - 1; window.ChangeOverload (); UpdateWindow (ext, widget); return true; } else if (descriptor.SpecialKey == SpecialKey.Escape) { HideWindow (ext, widget); return true; } return false; } internal static void PostProcessKeyEvent (CompletionTextEditorExtension ext, ICompletionWidget widget, KeyDescriptor descriptor) { } internal static void UpdateCursorPosition (CompletionTextEditorExtension ext, ICompletionWidget widget) { // Called after the key has been processed by the editor if (methods.Count == 0) return; for (int n=0; n< methods.Count; n++) { // If the cursor is outside of any of the methods parameter list, discard the // information window for that method. MethodData md = methods [n]; int pos = ext.GetCurrentParameterIndex (md.MethodProvider.StartOffset); if (pos == -1) { methods.RemoveAt (n); n--; } } // If the user enters more parameters than the current overload has, // look for another overload with more parameters. UpdateOverload (ext, widget); // Refresh. UpdateWindow (ext, widget); } internal static void RepositionWindow (CompletionTextEditorExtension ext, ICompletionWidget widget) { UpdateWindow (ext, widget); } internal static void ShowWindow (CompletionTextEditorExtension ext, ICompletionWidget widget, CodeCompletionContext ctx, ParameterHintingResult provider) { if (provider.Count == 0) return; // There can be several method parameter lists open at the same time, so // they have to be queued. The last one of queue is the one being shown // in the information window. MethodData md = new MethodData (); md.MethodProvider = provider; md.CurrentOverload = 0; md.CompletionContext = ctx; methods.Add (md); UpdateOverload (ext, widget); UpdateWindow (ext, widget); } internal static void HideWindow (CompletionTextEditorExtension ext, ICompletionWidget widget) { methods.Clear (); if (window != null) window.ChangeOverload (); UpdateWindow (ext, widget); } public static int GetCurrentOverload () { if (methods.Count == 0) return -1; return methods [methods.Count - 1].CurrentOverload; } public static ParameterHintingResult GetCurrentProvider () { if (methods.Count == 0) return null; return methods [methods.Count - 1].MethodProvider; } static void UpdateOverload (CompletionTextEditorExtension ext, ICompletionWidget widget) { if (methods.Count == 0 || window == null) return; // If the user enters more parameters than the current overload has, // look for another overload with more parameters. MethodData md = methods [methods.Count - 1]; int bestOverload = ext.GuessBestMethodOverload (md.MethodProvider, md.CurrentOverload); if (bestOverload != -1) { md.CurrentOverload = bestOverload; window.ChangeOverload (); UpdateWindow (ext, widget); } } public static int X { get; private set; } public static int Y { get; private set; } public static bool wasAbove = false; static bool wasVisi; static int lastW = -1, lastH = -1; internal static void UpdateWindow (CompletionTextEditorExtension textEditorExtension, ICompletionWidget completionWidget) { // Updates the parameter information window from the information // of the current method overload if (methods.Count > 0) { if (window == null) { window = new ParameterInformationWindow (); window.Ext = textEditorExtension; window.Widget = completionWidget; window.SizeAllocated += delegate(object o, SizeAllocatedArgs args) { if (args.Allocation.Width == lastW && args.Allocation.Height == lastH && wasVisi == CompletionWindowManager.IsVisible) return; PositionParameterInfoWindow (args.Allocation); }; window.Hidden += delegate { lastW = -1; lastH = -1; }; } else { window.Ext = textEditorExtension; window.Widget = completionWidget; } wasAbove = false; var lastMethod = methods [methods.Count - 1]; int curParam = window.Ext != null ? window.Ext.GetCurrentParameterIndex (lastMethod.MethodProvider.StartOffset) : 0; var geometry2 = DesktopService.GetUsableMonitorGeometry (window.Screen, window.Screen.GetMonitorAtPoint (X, Y)); window.ShowParameterInfo (lastMethod.MethodProvider, lastMethod.CurrentOverload, curParam - 1, geometry2.Width); PositionParameterInfoWindow (window.Allocation); window.Show (); } if (methods.Count == 0) { if (window != null) { window.HideParameterInfo (); // DestroyWindow (); wasAbove = false; wasVisi = false; lastW = -1; lastH = -1; } return; } } static void PositionParameterInfoWindow (Rectangle allocation) { lastW = allocation.Width; lastH = allocation.Height; wasVisi = CompletionWindowManager.IsVisible; var ctx = window.Widget.CurrentCodeCompletionContext; var md = methods [methods.Count - 1]; int cparam = window.Ext != null ? window.Ext.GetCurrentParameterIndex (md.MethodProvider.StartOffset) : 0; X = md.CompletionContext.TriggerXCoord; if (CompletionWindowManager.IsVisible) { // place above Y = ctx.TriggerYCoord - ctx.TriggerTextHeight - allocation.Height - 10; } else { // place below Y = ctx.TriggerYCoord; } var geometry = DesktopService.GetUsableMonitorGeometry (window.Screen, window.Screen.GetMonitorAtPoint (X, Y)); window.ShowParameterInfo (md.MethodProvider, md.CurrentOverload, cparam - 1, geometry.Width); if (X + allocation.Width > geometry.Right) X = geometry.Right - allocation.Width; if (Y < geometry.Top) Y = ctx.TriggerYCoord; if (wasAbove || Y + allocation.Height > geometry.Bottom) { Y = Y - ctx.TriggerTextHeight - allocation.Height - 4; wasAbove = true; } if (CompletionWindowManager.IsVisible) { var completionWindow = new Rectangle (CompletionWindowManager.X, CompletionWindowManager.Y, CompletionWindowManager.Wnd.Allocation.Width, CompletionWindowManager.Wnd.Allocation.Height); if (completionWindow.IntersectsWith (new Rectangle (X, Y, allocation.Width, allocation.Height))) { X = completionWindow.X; Y = completionWindow.Y - allocation.Height - 6; if (Y < 0) Y = completionWindow.Bottom + 6; } } window.Move (X, Y); } } class MethodData { public ParameterHintingResult MethodProvider; public CodeCompletionContext CompletionContext; int currentOverload; public int CurrentOverload { get { return currentOverload; } set { currentOverload = value; } } } }