// -----------------------------------------------------
// KpiUtilities
//
// Contains re-usable KPI-related utility functions.
// -----------------------------------------------------
using System;
using System.Collections;
using System.Text;
using System.Windows.Forms;
namespace KpiDemo
{
public static class KpiUtilities
{
/// <summary>
/// Returns a collection of TreeNodes representing
///the display folders contained
/// in the displayFolders parameter. These TreeNodes
///will be created if not found.
/// An empty array will be returned if displayFolders
///does not contain any displayfolders.
/// </summary>
/// <param name="rootNodeCollection">nodes collection
///to use as the root of the KPI tree</param>
/// <param name="displayFolders">the value of a
///DisplayFolders property</param>
/// <param name="displayFolderImageIndex">the
///ImageIndex to use for a display folder tree node
///</param> <returns>
/// An array of TreeNode objects representing the
///display folders contained in displayFolders.
/// May return an empty array but will not return
/// null.
/// </returns>
public static TreeNode[] GetDisplayFolderTreeNodes(
TreeNodeCollection rootNodeCollection,
string displayFolders,
int displayFolderImageIndex )
{
if ( string.IsNullOrEmpty( displayFolders ) )
{
return new TreeNode[0];
}
else
{
string[][] parsedDisplayFolders =
ParseDisplayFolders( displayFolders );
int pathCount =
parsedDisplayFolders.GetLength( 0 );
TreeNode[] displayFolderNodes =
new TreeNode[pathCount];
for ( int pathIndex = 0; pathIndex < pathCount;
pathIndex++ )
{
string[] path =
parsedDisplayFolders[pathIndex];
TreeNode folderNode = null;
TreeNodeCollection parentNodeCollection =
rootNodeCollection;
foreach ( string folder in path )
{
TreeNode[] nodesFound =
parentNodeCollection.Find( folder, false );
if ( nodesFound.Length > 0 )
{
folderNode = nodesFound[0];
}
else
{
folderNode =
parentNodeCollection.Add( folder, folder,
displayFolderImageIndex,
displayFolderImageIndex );
}
parentNodeCollection = folderNode.Nodes;
}
displayFolderNodes[pathIndex] = folderNode;
}
return displayFolderNodes;
}
}
/// <summary>
/// Parses the value of a DisplayFolders property
/// into an array of paths, each of which is
/// an array of folder names.
/// </summary>
/// <param name="displayFolders">string containing
/// the value of a DisplayFolders property</param>
/// <returns>
/// An array of arrays of strings. The outer array
/// may be zero-length, but will not be null.
/// The inner arrays will not be null or
/// zero-length. Duplicate paths will be removed.
/// </returns>
public static string[][]
ParseDisplayFolders( string displayFolders )
{
const char pathDelimiter = ';';
const char folderDelimiter = '\\';
if ( displayFolders == null )
{
return new string[0][];
}
// Get paths
int emptyPathCount = 0;
string[] pathStrings =
displayFolders.Split( pathDelimiter );
string[][] allPaths =
new string[pathStrings.Length][];
for ( int i = 0; i < pathStrings.Length; i++ )
{
// Get folders
int emptyFolderCount = 0;
string[] allFolders =
pathStrings[i].Split( folderDelimiter );
for ( int j = 0; j < allFolders.Length; j++ )
{
allFolders[j] = allFolders[j].Trim();
if ( allFolders[j].Length == 0 )
{
emptyFolderCount++;
}
}
// Get currentPath without any
// empty folders
string[] nonEmptyFolders;
if ( emptyFolderCount == 0 )
{
// This is the common case,
// so optimize
nonEmptyFolders = allFolders;
}
else if ( emptyFolderCount == allFolders.Length )
{
nonEmptyFolders = null;
emptyPathCount++;
}
else
{
nonEmptyFolders =
new string [allFolders.Length
emptyFolderCount];
for ( int j = 0, k = 0; j <
allFolders.Length; j++ )
{
if ( allFolders[j].Length > 0 )
{
nonEmptyFolders[k] = allFolders[j];
k++;
}
}
}
// Remove duplicate paths
if ( nonEmptyFolders != null &&
IsPathAlreadyInPathsArray( allPaths,
nonEmptyFolders, i ) )
{
nonEmptyFolders = null;
emptyPathCount++;
}
allPaths[i] = nonEmptyFolders;
}
// Get results without any empty paths
string[][] nonEmptyPaths;
if ( emptyPathCount == 0 )
{
// This is the common case, so optimize
nonEmptyPaths = allPaths;
}
else if ( emptyPathCount == allPaths.Length )
{
nonEmptyPaths = new string[0][];
}
else
{
nonEmptyPaths =
new string[allPaths.Length
emptyPathCount][];
for ( int i = 0, j = 0; i < allPaths.Length; i++ )
{
if ( allPaths[i] != null )
{
nonEmptyPaths[j] =
allPaths[i];
j++;
}
}
}
return nonEmptyPaths;
}
/// <summary>
/// Takes a normalized value such as those associated
/// with KPI Status and Trend and returns
/// a numeric index of an image based on the
/// normalized value.
/// </summary>
/// <param name="normalizedValue">normalized value
/// between -1 and 1 (values less than -1 are treated
/// as -1 and those greater than 1 are treated as
/// 1)</param>
/// <param name="firstImageIndex">value of the first
/// image index</param>
/// <param name="lastImageIndex">value of the last
/// image index</param>
/// <returns>An integer between firstImageIndex and
/// lastImageIndex, inclusive</returns>
public static int GetImageIndex( double normalizedValue,
int firstImageIndex, int lastImageIndex )
{
const double normalizedLowerBound = -1.0;
const double normalizedUpperBound = 1.0;
if ( double.IsNaN( normalizedValue ) )
{
return firstImageIndex;
}
else if ( normalizedValue <= -1 )
{
return firstImageIndex;
}
else if ( normalizedValue >= 1 )
{
return lastImageIndex;
}
else
{
const double inputRange =
normalizedUpperBound
normalizedLowerBound;
double outputRange =
( double )( Math.Abs( lastImageIndex
firstImageIndex ) + 1 );
double outputSegmentsFromLowerBound =
( normalizedValue - normalizedLowerBound )
* ( outputRange / inputRange );
outputSegmentsFromLowerBound =
Math.Round( outputSegmentsFromLowerBound,
10 ); // round off floating point errors
int zeroBasedIndex = ( int )(
( normalizedValue > 0 ) ?
Math.Floor( outputSegmentsFromLowerBound):
// borders between segments (whole
//numbers) belong to the preceeding
// segment
Math.Ceiling(
outputSegmentsFromLowerBound ) - 1 );
// borders between segments (whole
// numbers) belong to the following
// segment
return ( firstImageIndex < lastImageIndex )?
firstImageIndex + zeroBasedIndex :
firstImageIndex - zeroBasedIndex;
}
}
#region ParseDisplayFolder helper methods
/// <summary>
/// Checks whether paths contains currentPath before
/// currentIndex. Used by ParseDisplayFolders.
/// </summary>
private static bool IsPathAlreadyInPathsArray(
string[][] paths,
string[] currentPath,
int currentIndex )
{
if ( paths == null )
{
return false;
}
int count =
Math.Min( currentIndex, paths.GetLength( 0 ) );
for ( int i = 0; i < count; i++ )
{
if ( DoPathsMatch( paths[i], currentPath ) )
{
return true;
}
}
return false;
}
/// <summary>
/// Checks whether path1 and path2 match.
/// </summary>
private static bool
DoPathsMatch( string[] path1, string[] path2 )
{
if ( path1 == null && path2 == null )
{
return true;
}
else if ( path1 == null || path2 == null )
{
return false;
}
else if ( path1.Length != path2.Length )
{
return false;
}
else
{
for ( int i = 0; i < path1.Length; i++ )
{
if ( string.Compare( path1[i],
path2[i], true,System.Globalization.
CultureInfo.CurrentUICulture )
!= 0 )
{
return false;
}
}
return true;
}
}
#endregion ParseDisplayFolder helper methods
}
}