// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using Nova.CodeDOM;
namespace Nova.UI
{
/// <summary>
/// The view model for a <see cref="CodeDOM.DocComment"/>.
/// </summary>
public class DocCommentVM : CommentBaseVM
{
#region /* STATICS */
internal static void AddViewModelMapping()
{
CreateViewModel.Add(typeof(DocComment),
delegate(CodeObject codeObject, bool isDescription, Dictionary<CodeObject, CodeObjectVM> dictionary) { return new DocCommentVM((DocComment)codeObject, dictionary); });
}
#endregion
#region /* FIELDS */
/// <summary>
/// The content can be a simple string or a <see cref="ChildListVM{DocCommentVM}"/>, or a sub-tree of <see cref="CodeObjectVM"/>s.
/// </summary>
protected object _contentVM;
#endregion
#region /* CONSTRUCTORS */
/// <summary>
/// Create a view model instance for the specified <see cref="CodeDOM.DocComment"/>.
/// </summary>
public DocCommentVM(DocComment docComment, Dictionary<CodeObject, CodeObjectVM> dictionary)
: base(docComment, dictionary)
{
object content = docComment.Content;
if (content is string)
_contentVM = content;
else if (content is ChildList<DocComment>)
_contentVM = CreateListVM<DocComment, DocCommentVM>((ChildList<DocComment>)content, dictionary);
else if (content is CodeObject)
{
// Create BlockVMs specially so that the parent VM can be specified
_contentVM = (content is Block ? new BlockVM((Block)content, this, dictionary) : CreateVM((CodeObject)content, false, dictionary));
}
}
#endregion
#region /* PROPERTIES */
/// <summary>
/// The underlying <see cref="CodeDOM.DocComment"/> model.
/// </summary>
public DocComment DocComment
{
get { return (DocComment)CodeObject; }
}
#endregion
#region /* METHODS */
/// <summary>
/// Returns the <see cref="DocSummary"/> documentation comment, or null if none exists.
/// </summary>
public override DocSummaryVM GetDocSummary()
{
if (_contentVM is ChildListVM<DocCommentVM>)
return Enumerable.FirstOrDefault(Enumerable.OfType<DocSummaryVM>(((ChildListVM<DocCommentVM>)_contentVM)));
return null;
}
/// <summary>
/// Get the root documentation comment object.
/// </summary>
public DocCommentVM GetRootDocComment()
{
DocCommentVM parent = this;
while (parent.ParentVM is DocCommentVM)
parent = (DocCommentVM)parent.ParentVM;
return parent;
}
#endregion
#region /* RENDERING */
protected internal string GetContentForDisplay(RenderFlags flags)
{
// If NoTagNewLines is set, trim any leading AND/OR trailing whitespace from the content (any newlines
// determine if the content starts and/or ends on the same line as the start/end tag, and NoTagNewLines
// means we don't want to render them because we're not rendering the tags).
string content = (string)_contentVM;
if (flags.HasFlag(RenderFlags.NoTagNewLines))
content = content.Trim();
return content;
}
protected internal string GetContentForDisplay(DocTextVM docTextVM, bool isFirst, bool isLast, RenderFlags flags)
{
// If NoTagNewLines is set, trim any leading whitespace from the first child if it's a DocText, and trim
// any trailing whitespace from the last child if it's a DocText.
string text = docTextVM.DocText.Text;
if (flags.HasFlag(RenderFlags.NoTagNewLines))
{
if (isFirst)
text = text.TrimStart();
else if (isLast)
text = text.TrimEnd();
}
return text;
}
/// <summary>
/// Determines if documentation comment text is rendered with a proportional font instead of a fixed font.
/// </summary>
public static bool UseProportionalFont = true;
/// <summary>
/// Use an alternate (non-XML) rendering for documentation comments if true.
/// </summary>
public static bool UseAlternativeFormat;
protected virtual void RenderStart(CodeRenderer renderer, RenderFlags flags)
{
if (!flags.HasFlag(RenderFlags.Description) || DocComment.MissingEndTag)
{
string tagName = DocComment.TagName;
if (tagName != null)
{
if (!UseAlternativeFormat)
renderer.RenderText("<" + tagName + (_contentVM == null && !DocComment.MissingEndTag ? "/>" : ">"), DocComment.MissingEndTag ? WARNING_BRUSH : COMMENT_TAG_BRUSH, this);
else
{
string prefix = char.ToUpper(tagName[0]) + tagName.Substring(1) + (_contentVM != null ? " : " : "");
renderer.RenderText(prefix, COMMENT_TAG_BRUSH, (UseProportionalFont ? TextStyle.Proportional : TextStyle.Normal), this);
}
}
}
}
protected virtual void RenderContent(CodeRenderer renderer, TextStyle textStyle, RenderFlags flags)
{
if (_contentVM is string)
DocTextVM.RenderText(renderer, GetContentForDisplay(flags | (UseAlternativeFormat ? RenderFlags.NoTagNewLines : 0)), textStyle | TextStyle.NoCache, flags, this);
else if (_contentVM is ChildListVM<DocCommentVM>)
{
bool lastWasTerm = false;
ChildListVM<DocCommentVM> docComments = (ChildListVM<DocCommentVM>)_contentVM;
for (int i = 0; i < docComments.Count; ++i)
{
DocCommentVM docComment = docComments[i];
if (docComment is DocTextVM)
{
// Skip any DocText formatting after DocTerm tags when in alt render mode
if (!(UseAlternativeFormat && lastWasTerm))
{
string text = GetContentForDisplay((DocTextVM)docComment, i == 0, i == docComments.Count - 1, flags | (UseAlternativeFormat ? RenderFlags.NoTagNewLines : 0));
DocTextVM.RenderText(renderer, text, textStyle | TextStyle.NoCache, flags, this);
}
}
else
{
docComment.Render(renderer, textStyle, flags);
lastWasTerm = (docComment is DocListTermVM);
}
}
}
else if (_contentVM is CodeObjectVM)
((CodeObjectVM)_contentVM).Render(renderer, flags);
}
protected virtual void RenderEnd(CodeRenderer renderer, RenderFlags flags)
{
if (!DocComment.MissingEndTag && (_contentVM != null || DocComment.MissingStartTag) && !flags.HasFlag(RenderFlags.Description) && !UseAlternativeFormat)
{
string tagName = DocComment.TagName;
if (tagName != null)
renderer.RenderText("</" + tagName + ">", DocComment.MissingStartTag ? WARNING_BRUSH : COMMENT_TAG_BRUSH, this);
}
}
public override void Render(CodeRenderer renderer, RenderFlags flags)
{
Render(renderer, TextStyle.Normal, flags);
}
public virtual void Render(CodeRenderer renderer, TextStyle textStyle, RenderFlags flags)
{
if (HideAll) return;
bool isPrefix = flags.HasFlag(RenderFlags.IsPrefix);
int newLines = DocComment.NewLines;
bool isTopLevelDocComment = !flags.HasFlag(RenderFlags.InDocComment);
if (isTopLevelDocComment)
{
if (!isPrefix && newLines > 0 && !flags.HasFlag(RenderFlags.SuppressNewLine))
renderer.NewLines(newLines, ParentVM);
if (!flags.HasFlag(RenderFlags.NoBorder))
CreateBorder(renderer, flags);
RenderDocNewLines(renderer, 0, flags, this);
}
else if (!isPrefix && newLines > 0)
RenderDocNewLines(renderer, newLines, flags, this);
RenderFlags passFlags = (flags & RenderFlags.PassMask) | RenderFlags.InDocComment;
RenderStart(renderer, passFlags);
RenderContent(renderer, textStyle, passFlags);
RenderEnd(renderer, passFlags);
if (isTopLevelDocComment)
{
if (!flags.HasFlag(RenderFlags.NoBorder))
renderer.ReturnToBorderParent(this);
if (isPrefix)
{
// If this object is rendered as a child prefix object of another, then any whitespace is
// rendered here *after* the object instead of before it.
// A documentation comment must always be followed by a newline if it's a prefix.
renderer.NewLines(newLines < 1 ? 1 : newLines, ParentVM);
}
}
}
public static void RenderDocNewLines(CodeRenderer renderer, int count, RenderFlags flags, DocCommentVM tag)
{
// Render one or more newlines (0 means a prefix without a newline)
do
{
if (count > 0)
renderer.NewLine();
if (!HideDelimiters && !flags.HasFlag(RenderFlags.Description))
renderer.RenderText(DocComment.ParseToken + " ", COMMENT_TAG_BRUSH, tag.GetRootDocComment());
--count;
}
while (count > 0);
}
public override void RenderVisible(CodeRenderer renderer, RenderFlags flags)
{
flags |= RenderFlags.InDocComment;
if (_contentVM is ChildListVM<DocCommentVM>)
renderer.RenderVisibleList((ChildListVM<DocCommentVM>)_contentVM, flags);
else if (_contentVM is CodeObjectVM)
((CodeObjectVM)_contentVM).RenderVisible(renderer, flags);
}
public override void UnRender()
{
if (_contentVM is ChildListVM<DocCommentVM>)
ChildListHelpers.UnRender((ChildListVM<DocCommentVM>)_contentVM);
else if (_contentVM is CodeObjectVM)
((CodeObjectVM)_contentVM).UnRender();
base.UnRender();
}
#endregion
#region /* COMMANDS */
public static readonly RoutedCommand UseProportionalFontCommand = new RoutedCommand("Use Proportional Font for Doc Comments", typeof(DocCommentVM));
public static readonly RoutedCommand AlternativeFormatCommand = new RoutedCommand("Use Alternative Format for Doc Comments", typeof(DocCommentVM));
public static new void BindCommands(Window window)
{
WPFUtil.AddCommandBinding(window, UseProportionalFontCommand, proportionalDocComment_Executed);
WPFUtil.AddCommandBinding(window, AlternativeFormatCommand, altDocComment_Executed);
}
private static void proportionalDocComment_Executed(object sender, ExecutedRoutedEventArgs e)
{
UseProportionalFont = !UseProportionalFont;
CodeRenderer.ReRenderAll();
}
private static void altDocComment_Executed(object sender, ExecutedRoutedEventArgs e)
{
UseAlternativeFormat = !UseAlternativeFormat;
CodeRenderer.ReRenderAll();
}
#endregion
}
}