// // // // // $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.ComponentModel.Design.Serialization; using System.Drawing; using System.Reflection; using System.Text; using System.Windows.Forms; using ICSharpCode.FormsDesigner; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Project; namespace ICSharpCode.RubyBinding { public interface IRubyDesignerGenerator : IDesignerGenerator { /// /// Updates the Ruby form or user control's InitializeComponent method with any /// changes to the designed form or user control. /// void MergeRootComponentChanges(IDesignerHost host, IDesignerSerializationManager serializationManager); } /// /// Form's designer generator for the Ruby language. /// public class RubyDesignerGenerator : IRubyDesignerGenerator { FormsDesignerViewContent viewContent; ITextEditorOptions textEditorOptions; public RubyDesignerGenerator(ITextEditorOptions textEditorOptions) { this.textEditorOptions = textEditorOptions; } /// /// Gets the Ruby 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) { } public void NotifyFormRenamed(string newName) { } /// /// Updates the InitializeComponent method's body with the generated code. /// public void MergeRootComponentChanges(IDesignerHost host, IDesignerSerializationManager serializationManager) { ParseInformation parseInfo = ParseFile(); Merge(host, ViewContent.DesignerCodeFileDocument, parseInfo.CompilationUnit, textEditorOptions, serializationManager); } /// /// Merges the generated code into the specified document. /// /// The designer host. /// The document that the generated code will be merged into. /// The current compilation unit for the . public static void Merge(IDesignerHost host, IDocument document, ICompilationUnit compilationUnit, ITextEditorOptions textEditorOptions, IDesignerSerializationManager serializationManager) { // Get the document's initialize components method. IMethod method = GetInitializeComponents(compilationUnit); // Generate the Ruby source code. RubyCodeDomSerializer serializer = new RubyCodeDomSerializer(textEditorOptions.IndentationString); int indent = method.Region.BeginColumn; if (textEditorOptions.ConvertTabsToSpaces) { indent = (indent / textEditorOptions.IndentationSize); if (textEditorOptions.IndentationSize > 1) { indent += 1; } } string rootNamespace = GetProjectRootNamespace(compilationUnit); string methodBody = serializer.GenerateInitializeComponentMethodBody(host, serializationManager, 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, textEditorOptions.IndentationString); IDocumentLine classEndLine = GetClassEndLine(doc); InsertEventHandlerBeforeLine(doc, eventHandler, classEndLine); // Set position so it points to the line // where the event handler was inserted. position = classEndLine.LineNumber; } // Set the filename so it refers to the form being designed. file = viewContent.DesignerCodeFile.FileName; return true; } IDocumentLine GetClassEndLine(IDocument doc) { int line = doc.TotalNumberOfLines; while (line > 0) { IDocumentLine documentLine = doc.GetLine(line); if (documentLine.Text.Trim() == "end") { return documentLine; } line--; } return doc.GetLine(doc.TotalNumberOfLines); } void InsertEventHandlerBeforeLine(IDocument doc, string eventHandler, IDocumentLine classEndLine) { string newContent = "\r\n" + eventHandler + "\r\n"; int offset = classEndLine.Offset; doc.Insert(offset, newContent); } /// /// 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 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); } /// /// 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. /// /// The indent string to use for the event handler. protected string CreateEventHandler(string eventMethodName, string body, string indentation) { if (String.IsNullOrEmpty(body)) { body = String.Empty; } StringBuilder eventHandler = new StringBuilder(); eventHandler.Append(indentation); eventHandler.Append("def "); eventHandler.Append(eventMethodName); eventHandler.Append("(sender, e)"); eventHandler.AppendLine(); eventHandler.Append(indentation); eventHandler.Append(textEditorOptions.IndentationString); eventHandler.Append(body); eventHandler.AppendLine(); eventHandler.Append(indentation); eventHandler.Append("end"); 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; } } }