// 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;
using System.Collections.Generic;
using System.Reflection;
using System.Windows.Media;
using Nova.CodeDOM;
using Nova.Utilities;
namespace Nova.UI
{
/// <summary>
/// The view model for a <see cref="CodeDOM.TypeRefBase"/>.
/// </summary>
public abstract class TypeRefBaseVM : SymbolicRefVM
{
#region /* FIELDS */
/// <summary>
/// Optional type arguments (for generic types only).
/// </summary>
protected ChildListVM<ExpressionVM> _typeArgumentVMs;
#endregion
#region /* CONSTRUCTORS */
/// <summary>
/// Create a view model instance for the specified <see cref="CodeDOM.TypeRefBase"/>.
/// </summary>
protected TypeRefBaseVM(TypeRefBase typeRefBase, bool createTypeArgumentVMs, Dictionary<CodeObject, CodeObjectVM> dictionary)
: base(typeRefBase, dictionary)
{
if (createTypeArgumentVMs)
_typeArgumentVMs = CreateListVM<Expression, ExpressionVM>(typeRefBase.TypeArguments, dictionary);
}
#endregion
#region /* PROPERTIES */
/// <summary>
/// The underlying <see cref="CodeDOM.TypeRefBase"/> model.
/// </summary>
public TypeRefBase TypeRefBase
{
get { return (TypeRefBase)CodeObject; }
}
/// <summary>
/// The type argument <see cref="Expression"/>s of the reference (if any).
/// </summary>
public virtual ChildListVM<ExpressionVM> TypeArguments
{
get { return _typeArgumentVMs; }
}
/// <summary>
/// True if there are any type arguments.
/// </summary>
public bool HasTypeArguments
{
get { return (TypeArguments != null && TypeArguments.Count > 0); }
}
/// <summary>
/// The number of type arguments.
/// </summary>
public int TypeArgumentCount
{
get { return (TypeArguments != null ? TypeArguments.Count : 0); }
}
#endregion
#region /* METHODS */
#endregion
#region /* RENDERING */
public override void RenderToolTip(CodeRenderer renderer, RenderFlags flags)
{
RenderType(renderer, CodeObject.GetType(), flags, this);
renderer.RenderRightArrow(NORMAL_BRUSH, this);
RenderReferenceDescription(renderer, TypeRefBase.GetTypeWithoutConstant().Reference);
RenderSummaryCommentInToolTip(renderer);
RenderMessagesInToolTip(renderer, flags);
}
/// <summary>
/// Render a <see cref="Type"/>.
/// </summary>
public static void RenderType(CodeRenderer renderer, Type type, RenderFlags flags, CodeObjectVM tag)
{
RenderFlags passFlags = flags & ~RenderFlags.Description;
// Dereference (remove the trailing '&' or '*') if it's a reference type or pointer type
if (type.IsByRef)
type = type.GetElementType();
bool hasBorder = false;
bool isDelegate = false;
bool isDescription = flags.HasFlag(RenderFlags.Description);
if (isDescription)
{
string declType = null;
Brush borderBrush = DarkPalerGreen;
Brush backgroundBrush = PalerGreen;
if (type.IsClass)
{
if (TypeUtil.IsDelegateType(type))
{
declType = DelegateDecl.ParseToken;
borderBrush = DelegateDeclVM.StaticBorderBrush;
backgroundBrush = DelegateDeclVM.StaticBackgroundBrush;
isDelegate = true;
}
else if (!type.IsGenericParameter)
{
declType = ClassDecl.ParseToken;
borderBrush = ClassDeclVM.StaticBorderBrush;
backgroundBrush = ClassDeclVM.StaticBackgroundBrush;
}
}
else if (type.IsInterface)
{
declType = InterfaceDecl.ParseToken;
borderBrush = InterfaceDeclVM.StaticBorderBrush;
backgroundBrush = InterfaceDeclVM.StaticBackgroundBrush;
}
else if (type.IsEnum)
{
declType = EnumDecl.ParseToken;
borderBrush = EnumDeclVM.StaticBorderBrush;
backgroundBrush = EnumDeclVM.StaticBackgroundBrush;
}
else if (type.IsValueType)
{
declType = StructDecl.ParseToken;
borderBrush = StructDeclVM.StaticBorderBrush;
backgroundBrush = StructDeclVM.StaticBackgroundBrush;
}
hasBorder = (flags.HasFlag(RenderFlags.ForceBorder) && !flags.HasFlag(RenderFlags.NoBorder) && !type.IsGenericParameter);
if (hasBorder)
renderer.CreateBorder(borderBrush, backgroundBrush, tag);
MethodInfo delegateInvokeMethodInfo = (isDelegate ? TypeUtil.GetInvokeMethod(type) : null);
if (!flags.HasFlag(RenderFlags.NoPreAnnotations))
{
AttributeVM.RenderAttributes(renderer, type, tag);
if (delegateInvokeMethodInfo != null)
AttributeVM.RenderAttributes(renderer, delegateInvokeMethodInfo.ReturnParameter, tag, AttributeTarget.Return);
}
if (type.IsGenericParameter)
{
renderer.RenderText("(type parameter) ", NORMAL_BRUSH, TextStyle.Proportional);
if (type.DeclaringMethod != null)
RenderGenericMember(renderer, type.DeclaringMethod.Name, type.DeclaringMethod.GetGenericArguments(), passFlags, TYPE_BRUSH, tag);
else if (type.DeclaringType != null)
RenderTypeAsTypeRefVM(renderer, type.DeclaringType, passFlags, tag); // Render as TypeRefVM for nested tooltips
renderer.RenderText(Dot.ParseToken, PUNC_BRUSH, tag);
}
else
{
renderer.RenderText(ModifiersHelpers.AsString(TypeRefBase.GetTypeModifiers(type)), KEYWORD_BRUSH, tag);
renderer.RenderText(declType, KEYWORD_BRUSH, tag);
renderer.RenderText(" ", PUNC_BRUSH, tag);
}
if (delegateInvokeMethodInfo != null)
{
Type returnType = delegateInvokeMethodInfo.ReturnType;
RenderTypeAsTypeRefVM(renderer, returnType, RenderFlags.None, tag); // Render as TypeRefVM for nested tooltips
renderer.RenderText(" ", PUNC_BRUSH, tag);
}
}
bool hasDotPrefix = flags.HasFlag(RenderFlags.HasDotPrefix);
// Go by local generic arguments instead of IsGenericType so that nested types with generic
// enclosing types aren't treated as generic unless they have their own type arguments.
string keyword;
Type[] genericArguments = TypeUtil.GetLocalGenericArguments(type);
if (genericArguments != null && genericArguments.Length > 0)
{
// Render "Nullable<Type>" as "Type?"
if (TypeUtil.IsNullableType(type))
{
Type typeParam = genericArguments[0];
if (!hasDotPrefix && TypeRefBase.TypeNameToKeywordMap.TryGetValue(typeParam.Name, out keyword) && typeParam.Namespace == "System")
renderer.RenderText(keyword + TypeRefBase.ParseTokenNullable, KEYWORD_BRUSH, tag);
else
{
RenderTypeAsTypeRefVM(renderer, typeParam, RenderFlags.None, tag); // Render as TypeRefVM for nested tooltips
renderer.RenderText(TypeRefBase.ParseTokenNullable, TYPE_BRUSH, tag);
}
}
else
{
if (type.IsNested && !hasDotPrefix && flags.HasFlag(RenderFlags.ShowParentTypes))
{
RenderTypeAsTypeRefVM(renderer, type.DeclaringType, passFlags, tag); // Render as TypeRefVM for nested tooltips
renderer.RenderText(Dot.ParseToken, PUNC_BRUSH, tag);
}
RenderGenericMember(renderer, TypeUtil.NonGenericName(type), genericArguments, flags, TYPE_BRUSH, tag);
}
}
else if (type.IsGenericParameter)
{
// If there is an 'open' type parameter, render it as an error (or warning if in a doc comment)
Brush brush = (tag is OpenTypeParameterRefVM ? (flags.HasFlag(RenderFlags.InDocComment) ? WARNING_BRUSH : ERROR_BRUSH) : IDENTIFIER_BRUSH);
renderer.RenderText(type.Name, brush, tag);
}
else if (type.IsArray)
{
RenderTypeAsTypeRefVM(renderer, type.GetElementType(), RenderFlags.None, tag); // Render as TypeRefVM for nested tooltips
renderer.RenderText(TypeRefBase.ArrayRankToString(type.GetArrayRank()), PUNC_BRUSH, tag);
}
else if (!hasDotPrefix && !isDescription && TypeRefBase.TypeNameToKeywordMap.TryGetValue(type.Name, out keyword) && type.Namespace == "System")
{
renderer.RenderText(keyword, KEYWORD_BRUSH, tag);
}
else
{
if (type.IsNested && !hasDotPrefix && flags.HasFlag(RenderFlags.ShowParentTypes))
{
RenderTypeAsTypeRefVM(renderer, type.DeclaringType, passFlags, tag); // Render as TypeRefVM for nested tooltips
renderer.RenderText(Dot.ParseToken, PUNC_BRUSH, tag);
}
renderer.RenderName(type.Name, TYPE_BRUSH, tag, flags);
}
if (isDescription)
{
if (isDelegate)
{
MethodInfo delegateInvokeMethodInfo = TypeUtil.GetInvokeMethod(type);
if (delegateInvokeMethodInfo != null)
MethodRefVM.RenderMethodParameters(renderer, delegateInvokeMethodInfo, RenderFlags.None, tag);
}
else
{
bool hasBaseItem = false;
// Render base type (if any)
if (!type.IsValueType) // Don't show the implicit ValueType base for structs
{
Type baseType = type.IsEnum ? Enum.GetUnderlyingType(type) : type.BaseType;
if (baseType != null && baseType != typeof(object) && (!type.IsEnum || baseType != typeof(int)))
{
renderer.RenderText(" " + BaseListTypeDecl.ParseToken + " ", PUNC_BRUSH, tag);
RenderTypeAsTypeRefVM(renderer, baseType, flags, tag); // Render as TypeRefVM for nested tooltips
hasBaseItem = true;
}
}
// Render implemented interfaces (if any)
if (!type.IsEnum) // Don't show the interfaces of the implicit Enum base for enums
{
Type[] interfaces = type.GetInterfaces();
if (interfaces.Length > 0)
{
// There's no way to tell which interfaces are implemented by the current type as
// opposed to one of it's base types. We could subtract all interfaces from the base
// type, but that wouldn't be perfect since the same interface can be implemented at
// multiple levels. Instead, display them all, since an external user really just
// cares that they're implemented by the type, and doesn't care if it's really by a
// base type.
foreach (Type @interface in interfaces)
{
renderer.RenderText((hasBaseItem ? Expression.ParseTokenSeparator : (" " + BaseListTypeDecl.ParseToken)) + " ", PUNC_BRUSH, tag);
RenderTypeAsTypeRefVM(renderer, @interface, flags, tag); // Render as TypeRefVM for nested tooltips
hasBaseItem = true;
}
}
}
// Render any type constraints for local type arguments
if (type.IsGenericType)
RenderConstraints(renderer, TypeUtil.GetLocalGenericArguments(type.GetGenericTypeDefinition()), passFlags, tag);
}
}
// If we have a border, return to the previous one
if (hasBorder)
renderer.ReturnToBorderParent(tag);
}
/// <summary>
/// Render a Type as a TypeRefVM for nested tooltips.
/// </summary>
public static void RenderTypeAsTypeRefVM(CodeRenderer renderer, Type type, RenderFlags flags, CodeObjectVM parentVM)
{
// Treat generic parameters as TypeParameter definitions
CodeObject codeObject;
if (type.IsGenericParameter)
codeObject = new TypeParameter(type.Name);
else
{
codeObject = TypeRef.Create(type);
// Evaluate type parameters some day, but also need to evaluate the type arguments in the declaration
//TypeRefBase typeRefBase = TypeRef.Create(typeReference).EvaluateTypeArgumentTypes(parentVM.CodeObject);
}
// Use parentVM to render if possible, but it can be null if we're inside a tooltip
CodeObjectVM codeObjectVM;
if (parentVM != null)
codeObjectVM = parentVM.CreateVM(codeObject, true);
else
codeObjectVM = CreateVM(codeObject, null, true);
codeObjectVM.Render(renderer, flags | RenderFlags.NoBorder);
}
/// <summary>
/// Render a generic member.
/// </summary>
public static void RenderGenericMember(CodeRenderer renderer, string name, Type[] args, RenderFlags flags, Brush brush, CodeObjectVM tag)
{
renderer.RenderText(name, brush, tag);
if (!flags.HasFlag(RenderFlags.SuppressTypeArgs))
{
// Render the angle brackets normally in the GUI, even if we're inside a documentation comment
renderer.RenderText(TypeRefBase.ParseTokenArgumentStart, PUNC_BRUSH, tag);
bool first = true;
foreach (Type typeArg in args)
{
if (!first)
renderer.RenderText(Expression.ParseTokenSeparator + " ", PUNC_BRUSH, tag);
RenderTypeAsTypeRefVM(renderer, typeArg, RenderFlags.None, tag); // Render as TypeRefVM for nested tooltips
first = false;
}
renderer.RenderText(TypeRefBase.ParseTokenArgumentEnd, PUNC_BRUSH, tag);
}
}
/// <summary>
/// Render type arguments (if any).
/// </summary>
public static void RenderTypeArguments(CodeRenderer renderer, IEnumerable<ExpressionVM> typeArguments, RenderFlags flags, CodeObjectVM tag)
{
if (typeArguments != null)
{
// Don't pass Description, SuppressBrackets, or ShowParentTypes on to any type arguments, and don't display EOL comments if this is a Description
RenderFlags passFlags = (flags & (RenderFlags.PassMask & ~(RenderFlags.Description | RenderFlags.SuppressBrackets | RenderFlags.ShowParentTypes)))
| (flags.HasFlag(RenderFlags.Description) ? RenderFlags.NoEOLComments : 0);
// Render the angle brackets normally in the GUI, even if we're inside a documentation comment
renderer.RenderText(TypeRefBase.ParseTokenArgumentStart, PUNC_BRUSH, tag);
renderer.RenderList(typeArguments, passFlags, tag);
renderer.RenderText(TypeRefBase.ParseTokenArgumentEnd, PUNC_BRUSH, tag);
}
}
/// <summary>
/// Render array rank brackets (if any).
/// </summary>
public void RenderArrayRanks(CodeRenderer renderer, RenderFlags flags)
{
if (!flags.HasFlag(RenderFlags.SuppressBrackets))
{
// Use the array ranks if we have any, otherwise use any on the referenced type
if (TypeRefBase.ArrayRanks != null)
{
foreach (int rank in TypeRefBase.ArrayRanks)
{
// Render with a null object tag for now (no special tooltip handling)
renderer.RenderText(TypeRefBase.ArrayRankToString(rank), PUNC_BRUSH, null);
}
}
else if (TypeRefBase.Reference is Type)
RenderArrayRank(renderer, (Type)TypeRefBase.Reference);
}
}
private static void RenderArrayRank(CodeRenderer renderer, Type type)
{
if (type.IsArray)
{
RenderArrayRank(renderer, type.GetElementType());
// Render with a null object tag for now (no special tooltip handling)
renderer.RenderText(TypeRefBase.ArrayRankToString(type.GetArrayRank()), PUNC_BRUSH, null);
}
}
/// <summary>
/// Render type constraints.
/// </summary>
public static void RenderConstraints(CodeRenderer renderer, Type[] typeParameters, RenderFlags flags, CodeObjectVM tag)
{
foreach (Type typeParameter in typeParameters)
{
bool isValueType = false;
GenericParameterAttributes attributes = typeParameter.GenericParameterAttributes;
bool hasBorder = false;
if (attributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint))
{
RenderConstraintPrefix(renderer, ref hasBorder, typeParameter, flags, tag);
renderer.RenderText(ClassConstraint.ParseToken, KEYWORD_BRUSH, tag);
}
if (attributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint))
{
RenderConstraintPrefix(renderer, ref hasBorder, typeParameter, flags, tag);
renderer.RenderText(StructConstraint.ParseToken, KEYWORD_BRUSH, tag);
isValueType = true;
}
foreach (Type constraint in typeParameter.GetGenericParameterConstraints())
{
if (constraint != typeof(ValueType)) // Already handled above
{
RenderConstraintPrefix(renderer, ref hasBorder, typeParameter, flags, tag);
RenderTypeAsTypeRefVM(renderer, constraint, RenderFlags.None, tag); // Render as TypeRefVM for nested tooltips
}
}
if (attributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint) && !isValueType)
{
RenderConstraintPrefix(renderer, ref hasBorder, typeParameter, flags, tag);
renderer.RenderText(NewConstraint.ParseToken, KEYWORD_BRUSH, tag);
if (!MethodDeclBaseVM.HideMethodParens)
renderer.RenderText(Expression.ParseTokenStartGroup + Expression.ParseTokenEndGroup, PUNC_BRUSH, tag);
}
// If we have a border, return to the previous one
if (hasBorder)
renderer.ReturnToBorderParent(tag);
}
}
private static void RenderConstraintPrefix(CodeRenderer renderer, ref bool hasBorder, Type typeParameter, RenderFlags flags, CodeObjectVM tag)
{
if (!hasBorder)
{
renderer.RenderText(" ", PUNC_BRUSH, tag);
hasBorder = !flags.HasFlag(RenderFlags.NoBorder);
if (hasBorder)
renderer.CreateBorder(StaticBorderBrush, StaticBackgroundBrush, tag);
renderer.RenderText(ConstraintClause.ParseToken + " ", KEYWORD_BRUSH, tag);
RenderType(renderer, typeParameter, RenderFlags.None, tag);
renderer.RenderText(" " + ConstraintClause.ParseTokenSeparator + " ", PUNC_BRUSH, tag);
}
else
renderer.RenderText(Expression.ParseTokenSeparator + " ", PUNC_BRUSH, tag);
}
public override void UnRender()
{
ChildListHelpers.UnRender(_typeArgumentVMs);
base.UnRender();
}
#endregion
}
}