/******************************************************************************
* Copyright (c) 2016 Oracle
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Konstantin Komissarchik - initial implementation and ongoing maintenance
******************************************************************************/
package org.eclipse.sapphire.ui.forms;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.sapphire.Disposable;
import org.eclipse.sapphire.Element;
import org.eclipse.sapphire.Event;
import org.eclipse.sapphire.Listener;
import org.eclipse.sapphire.ListenerContext;
import org.eclipse.sapphire.util.ListFactory;
/**
* @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a>
*/
public final class MasterDetailsContentOutline implements Disposable
{
private final MasterDetailsEditorPagePart editorPagePart;
private final MasterDetailsEditorPageDef editorPageDef;
private final Element rootModelElement;
private MasterDetailsContentNodePart root;
private List<MasterDetailsContentNodePart> selection;
private final ListenerContext listeners;
private String filterText;
public MasterDetailsContentOutline( final MasterDetailsEditorPagePart editorPagePart )
{
this.editorPagePart = editorPagePart;
this.editorPageDef = editorPagePart.definition();
this.rootModelElement = editorPagePart.getModelElement();
this.selection = Collections.emptyList();
this.listeners = new ListenerContext();
this.filterText = "";
}
public MasterDetailsContentNodePart getRoot()
{
if( this.root == null )
{
final MasterDetailsContentNodeDef rootNodeDef = this.editorPageDef.getRootNode();
this.root = new MasterDetailsContentNodePart();
this.root.init( this.editorPagePart, this.rootModelElement, rootNodeDef, Collections.<String,String>emptyMap() );
this.root.initialize();
loadTreeState();
attach
(
new Listener()
{
@Override
public void handle( final Event event )
{
if( event instanceof NodeExpandedStateChangedEvent || event instanceof SelectionChangedEvent )
{
saveTreeState();
}
}
}
);
}
return this.root;
}
public MasterDetailsContentNodePart getSelectedNode()
{
if( this.selection.isEmpty() )
{
return null;
}
else
{
return this.selection.get( 0 );
}
}
public List<MasterDetailsContentNodePart> getSelectedNodes()
{
return this.selection;
}
public void setSelectedNode( final MasterDetailsContentNodePart selection )
{
if( selection == null )
{
setSelectedNodes( Collections.<MasterDetailsContentNodePart>emptyList() );
}
else
{
setSelectedNodes( Collections.singletonList( selection ) );
}
}
public void setSelectedNodes( final List<MasterDetailsContentNodePart> selection )
{
if( ! this.selection.equals( selection ) )
{
for( MasterDetailsContentNodePart node : selection )
{
final MasterDetailsContentNodePart parent = node.getParentNode();
if( parent != null )
{
parent.setExpanded( true );
}
}
if( selection.isEmpty() )
{
this.selection = Collections.emptyList();
}
else
{
this.selection = new ArrayList<MasterDetailsContentNodePart>( selection );
}
this.listeners.broadcast( new SelectionChangedEvent( this.selection ) );
}
}
public void setSelection( final String path )
{
MasterDetailsContentNodePart node = this.root;
for( String segment : path.split( "/" ) )
{
boolean segmentMatched = false;
for( MasterDetailsContentNodePart n : node.nodes().visible() )
{
if( n.getLabel().equals( segment ) )
{
node = n;
segmentMatched = true;
break;
}
}
if( ! segmentMatched )
{
break;
}
}
if( node != this.root )
{
setSelectedNode( node );
}
}
void refreshSelection()
{
final List<MasterDetailsContentNodePart> newSelection = new ArrayList<MasterDetailsContentNodePart>();
for( MasterDetailsContentNodePart node : this.selection )
{
final LinkedList<MasterDetailsContentNodePart> path = new LinkedList<MasterDetailsContentNodePart>();
while( node != this.root )
{
path.addFirst( node );
node = node.getParentNode();
}
node = this.root;
for( MasterDetailsContentNodePart n : path )
{
if( node.nodes().visible().contains( n ) )
{
node = n;
}
else
{
break;
}
}
if( node == this.root )
{
final List<MasterDetailsContentNodePart> topLevelNodes = this.root.nodes().visible();
if( topLevelNodes.size() > 0 )
{
node = topLevelNodes.get( 0 );
}
else
{
node = null;
}
}
if( ! newSelection.contains( node ) )
{
newSelection.add( node );
}
}
setSelectedNodes( newSelection );
}
public List<MasterDetailsContentNodePart> getExpandedNodes()
{
final List<MasterDetailsContentNodePart> result = new ArrayList<MasterDetailsContentNodePart>();
for( MasterDetailsContentNodePart node : this.root.nodes().visible() )
{
node.getExpandedNodes( result );
}
return result;
}
public void setExpandedNodes( final Set<MasterDetailsContentNodePart> expandedNodes )
{
for( MasterDetailsContentNodePart node : this.root.nodes().visible() )
{
setExpandedNodes( node, expandedNodes );
}
}
private static void setExpandedNodes( final MasterDetailsContentNodePart node,
final Set<MasterDetailsContentNodePart> expandedNodes )
{
for( MasterDetailsContentNodePart child : node.nodes().visible() )
{
setExpandedNodes( child, expandedNodes );
}
final boolean shouldBeExpanded = expandedNodes.contains( node );
if( node.isExpanded() != shouldBeExpanded )
{
node.setExpanded( shouldBeExpanded );
}
}
public String getFilterText()
{
return this.filterText;
}
public void setFilterText( final String filterText )
{
if( ! this.filterText.equals( filterText ) )
{
this.filterText = filterText;
this.listeners.broadcast( new FilterChangedEvent( filterText ) );
}
}
public final ListenerContext listeners()
{
return this.listeners;
}
public final boolean attach( final Listener listener )
{
return this.listeners.attach( listener );
}
public final boolean detach( final Listener listener )
{
return this.listeners.detach( listener );
}
public void notifyOfNodeExpandedStateChange( final MasterDetailsContentNodePart node )
{
this.listeners.broadcast( new NodeExpandedStateChangedEvent( node ) );
}
@Override
public void dispose()
{
if( this.root != null )
{
this.root.dispose();
}
}
private void loadTreeState()
{
final List<MasterDetailsContentNodePart> selection = new ArrayList<MasterDetailsContentNodePart>();
final MasterDetailsEditorPageState editorPageState = this.editorPagePart.state();
if( editorPageState != null )
{
final MasterDetailsNodeState rootNodeState = editorPageState.getContentOutlineState().getRoot();
for( MasterDetailsContentNodePart node : this.root.nodes().visible() )
{
loadTreeState( rootNodeState, node, selection );
}
}
if( ! selection.isEmpty() )
{
setSelectedNodes( selection );
}
else
{
MasterDetailsContentNodePart node = this.root;
final String defaultInitialNodePath = this.editorPageDef.getInitialSelectionPath().text();
if( defaultInitialNodePath != null )
{
for( String segment : defaultInitialNodePath.split( "/" ) )
{
node = node.findNode( segment );
if( node != null && node.visible() )
{
node.setExpanded( true );
}
else
{
break;
}
}
}
if( node != null )
{
setSelectedNode( node );
}
}
}
private void loadTreeState( final MasterDetailsNodeState parentNodeState,
final MasterDetailsContentNodePart node,
final List<MasterDetailsContentNodePart> selection )
{
final String nodeLabel = node.getLabel();
for( MasterDetailsNodeState childNodeState : parentNodeState.getChildren() )
{
if( nodeLabel.equals( childNodeState.getLabel().text() ) )
{
node.setExpanded( childNodeState.getExpanded().content() );
if( childNodeState.getSelected().content() )
{
selection.add( node );
}
for( MasterDetailsContentNodePart child : node.nodes().visible() )
{
loadTreeState( childNodeState, child, selection );
}
break;
}
}
}
private void saveTreeState()
{
final MasterDetailsEditorPageState editorPageState = this.editorPagePart.state();
if( editorPageState != null )
{
final MasterDetailsNodeState rootNodeState = editorPageState.getContentOutlineState().getRoot();
rootNodeState.getChildren().clear();
final List<MasterDetailsContentNodePart> selection = getSelectedNodes();
for( MasterDetailsContentNodePart node : this.root.nodes().visible() )
{
saveTreeState( rootNodeState, node, selection );
}
}
}
private void saveTreeState( final MasterDetailsNodeState parentNodeState,
final MasterDetailsContentNodePart node,
final List<MasterDetailsContentNodePart> selection )
{
final boolean isExpanded = node.isExpanded();
final boolean isSelected = selection.contains( node );
if( isExpanded || isSelected )
{
final MasterDetailsNodeState childNodeState = parentNodeState.getChildren().insert();
childNodeState.setLabel( node.getLabel() );
if( isExpanded )
{
childNodeState.setExpanded( isExpanded );
}
if( isSelected )
{
childNodeState.setSelected( isSelected );
}
for( MasterDetailsContentNodePart child : node.nodes().visible() )
{
saveTreeState( childNodeState, child, selection );
}
}
}
public static final class NodeExpandedStateChangedEvent extends Event
{
private final MasterDetailsContentNodePart node;
public NodeExpandedStateChangedEvent( final MasterDetailsContentNodePart node )
{
this.node = node;
}
public MasterDetailsContentNodePart node()
{
return this.node;
}
}
public static final class SelectionChangedEvent extends Event
{
private final List<MasterDetailsContentNodePart> selection;
public SelectionChangedEvent( final List<MasterDetailsContentNodePart> selection )
{
this.selection = ListFactory.unmodifiable( selection );
}
public List<MasterDetailsContentNodePart> selection()
{
return this.selection;
}
@Override
public boolean supersedes( final Event event )
{
return ( event instanceof SelectionChangedEvent );
}
}
public static final class FilterChangedEvent extends Event
{
private final String filter;
public FilterChangedEvent( final String filter )
{
this.filter = filter;
}
public String filter()
{
return this.filter;
}
}
}