// // // // // $Revision$ // using System; using System.CodeDom; using System.CodeDom.Compiler; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.Design; using System.Text; using System.Windows.Forms; using ICSharpCode.FormsDesigner; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Project; namespace ICSharpCode.PythonBinding { public interface IPythonDesignerGenerator : IDesignerGenerator { /// /// Updates the python form or user control's InitializeComponent method with any /// changes to the designed form or user control. /// void MergeRootComponentChanges(IComponent component, IResourceService resourceService); } /// /// Form's designer generator for the Python language. /// public class PythonDesignerGenerator : IPythonDesignerGenerator { FormsDesignerViewContent viewContent; ICSharpCode.TextEditor.Document.ITextEditorProperties textEditorProperties; public PythonDesignerGenerator(ICSharpCode.TextEditor.Document.ITextEditorProperties textEditorProperties) { this.textEditorProperties = textEditorProperties; } /// /// Gets the Python code dom provider. /// public CodeDomProvider CodeDomProvider { get { return null; } } public void Attach(FormsDesignerViewContent viewContent) { this.viewContent = viewContent; } public void Detach() { this.viewContent = null; } public IEnumerable GetSourceFiles(out OpenedFile designerCodeFile) { designerCodeFile = this.ViewContent.PrimaryFile; return new [] {designerCodeFile}; } public void MergeFormChanges(CodeCompileUnit unit) { } /// /// Updates the InitializeComponent method's body with the generated code. /// public void MergeRootComponentChanges(IComponent component, IResourceService resourceService) { ParseInformation parseInfo = ParseFile(); Merge(component, ViewContent.DesignerCodeFileDocument, parseInfo.CompilationUnit, textEditorProperties, resourceService); } /// /// Merges the generated code into the specified document. /// /// The root component in the designer host. /// The document that the generated code will be merged into. /// The current compilation unit for the . public static void Merge(IComponent component, IDocument document, ICompilationUnit compilationUnit, ICSharpCode.TextEditor.Document.ITextEditorProperties textEditorProperties, IResourceService resourceService) { // Get the document's initialize components method. IMethod method = GetInitializeComponents(compilationUnit); // Generate the python source code. PythonControl pythonControl = new PythonControl(NRefactoryToPythonConverter.GetIndentString(textEditorProperties), resourceService); int indent = method.Region.BeginColumn; if (textEditorProperties.ConvertTabsToSpaces) { indent = (indent / textEditorProperties.IndentationSize); if (textEditorProperties.IndentationSize > 1) { indent += 1; } } string rootNamespace = GetProjectRootNamespace(compilationUnit); string methodBody = pythonControl.GenerateInitializeComponentMethodBody(component as Control, rootNamespace, indent); // Merge the code. DomRegion methodRegion = GetBodyRegionInDocument(method); int startOffset = GetStartOffset(document, methodRegion); int endOffset = GetEndOffset(document, methodRegion); document.Replace(startOffset, endOffset - startOffset, methodBody); } /// /// Inserts an event handler. /// public bool InsertComponentEvent(IComponent component, EventDescriptor edesc, string eventMethodName, string body, out string file, out int position) { position = GetExistingEventHandler(eventMethodName); if (position == -1) { // Ensure the text editor has the latest version // of the source code before we insert any new code. viewContent.MergeFormChanges(); // Insert the event handler at the end of the class with an extra // new line before it. IDocument doc = viewContent.DesignerCodeFileDocument; string eventHandler = CreateEventHandler(eventMethodName, body, "\t"); int line = doc.TotalNumberOfLines; IDocumentLine lastLineSegment = doc.GetLine(line); int offset = lastLineSegment.Offset + lastLineSegment.Length; string newContent = "\r\n" + eventHandler; if (lastLineSegment.Length > 0) { // Add an extra new line between the last line and the event handler. newContent = "\r\n" + newContent; } doc.Insert(offset, newContent); // Set position so it points to the line // where the event handler was inserted. position = line + 1; } // Set the filename so it refers to the form being designed. file = viewContent.DesignerCodeFile.FileName; return true; } /// /// Returns a list of method names that could be used as an /// event handler with the specified event. /// public ICollection GetCompatibleMethods(EventDescriptor edesc) { // Get the form or user control class. ParseInformation parseInfo = ParseFile(); // Look at the form's methods and see which are compatible. ArrayList methods = new ArrayList(); IClass c = GetClass(parseInfo.CompilationUnit); foreach (IMethod method in c.Methods) { if (method.Parameters.Count == 2) { methods.Add(method.Name); } } return methods; } /// /// Gets the non-generated InitializeComponents from parse info. /// public static IMethod GetInitializeComponents(ParseInformation parseInfo) { return GetInitializeComponents(parseInfo.CompilationUnit); } /// /// Gets the non-generated InitializeComponents from the compilation unit. /// public static IMethod GetInitializeComponents(ICompilationUnit unit) { foreach (IClass c in unit.Classes) { if (FormsDesignerSecondaryDisplayBinding.BaseClassIsFormOrControl(c)) { IMethod method = FormsDesignerSecondaryDisplayBinding.GetInitializeComponents(c); if (method != null) { return method; } } } return null; } /// /// Converts from the DOM region to a document region. /// public static DomRegion GetBodyRegionInDocument(IMethod method) { DomRegion bodyRegion = method.BodyRegion; return new DomRegion(bodyRegion.BeginLine + 1, 1, bodyRegion.EndLine + 1, 1); } /// /// Gets the view content attached to this generator. /// public FormsDesignerViewContent ViewContent { get { return viewContent; } } /// /// The default implementation calls the ParserService.ParseFile. This /// method is overridable so the class can be easily tested without /// the ParserService being required. /// protected virtual ParseInformation ParseFile(string fileName, string textContent) { return ParserService.ParseFile(fileName, new StringTextBuffer(textContent)); } /// /// Returns the generated event handler. /// protected string CreateEventHandler(string eventMethodName, string body, string indentation) { if (String.IsNullOrEmpty(body)) { body = "pass"; } StringBuilder eventHandler = new StringBuilder(); eventHandler.Append(indentation); eventHandler.Append("def "); eventHandler.Append(eventMethodName); eventHandler.Append("(self, sender, e):"); eventHandler.AppendLine(); eventHandler.Append(indentation); eventHandler.Append("\t"); eventHandler.Append(body); return eventHandler.ToString(); } /// /// Gets the form or user control class from the compilation unit. /// IClass GetClass(ICompilationUnit unit) { return unit.Classes[0]; } /// /// Gets the start offset of the region. /// static int GetStartOffset(IDocument document, DomRegion region) { return document.PositionToOffset(region.BeginLine, region.BeginColumn); } /// /// Gets the end offset of the region. /// static int GetEndOffset(IDocument document, DomRegion region) { if (region.EndLine > document.TotalNumberOfLines) { // At end of document. return document.TextLength; } return document.PositionToOffset(region.EndLine, region.EndColumn); } /// /// Checks if the event handler already exists. /// /// The line position of the first line of the existing event handler. int GetExistingEventHandler(string methodName) { ParseInformation parseInfo = ParseFile(); IClass c = GetClass(parseInfo.CompilationUnit); foreach (IMethod method in c.Methods) { if ((method.Name == methodName) && (method.Parameters.Count == 2)) { return method.Region.BeginLine; } } return -1; } /// /// Parses the form or user control being designed. /// ParseInformation ParseFile() { return ParseFile(this.ViewContent.DesignerCodeFile.FileName, this.ViewContent.DesignerCodeFileContent); } static string GetProjectRootNamespace(ICompilationUnit compilationUnit) { IProject project = compilationUnit.ProjectContent.Project as IProject; if (project != null) { return project.RootNamespace; } return String.Empty; } } }