// 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;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Nova.CodeDOM;
namespace Nova.UI
{
/// <summary>
/// The view model for a <see cref="CodeDOM.Block"/>.
/// </summary>
public class BlockVM : CodeObjectVM, ICollection<CodeObjectVM>, ICollection
{
#region /* STATICS */
internal static void AddViewModelMapping()
{
//// Force manual creation of BlockVMs, so that the parent VM can be properly set
//CreateViewModel.Add(typeof(Block),
// delegate(CodeObject codeObject, bool isDescription, Dictionary<CodeObject, CodeObjectVM> dictionary) { return new BlockVM((Block)codeObject, null, dictionary); });
}
#endregion
#region /* FIELDS */
/// <summary>
/// Child <see cref="CodeObjectVM"/>s - the Parent of this collection will be the BlockVM's Parent, so
/// that the Parent of all child view models will be the BlockVM's Parent, not the BlockVM.
/// </summary>
protected ChildListVM<CodeObjectVM> _codeObjectVMs;
/// <summary>
/// Canvas used for virtualized display of child items.
/// </summary>
protected Canvas _canvas;
#endregion
#region /* CONSTRUCTORS */
/// <summary>
/// Create a view model instance for the specified <see cref="CodeDOM.Block"/>.
/// </summary>
public BlockVM(Block block, CodeObjectVM parentVM, Dictionary<CodeObject, CodeObjectVM> dictionary)
: base(block, dictionary)
{
ParentVM = parentVM;
_codeObjectVMs = parentVM.CreateListVM<CodeObject, CodeObjectVM>(block, dictionary);
}
#endregion
#region /* PROPERTIES */
/// <summary>
/// The underlying <see cref="CodeDOM.Block"/> model.
/// </summary>
public Block Block
{
get { return (Block)CodeObject; }
}
/// <summary>
/// The number of code objects in the <see cref="BlockVM"/>.
/// </summary>
public int Count
{
get { return (_codeObjectVMs != null ? _codeObjectVMs.Count : 0); }
}
/// <summary>
/// Always <c>false</c>.
/// </summary>
public bool IsReadOnly
{
get { return false; }
}
/// <summary>
/// True if access to the <see cref="ICollection"/> is synchronized.
/// </summary>
public virtual bool IsSynchronized
{
get { return false; }
}
/// <summary>
/// Gets an object that can be used to synchronize access to the <see cref="ICollection"/>.
/// </summary>
public virtual object SyncRoot
{
get { return this; }
}
/// <summary>
/// Get the child <see cref="CodeObjectVM"/> at the specified index.
/// </summary>
public CodeObjectVM this[int index]
{
get { return _codeObjectVMs[index]; }
}
/// <summary>
/// Get the last <see cref="CodeObjectVM"/> in the <see cref="BlockVM"/>.
/// </summary>
public CodeObjectVM Last
{
get { return _codeObjectVMs.Last; }
}
/// <summary>
/// True if the <see cref="BlockVM"/> is rendered at the top level (has no indent).
/// </summary>
public bool IsTopLevel
{
get
{
CodeObject parent = ParentVM.CodeObject;
return (!(parent is IBlock) || ((IBlock)parent).IsTopLevel);
}
}
#endregion
#region /* METHODS */
/// <summary>
/// Add a code view model to the <see cref="BlockVM"/>.
/// </summary>
/// <param name="obj">The object to be added.</param>
public void Add(CodeObjectVM obj)
{
_codeObjectVMs.Add(obj);
}
/// <summary>
/// Clear all members from the <see cref="BlockVM"/>.
/// </summary>
public void Clear()
{
RemoveAll();
}
/// <summary>
/// Check if the block contains the specified code view model.
/// </summary>
/// <param name="codeObject">The object being searched for.</param>
/// <returns>True if the block contains the object, otherwise false.</returns>
public bool Contains(CodeObjectVM codeObject)
{
return Enumerable.Any(this, delegate(CodeObjectVM child) { return child == codeObject; });
}
/// <summary>
/// Copy the code view models in the block to the specified array, starting at the specified offset.
/// </summary>
/// <param name="codeObjects">The array to copy into.</param>
/// <param name="index">The starting index in the array.</param>
public void CopyTo(CodeObjectVM[] codeObjects, int index)
{
CopyTo((Array)codeObjects, index);
}
/// <summary>
/// Copy the code view models in the block to the specified array, starting at the specified offset.
/// </summary>
/// <param name="array">The array to copy into.</param>
/// <param name="index">The starting index in the array.</param>
public void CopyTo(Array array, int index)
{
if (array == null)
throw new ArgumentNullException("array", "Null array reference");
if (index < 0)
throw new ArgumentOutOfRangeException("index", "Index is out of range");
if (array.Rank > 1)
throw new ArgumentException("Array is multi-dimensional", "array");
foreach (CodeObjectVM obj in this)
array.SetValue(obj, index++);
}
/// <summary>
/// Insert a code view model into the block at the specified index.
/// </summary>
/// <param name="index">The index at which to insert.</param>
/// <param name="codeObject">The object to be inserted.</param>
public void Insert(int index, CodeObjectVM codeObject)
{
// Insert the code view model into the block
_codeObjectVMs.Insert(index, codeObject);
}
/// <summary>
/// Find the first child code view model having type T.
/// </summary>
public T Find<T>() where T : CodeObjectVM
{
return Enumerable.FirstOrDefault(Enumerable.OfType<T>(_codeObjectVMs));
}
/// <summary>
/// Get an enumerator for the code view models in the <see cref="BlockVM"/>.
/// </summary>
IEnumerator<CodeObjectVM> IEnumerable<CodeObjectVM>.GetEnumerator()
{
return ((IEnumerable<CodeObjectVM>)_codeObjectVMs).GetEnumerator();
}
/// <summary>
/// Get an enumerator for the code view models in the <see cref="BlockVM"/>.
/// </summary>
IEnumerator IEnumerable.GetEnumerator()
{
return _codeObjectVMs.GetEnumerator();
}
/// <summary>
/// Remove the specified code view model from the <see cref="BlockVM"/>.
/// </summary>
/// <returns>True if the code object was found and removed, otherwise false.</returns>
public bool Remove(CodeObjectVM codeObject)
{
return _codeObjectVMs.Remove(codeObject);
}
/// <summary>
/// Remove the code view model at the specified index from the <see cref="BlockVM"/>.
/// </summary>
public void RemoveAt(int index)
{
_codeObjectVMs.RemoveAt(index);
}
/// <summary>
/// Remove all code view models from the <see cref="BlockVM"/>.
/// </summary>
public void RemoveAll()
{
_codeObjectVMs.Clear();
}
#endregion
#region /* RENDERING */
public override Brush BorderBrush
{
get { return (ParentVM != null ? ParentVM.BorderBrush : base.BorderBrush); }
}
public override Brush BackgroundBrush
{
get { return (ParentVM != null ? ParentVM.BackgroundBrush : base.BackgroundBrush); }
}
/// <summary>
/// Determines if braces should be hidden.
/// </summary>
public static bool HideBraces;
/// <summary>
/// Determines if braces should be tiny.
/// </summary>
public static bool TinyBraces;
public override void Render(CodeRenderer renderer, RenderFlags flags)
{
Block block = Block;
if (block.IsGenerated)
return;
if (flags.HasFlag(RenderFlags.Description))
{
TypeRefBaseVM.RenderType(renderer, Block.GetType(), RenderFlags.None, this);
return;
}
bool isTopLevel = IsTopLevel;
if (isTopLevel)
flags |= RenderFlags.NoBlockIndent;
// Render the open brace if appropriate
bool useBraces = block.HasBraces && !isTopLevel && !HideBraces;
if (useBraces || block.HasFirstOnLineAnnotations)
{
if (IsFirstOnLine)
{
if (!flags.HasFlag(RenderFlags.SuppressNewLine))
renderer.NewLine();
}
else
renderer.RenderText(" ", PUNC_BRUSH, ParentVM);
RenderBefore(renderer, flags);
if (useBraces)
{
renderer.RenderText(Block.ParseTokenStart, PUNC_BRUSH, CodeRenderer.CodeFontFamily,
TinyBraces ? CodeRenderer.TinyFontSize : CodeRenderer.CodeFontSize, 0, TextStyle.Normal, ParentVM);
}
flags &= ~RenderFlags.SuppressNewLine;
}
if (!flags.HasFlag(RenderFlags.NoEOLComments))
RenderInfixEOLComments(renderer, flags);
if (!flags.HasFlag(RenderFlags.NoBlockIndent))
renderer.IndentOnNewLineBegin(this, CodeObject.TabSize - 1); // Allow for SpaceIndent
// Render the body of the block
bool isSingleLineBody = true;
int codeObjectVMCount = (_codeObjectVMs != null ? _codeObjectVMs.Count : 0);
// Render an empty statement ';' if we have no children or a single comment and no braces
CodeObject parent = ParentVM.CodeObject;
if ((codeObjectVMCount == 0 || (codeObjectVMCount == 1 && _codeObjectVMs[0] is CommentVM)) && !block.HasBraces && (!(parent is BlockStatement) || ((BlockStatement)parent).RequiresEmptyStatement))
{
if (IsFirstOnLine)
{
if (!flags.HasFlag(RenderFlags.SuppressNewLine))
renderer.NewLine();
}
else
renderer.RenderText(" ", PUNC_BRUSH, ParentVM);
renderer.RenderText(Statement.ParseTokenTerminator, PUNC_BRUSH, ParentVM);
if (codeObjectVMCount > 0 && !CommentBaseVM.HideAll)
renderer.RenderText(" ", PUNC_BRUSH, ParentVM);
flags |= RenderFlags.SuppressNewLine;
}
if (codeObjectVMCount > 0)
{
RenderFlags passFlags = (flags & (RenderFlags.PassMask | RenderFlags.SuppressNewLine)) | RenderFlags.PrefixSpace;
bool firstItem = true;
bool skippingComment = false;
int skippedCommentNewLines = 0;
bool measureBlock = false, hasCanvas = false;
double lineWidth = 0, lineHeight = 0;
if (renderer.Measuring)
measureBlock = true;
else
{
hasCanvas = (_codeObjectVMs.Count > 1 && Height > 0 && Y > 0 && CodeRenderer.VirtualizeRendering);
if (hasCanvas)
{
renderer.NewLine();
_canvas = renderer.CreateCanvas(Height, Width);
}
}
// Process the child members of the block
for (int i = 0; i < codeObjectVMCount; ++i)
{
CodeObjectVM codeObjectVM = _codeObjectVMs[i];
CodeObject codeObject = codeObjectVM.CodeObject;
if (codeObject.IsGenerated)
continue;
int objNewLines = codeObject.NewLines;
if (CommentBaseVM.HideAll && codeObjectVM is CommentBaseVM)
{
// Skip comments if option is enabled
skippingComment = true;
skippedCommentNewLines = Math.Max(skippedCommentNewLines, objNewLines);
}
else
{
// Check for newlines, temporarily adjusting as necessary if we're skipping hidden comment(s)
int backupObjNewLines = objNewLines;
if (skippingComment)
{
if (firstItem)
objNewLines = 1;
else if (objNewLines <= 1 && skippedCommentNewLines > 1)
objNewLines = skippedCommentNewLines;
codeObject.SetNewLines(objNewLines);
}
if (objNewLines > 0)
isSingleLineBody = false;
// Do calculations if we're measuring (so we can do partial rendering with a canvas)
if (measureBlock)
{
if (codeObjectVM.IsFirstOnLine)
{
// For the first item, get the current absolute Y of the Canvas and store it in the Y of the BlockVM
// (if Y isn't non-zero for a BlockVM, it won't be virtualized with a Canvas).
// Include any newline that hasn't been rendered yet (after the closing brace).
if (firstItem && ParentVM is IBlockVM)
Y = renderer.GetCurrentAbsoluteY() + (flags.HasFlag(RenderFlags.SuppressNewLine) ? 0 : CodeRenderer.FontHeight);
if (objNewLines > 1)
Height += (objNewLines - 1) * (CodeRenderer.HalfHeightBlankLines ? (CodeRenderer.FontHeight / 2) : CodeRenderer.FontHeight);
codeObjectVM.Y = Height;
}
else
{
// Don't measure the block if the first item isn't first-on-line (partial rendering won't be done)
if (firstItem)
measureBlock = false;
else
{
// Handle 2nd or Nth object on the same line
CodeObjectVM previousVM = _codeObjectVMs[i - 1];
codeObjectVM.Y = previousVM.Y;
lineWidth += CodeRenderer.SpaceWidth;
codeObjectVM.X = lineWidth;
}
}
}
// If we're using a canvas, skip rendering if the code object occurs before OR after the visible window
if (!hasCanvas || (Y + codeObjectVM.Y + codeObjectVM.Height >= renderer.StartY && Y + codeObjectVM.Y < renderer.EndY))
{
// Render the code object (do NOT increase the indent, the indent state logic will take care of that)
codeObjectVM.Render(renderer, passFlags | RenderFlags.ForceBorder | RenderFlags.NoIncreaseIndent
| ((isSingleLineBody || flags.HasFlag(RenderFlags.NoBlockIndent)) ? 0
: (objNewLines > 0 ? RenderFlags.SpaceIndent : 0)));
}
// Do calculations if we're measuring (so we can do partial rendering with a canvas)
if (measureBlock)
{
if (codeObjectVM.IsFirstOnLine)
{
Height += codeObjectVM.Height;
lineWidth = codeObjectVM.Width;
}
else
{
// Handle 2nd or Nth object on the same line
CodeObjectVM previousVM = _codeObjectVMs[i - 1];
if (lineHeight == 0)
lineHeight = previousVM.Height;
if (codeObjectVM.Height > lineHeight)
{
Height += (codeObjectVM.Height - lineHeight);
lineHeight = codeObjectVM.Height;
}
lineWidth += codeObjectVM.Width;
if (i >= codeObjectVMCount - 1 || _codeObjectVMs[i + 1].IsFirstOnLine)
{
// Adjust the Y positions of all objects on the line to center them
for (int j = i; ; --j)
{
CodeObjectVM lineVM = _codeObjectVMs[j];
lineVM.Y += (lineHeight - lineVM.Height) / 2;
if (lineVM.IsFirstOnLine)
break;
}
lineHeight = 0;
}
}
if (lineWidth > Width)
Width = lineWidth;
}
// Undo any temporary changes to the newlines
if (skippingComment)
codeObject.SetNewLines(backupObjNewLines);
firstItem = false;
skippingComment = false;
skippedCommentNewLines = 0;
}
passFlags &= ~RenderFlags.SuppressNewLine;
flags &= ~RenderFlags.SuppressNewLine;
}
if (hasCanvas)
renderer.ReturnToCanvasParent();
}
if (!flags.HasFlag(RenderFlags.NoBlockIndent))
renderer.IndentOnNewLineEnd(this);
// Render the close brace if appropriate
if (useBraces)
{
int endNewLines = block.EndNewLines;
if (endNewLines > 0)
renderer.NewLines(endNewLines, ParentVM);
else
renderer.RenderText(" ", PUNC_BRUSH, ParentVM);
renderer.RenderText(Block.ParseTokenEnd, PUNC_BRUSH, CodeRenderer.CodeFontFamily,
TinyBraces ? CodeRenderer.TinyFontSize : CodeRenderer.CodeFontSize, 0, TextStyle.Normal, ParentVM);
}
// Render any EOL comments (rendered after the close brace when it's on a line by itself - special Infix
// EOL comments may also exist and be rendered after the open brace above).
if (!flags.HasFlag(RenderFlags.NoEOLComments))
RenderEOLComments(renderer, RenderFlags.None);
RenderAfter(renderer, flags);
}
public override void RenderVisible(CodeRenderer renderer, RenderFlags flags)
{
Block block = Block;
if (block.IsGenerated)
return;
if (IsTopLevel)
flags |= RenderFlags.NoBlockIndent;
// Render the body of the block
if (_codeObjectVMs != null && _codeObjectVMs.Count > 0)
{
RenderFlags passFlags = (flags & RenderFlags.PassMask) | RenderFlags.PrefixSpace;
bool firstItem = true;
bool skippingComment = false;
int skippedCommentNewLines = 0;
bool hasCanvas = (_canvas != null);
// Process the child members of the block
for (int i = 0; i < _codeObjectVMs.Count; ++i)
{
CodeObjectVM codeObjectVM = _codeObjectVMs[i];
CodeObject codeObject = codeObjectVM.CodeObject;
if (codeObject.IsGenerated)
continue;
int objNewLines = codeObject.NewLines;
if (CommentBaseVM.HideAll && codeObjectVM is CommentBaseVM)
{
// Skip comments if option is enabled
skippingComment = true;
skippedCommentNewLines = Math.Max(skippedCommentNewLines, objNewLines);
}
else
{
// Check for newlines, temporarily adjusting as necessary if we're skipping hidden comment(s)
int backupObjNewLines = objNewLines;
if (skippingComment)
{
if (firstItem)
objNewLines = 1;
else if (objNewLines <= 1 && skippedCommentNewLines > 1)
objNewLines = skippedCommentNewLines;
codeObject.SetNewLines(objNewLines);
}
// If we're using a canvas, render or free as appropriate, otherwise recursively call RenderVisible()
if (hasCanvas)
{
// If the object is visible, render it now if it's not rendered
if (Y + codeObjectVM.Y + codeObjectVM.Height >= renderer.StartY && Y + codeObjectVM.Y < renderer.EndY)
{
if (codeObjectVM.FrameworkElement == null)
{
// Render the code object (do NOT increase the indent, the indent state logic will take care of that)
renderer.SetCurrentCanvas(_canvas);
codeObjectVM.Render(renderer, passFlags | RenderFlags.ForceBorder | RenderFlags.NoIncreaseIndent
| (flags.HasFlag(RenderFlags.NoBlockIndent) ? 0 : (objNewLines > 0 ? RenderFlags.SpaceIndent : 0)));
}
else
codeObjectVM.RenderVisible(renderer, passFlags);
}
else if (codeObjectVM.FrameworkElement != null)
{
// Recursively free all UI objects if it's no longer visible
_canvas.Children.Remove(codeObjectVM.FrameworkElement);
codeObjectVM.UnRender();
}
}
else
codeObjectVM.RenderVisible(renderer, passFlags);
// Undo any temporary changes to the newlines
if (skippingComment)
codeObject.SetNewLines(backupObjNewLines);
firstItem = false;
skippingComment = false;
skippedCommentNewLines = 0;
}
}
if (hasCanvas)
renderer.ReturnToCanvasParent();
}
}
public override void UnRender()
{
ChildListHelpers.UnRender(_codeObjectVMs);
base.UnRender();
}
public override void RenderToolTip(CodeRenderer renderer, RenderFlags flags)
{
TypeRefBaseVM.RenderType(renderer, CodeObject.GetType(), flags, this);
RenderMessagesInToolTip(renderer, flags);
}
#endregion
#region /* COMMANDS */
public static readonly RoutedCommand TinyBracesCommand = new RoutedCommand("Tiny Braces", typeof(BlockVM));
public static readonly RoutedCommand HideBracesCommand = new RoutedCommand("Hide Braces", typeof(BlockVM));
static BlockVM()
{
InitializeCommands();
}
private static void InitializeCommands()
{
HideBracesCommand.InputGestures.Add(new KeyGesture(Key.OemCloseBrackets, ModifierKeys.Alt, "Alt-]"));
}
public static new void BindCommands(Window window)
{
WPFUtil.AddCommandBinding(window, TinyBracesCommand, tinyBraces_Executed);
WPFUtil.AddCommandBinding(window, HideBracesCommand, hideBraces_Executed);
}
private static void tinyBraces_Executed(object sender, ExecutedRoutedEventArgs e)
{
TinyBraces = !TinyBraces;
CodeRenderer.ReRenderAll();
}
private static void hideBraces_Executed(object sender, ExecutedRoutedEventArgs e)
{
HideBraces = !HideBraces;
CodeRenderer.ReRenderAll();
}
#endregion
}
}