///
/// Copyright © 2003-2008 JetBrains s.r.o.
/// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder).
///
using System;
using System.Collections;
using System.Diagnostics;
using JetBrains.Omea.Base;
using JetBrains.Omea.Containers;
namespace JetBrains.JetListViewLibrary
{
///
/// Returns the names of groups for JetListView items.
///
public interface IGroupProvider
{
///
/// Returns the group name for the specified item.
///
/// The item displayed in the view.
/// The name of the group in which the item is shown.
string GetGroupName( object item );
}
///
/// The node which is a header of a group.
///
internal class GroupHeaderNode: IViewNode
{
private readonly NodeGroupCollection _owner;
private bool _expanded = true;
private string _text;
private JetListViewNode _topNode = null;
public GroupHeaderNode( NodeGroupCollection owner, string text )
{
_text = text;
_owner = owner;
}
public bool Expanded
{
get { return _expanded; }
set
{
if ( _expanded != value )
{
_expanded = value;
_owner.OnGroupExpandChanged( this );
}
}
}
public string Text
{
get { return _text; }
}
internal JetListViewNode TopNode
{
get { return _topNode; }
set { _topNode = value; }
}
}
///
/// Collection of grouped items in a JetListView.
///
internal class NodeGroupCollection: IVisibleNodeCollection, IDisposable
{
private JetListViewNodeCollection _nodeCollection;
private IGroupProvider _groupProvider;
private SortedList _groupMap = new SortedList();
public NodeGroupCollection( JetListViewNodeCollection nodeCollection, IGroupProvider groupProvider )
{
_nodeCollection = nodeCollection;
_groupProvider = groupProvider;
_nodeCollection.ViewNodeRemoving += HandleViewNodeRemoving;
_nodeCollection.NodeMoving += HandleNodeMoving;
_nodeCollection.NodeChanged += HandleNodeChanged;
_nodeCollection.NodesCollapsed += ForwardNodesCollapsed;
}
public void Dispose()
{
_nodeCollection.NodesCollapsed -= ForwardNodesCollapsed;
_nodeCollection.ViewNodeRemoving -= HandleViewNodeRemoving;
_nodeCollection.NodeMoving -= HandleNodeMoving;
_nodeCollection.NodeChanged -= HandleNodeChanged;
}
public event GroupEventHandler GroupAdded;
public event GroupEventHandler GroupRemoved;
public event ViewNodeEventHandler ViewNodeRemoving;
public event GroupEventHandler GroupExpandChanged;
public event EventHandler NodesCollapsed;
public GroupHeaderNode GetGroupHeader( string group )
{
GroupHeaderNode node = (GroupHeaderNode) _groupMap [group];
if ( node == null )
{
node = new GroupHeaderNode( this, group );
_groupMap [group] = node;
OnGroupAdded( node );
}
return node;
}
internal GroupHeaderNode GetNodeGroupHeader( JetListViewNode node )
{
JetListViewNode lvNode = node as JetListViewNode;
while( lvNode.Level > 0 )
{
lvNode = lvNode.Parent;
}
string groupName = _groupProvider.GetGroupName( lvNode.Data );
if ( groupName == null )
{
throw new InvalidOperationException( "Group provider returned null group name for object " + lvNode.Data );
}
return GetGroupHeader( groupName );
}
public int GroupCount
{
get { return _groupMap.Count; }
}
public IGroupProvider GroupProvider
{
get { return _groupProvider; }
}
private void OnGroupAdded( GroupHeaderNode header )
{
if ( GroupAdded != null )
{
GroupAdded( this, new GroupEventArgs( header ) );
}
}
private void OnGroupRemoved( GroupHeaderNode header )
{
if ( GroupRemoved != null )
{
GroupRemoved( this, new GroupEventArgs( header ) );
}
}
internal void OnGroupExpandChanged( GroupHeaderNode headerNode )
{
if ( GroupExpandChanged != null )
{
GroupExpandChanged( this, new GroupEventArgs( headerNode ) );
}
if ( !headerNode.Expanded )
{
if ( NodesCollapsed != null )
{
NodesCollapsed( this, EventArgs.Empty );
}
}
}
private void OnViewNodeRemoving( IViewNode viewNode )
{
if ( ViewNodeRemoving != null )
{
ViewNodeRemoving( this, new ViewNodeEventArgs( viewNode ) );
}
}
public IVisibleNodeEnumerator GetFullEnumerator()
{
return new GroupedItemEnumerator( this,
_nodeCollection.VisibleItems.GetEnumerator(), MoveDirection.Down );
}
public IVisibleNodeEnumerator GetDirectionalEnumerator( IViewNode startNode, MoveDirection direction )
{
Guard.NullArgument( startNode, "startNode" );
JetListViewNode startLvNode = startNode as JetListViewNode;
if ( startLvNode != null )
{
return new GroupedItemEnumerator( this,
_nodeCollection.GetDirectionalEnumerator( startLvNode, direction ), direction,
(direction == MoveDirection.Down) );
}
GroupHeaderNode startHeaderNode = startNode as GroupHeaderNode;
Debug.Assert( startHeaderNode.TopNode != null );
if ( startHeaderNode.TopNode == null )
{
return new GroupedItemEnumerator(this, new EmptyEnumerator(), direction, false );
}
GroupedItemEnumerator enumerator = new GroupedItemEnumerator( this,
_nodeCollection.GetDirectionalEnumerator( startHeaderNode.TopNode, direction ),
direction, false );
if ( direction == MoveDirection.Up )
{
// move from first node of current group to last node of previous group
enumerator.MoveNext();
}
return enumerator;
}
public MoveDirection GetMoveDirection( IViewNode startNode, IViewNode endNode )
{
JetListViewNode startLvNode = startNode as JetListViewNode;
if ( startLvNode == null )
{
GroupHeaderNode startHeaderNode = (GroupHeaderNode) startNode;
startLvNode = startHeaderNode.TopNode;
}
JetListViewNode endLvNode = endNode as JetListViewNode;
if ( endLvNode == null )
{
GroupHeaderNode endHeaderNode = (GroupHeaderNode) endNode;
endLvNode = endHeaderNode.TopNode;
}
int orderDiff = _nodeCollection.CompareVisibleOrder( startLvNode, endLvNode );
if ( orderDiff == 0 && startNode is GroupHeaderNode && !(endNode is GroupHeaderNode) )
{
return MoveDirection.Down;
}
return (orderDiff < 0 )
? MoveDirection.Down
: MoveDirection.Up;
}
public IViewNode GetVisibleParent( IViewNode node )
{
if ( node is GroupHeaderNode )
{
return node;
}
JetListViewNode lvNode = node as JetListViewNode;
GroupHeaderNode headerNode = GetNodeGroupHeader( lvNode );
if ( !headerNode.Expanded )
{
return headerNode;
}
return _nodeCollection.GetVisibleParent( lvNode );
}
public bool IsNodeVisible( IViewNode node )
{
if ( node is GroupHeaderNode )
{
return true;
}
JetListViewNode lvNode = node as JetListViewNode;
if ( !JetListViewNodeCollection.IsNodeVisible( lvNode ) )
{
return false;
}
return GetNodeGroupHeader( lvNode ).Expanded;
}
public void EnsureNodeVisible( IViewNode node )
{
JetListViewNode lvNode = node as JetListViewNode;
if ( lvNode != null )
{
_nodeCollection.ExpandParents( lvNode );
GroupHeaderNode group = GetNodeGroupHeader( lvNode );
group.Expanded = true;
}
}
private void ForwardNodesCollapsed( object sender, EventArgs e )
{
if ( NodesCollapsed != null )
{
NodesCollapsed( this, e );
}
}
private void HandleViewNodeRemoving( object sender, ViewNodeEventArgs e )
{
OnViewNodeRemoving( e.ViewNode );
ProcessViewNodeRemoving( (JetListViewNode) e.ViewNode );
}
private void HandleNodeMoving( object sender, JetListViewNodeEventArgs e )
{
ProcessViewNodeRemoving( e.Node );
}
private void ProcessViewNodeRemoving( JetListViewNode node )
{
if ( node.Level == 0 )
{
string nodeGroup = _groupProvider.GetGroupName( node.Data );
GroupHeaderNode nodeGroupHeader = (GroupHeaderNode) _groupMap [nodeGroup];
if ( nodeGroupHeader != null )
{
bool haveSameGroup = false;
int index = _nodeCollection.Root.IndexOf( node );
if ( index > 0 )
{
JetListViewNode prevNode = _nodeCollection.Root.Nodes [index-1];
if ( _groupProvider.GetGroupName( prevNode.Data ) == nodeGroup )
{
haveSameGroup = true;
}
}
if ( !haveSameGroup && index < _nodeCollection.Root.ChildCount-1 )
{
JetListViewNode nextNode = _nodeCollection.Root.Nodes [index+1];
if ( _groupProvider.GetGroupName( nextNode.Data ) == nodeGroup )
{
haveSameGroup = true;
}
}
if ( !haveSameGroup )
{
RemoveGroup( nodeGroupHeader );
}
}
}
}
private void HandleNodeChanged( object sender, JetListViewNodeEventArgs e )
{
if ( e.Node.Level == 0 )
{
GroupHeaderNode groupHeader = GetNodeGroupHeader( e.Node );
int headerIndex = _groupMap.IndexOfValue( groupHeader );
if ( headerIndex > 0 )
{
GroupHeaderNode prevGroup = (GroupHeaderNode) _groupMap.GetByIndex( headerIndex-1 );
if ( prevGroup.TopNode == e.Node )
{
RemoveGroup( prevGroup );
return;
}
}
if ( headerIndex < _groupMap.Count-1 )
{
GroupHeaderNode nextGroup = (GroupHeaderNode) _groupMap.GetByIndex( headerIndex+1 );
if ( nextGroup.TopNode == e.Node )
{
RemoveGroup( nextGroup );
return;
}
}
}
}
private void RemoveGroup( GroupHeaderNode nodeGroupHeader )
{
OnViewNodeRemoving( nodeGroupHeader );
_groupMap.Remove( nodeGroupHeader.Text );
OnGroupRemoved( nodeGroupHeader );
}
public void SetAllGroupsExpanded( bool expanded )
{
foreach( GroupHeaderNode node in _groupMap.Values )
{
node.Expanded = expanded;
}
}
public IViewNode LastVisibleViewNode
{
get { return GetNodeOrCollapsedHeader( _nodeCollection.LastVisibleNode ); }
}
public IViewNode FirstVisibleViewNode
{
get { return GetNodeOrCollapsedHeader( _nodeCollection.FirstVisibleNode ); }
}
private IViewNode GetNodeOrCollapsedHeader( JetListViewNode viewNode )
{
if ( viewNode != null )
{
GroupHeaderNode headerNode = GetNodeGroupHeader( viewNode );
if ( !headerNode.Expanded )
{
return headerNode;
}
}
return viewNode;
}
public int VisibleNodeCount
{
get
{
int count = 0;
IEnumerator enumerator = GetFullEnumerator();
while( enumerator.MoveNext() )
{
count++;
}
return count;
}
}
}
internal class GroupedItemEnumerator: IVisibleNodeEnumerator
{
private readonly NodeGroupCollection _groupCollection;
private GroupHeaderNode _curHeaderNode;
private IEnumerator _baseEnumerator;
private bool _skipFirstGroupHeader = false;
private bool _onHeaderNode = false;
private bool _lastHeaderNode = false;
private MoveDirection _moveDirection;
public GroupedItemEnumerator( NodeGroupCollection groupCollection, IEnumerator baseEnumerator,
MoveDirection moveDirection )
{
_groupCollection = groupCollection;
_baseEnumerator = baseEnumerator;
_moveDirection = moveDirection;
}
public GroupedItemEnumerator( NodeGroupCollection groupCollection, IEnumerator baseEnumerator,
MoveDirection moveDirection, bool skipFirstGroupHeader )
{
_groupCollection = groupCollection;
_baseEnumerator = baseEnumerator;
_moveDirection = moveDirection;
_skipFirstGroupHeader = skipFirstGroupHeader;
}
public bool MoveNext()
{
if ( _onHeaderNode )
{
if ( _moveDirection == MoveDirection.Up )
{
if ( _lastHeaderNode )
{
return false;
}
JetListViewNode groupNode = (JetListViewNode) _baseEnumerator.Current;
string newGroup = _groupCollection.GroupProvider.GetGroupName( groupNode.Data );
if ( newGroup == null )
{
throw new InvalidOperationException( "Group provider returned null group name for object " + groupNode.Data );
}
_curHeaderNode = _groupCollection.GetGroupHeader( newGroup );
}
if ( _curHeaderNode.Expanded )
{
_onHeaderNode = false;
return true;
}
if ( !SkipCollapsedGroup() )
{
if ( _moveDirection == MoveDirection.Up )
{
_lastHeaderNode = true;
return true;
}
return false;
}
}
else
{
if ( !_baseEnumerator.MoveNext() )
{
if ( _moveDirection == MoveDirection.Up )
{
_onHeaderNode = true;
_lastHeaderNode = true;
return true;
}
return false;
}
}
JetListViewNode curNode = (JetListViewNode) _baseEnumerator.Current;
GroupHeaderNode newHeaderNode = _groupCollection.GetNodeGroupHeader( curNode );
if ( newHeaderNode != _curHeaderNode )
{
if ( _curHeaderNode == null && _moveDirection == MoveDirection.Up )
{
_curHeaderNode = newHeaderNode;
}
else
{
if ( _moveDirection == MoveDirection.Down )
{
_curHeaderNode = newHeaderNode;
// if we're enumerating from middle of a group, we can't overwrite topNode
if ( !_skipFirstGroupHeader || _curHeaderNode.TopNode == null )
{
_curHeaderNode.TopNode = curNode;
}
}
if ( _skipFirstGroupHeader )
{
_skipFirstGroupHeader = false;
}
else
{
_onHeaderNode = true;
}
}
}
return true;
}
public void Reset()
{
_baseEnumerator.Reset();
}
public object Current
{
get
{
if ( _onHeaderNode )
{
return _curHeaderNode;
}
return _baseEnumerator.Current;
}
}
public IViewNode CurrentNode
{
get { return (IViewNode) Current; }
}
private bool SkipCollapsedGroup()
{
while( _baseEnumerator.MoveNext() )
{
JetListViewNode groupNode = (JetListViewNode) _baseEnumerator.Current;
if ( groupNode.Level == 0 )
{
string groupName = _groupCollection.GroupProvider.GetGroupName( groupNode.Data );
if ( groupName != _curHeaderNode.Text )
{
return true;
}
}
}
return false;
}
}
}