Added tree UI classes (not used yet)

This commit is contained in:
Vhati 2013-11-19 02:10:32 -05:00
parent 6ca3fad77b
commit 71aabb6205
12 changed files with 1377 additions and 0 deletions

View file

@ -0,0 +1,91 @@
package net.vhati.modmanager.ui.tree;
import java.awt.BorderLayout;
import java.awt.Component;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
import net.vhati.modmanager.ui.tree.ChecklistTreePathFilter;
import net.vhati.modmanager.ui.tree.ChecklistTreeSelectionModel;
import net.vhati.modmanager.ui.tree.TristateCheckBox;
import net.vhati.modmanager.ui.tree.TristateButtonModel.TristateState;
/**
* A cell renderer that augments an existing renderer with a checkbox.
*/
public class ChecklistTreeCellRenderer extends JPanel implements TreeCellRenderer {
protected ChecklistTreeSelectionModel selectionModel;
protected ChecklistTreePathFilter checklistFilter;
protected TreeCellRenderer delegate;
protected TristateCheckBox checkbox = new TristateCheckBox();
protected int checkMaxX = 0;
/**
* Constructor.
*
* @param delegate a traditional TreeCellRenderer
* @param selectionModel a model to query for checkbox states
* @param checklistFilter a TreePath filter, or null to always show a checkbox
*/
public ChecklistTreeCellRenderer( TreeCellRenderer delegate, ChecklistTreeSelectionModel selectionModel, ChecklistTreePathFilter checklistFilter ) {
super();
this.delegate = delegate;
this.selectionModel = selectionModel;
this.checklistFilter = checklistFilter;
this.setLayout( new BorderLayout() );
this.setOpaque( false );
checkbox.setOpaque( false );
checkMaxX = checkbox.getPreferredSize().width;
}
@Override
public Component getTreeCellRendererComponent( JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus ) {
this.removeAll();
checkbox.setState( TristateState.DESELECTED );
Component delegateComp = delegate.getTreeCellRendererComponent( tree, value, sel, expanded, leaf, row, hasFocus );
TreePath path = tree.getPathForRow( row );
if ( path != null ) {
if ( selectionModel.isPathSelected( path, selectionModel.isDigged() ) ) {
checkbox.setState( TristateState.SELECTED );
} else {
checkbox.setState( ( selectionModel.isDigged() && selectionModel.isPartiallySelected( path ) ) ? TristateState.INDETERMINATE : TristateState.DESELECTED );
}
}
checkbox.setVisible( path == null || checklistFilter == null || checklistFilter.isSelectable( path ) );
checkbox.setEnabled( tree.isEnabled() );
this.add( checkbox, BorderLayout.WEST );
this.add( delegateComp, BorderLayout.CENTER );
return this;
}
public void setDelegate( TreeCellRenderer delegate ) {
this.delegate = delegate;
}
public TreeCellRenderer getDelegate() {
return delegate;
}
/**
* Returns the checkbox's right edge (in the renderer component's coordinate space).
*
* Values less than that can be interpreted as within the checkbox's bounds.
* X=0 is the renderer component's left edge.
*/
public int getCheckboxMaxX() {
return checkMaxX;
}
}

View file

@ -0,0 +1,129 @@
/**
* Based on CheckTreeManager (rev 120, 2007-07-20)
* By Santhosh Kumar T
* https://java.net/projects/myswing
*
* https://java.net/projects/myswing/sources/svn/content/trunk/src/skt/swing/tree/check/CheckTreeManager.java?rev=120
*/
/**
* MySwing: Advanced Swing Utilites
* Copyright (C) 2005 Santhosh Kumar T
* <p/>
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* <p/>
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package net.vhati.modmanager.ui.tree;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import net.vhati.modmanager.ui.tree.ChecklistTreePathFilter;
import net.vhati.modmanager.ui.tree.ChecklistTreeSelectionModel;
public class ChecklistTreeManager extends MouseAdapter implements TreeSelectionListener {
private ChecklistTreeSelectionModel selectionModel;
private ChecklistTreePathFilter checklistFilter;
protected JTree tree = new JTree();
protected int checkMaxX = 0;
/**
* Constructor.
*
* Modifies a given tree to add checkboxes.
* - The tree's existing cell renderer will be wrapped with a ChecklistTreeCellRenderer.
* - A MouseListener will be added to the tree to detect clicks, which will toggle checkboxes.
*
* A secondary ChecklistTreeSelectionModel will track checkboxes' states (independent of row
* highlighting).
*
* @param tree a tree to modify
* @param dig true show that a node is partially selected by scanning its descendents, false otherwise
* @checklistFilter a filter to decide which TreePaths need checkboxes, or null
*/
public ChecklistTreeManager( JTree tree, boolean dig, ChecklistTreePathFilter checklistFilter ) {
this.tree = tree;
this.checklistFilter = checklistFilter;
// Note: If largemodel is not set then treenodes are getting truncated.
// Need to debug further to find the problem.
if ( checklistFilter != null ) tree.setLargeModel( true );
selectionModel = new ChecklistTreeSelectionModel( tree.getModel(), dig );
ChecklistTreeCellRenderer checklistRenderer = new ChecklistTreeCellRenderer( tree.getCellRenderer(), selectionModel, checklistFilter );
setCheckboxMaxX( checklistRenderer.getCheckboxMaxX() );
tree.setCellRenderer( checklistRenderer );
selectionModel.addTreeSelectionListener( this );
tree.addMouseListener( this );
}
/**
* Sets the checkbox's right edge (in the TreeCellRenderer component's coordinate space).
*
* Values less than that will be interpreted as within the checkbox's bounds.
* X=0 is the renderer component's left edge.
*/
public void setCheckboxMaxX( int x ) {
checkMaxX = x;
}
public ChecklistTreePathFilter getChecklistFilter() {
return checklistFilter;
}
public ChecklistTreeSelectionModel getSelectionModel() {
return selectionModel;
}
@Override
public void mouseClicked( MouseEvent e ) {
TreePath path = tree.getPathForLocation( e.getX(), e.getY() );
if ( path == null ) return;
if ( e.getX() > tree.getPathBounds(path).x + checkMaxX ) return;
if ( checklistFilter != null && !checklistFilter.isSelectable(path) ) return;
boolean selected = selectionModel.isPathSelected( path, selectionModel.isDigged() );
selectionModel.removeTreeSelectionListener( this );
try {
if ( selected ) {
selectionModel.removeSelectionPath( path );
} else {
selectionModel.addSelectionPath( path );
}
}
finally {
selectionModel.addTreeSelectionListener( this );
tree.treeDidChange();
}
}
@Override
public void valueChanged( TreeSelectionEvent e ) {
tree.treeDidChange();
}
}

View file

@ -0,0 +1,196 @@
package net.vhati.modmanager.ui.tree;
import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.swing.DropMode;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import net.vhati.modmanager.ui.tree.ChecklistTreeManager;
import net.vhati.modmanager.ui.tree.ChecklistTreeSelectionModel;
import net.vhati.modmanager.ui.tree.GroupTreeCellRenderer;
import net.vhati.modmanager.ui.tree.TreeTransferHandler;
public class ChecklistTreePanel extends JPanel {
private DefaultTreeModel treeModel = null;
private JTree tree = null;
private ChecklistTreeManager checklistManager = null;
public ChecklistTreePanel() {
super( new BorderLayout() );
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode( "Root" );
treeModel = new DefaultTreeModel( rootNode );
tree = new JTree( treeModel );
tree.setCellRenderer( new GroupTreeCellRenderer() );
tree.setRootVisible( false );
tree.getSelectionModel().setSelectionMode( TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION );
checklistManager = new ChecklistTreeManager( tree, true, null );
JScrollPane scrollPane = new JScrollPane( tree, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED );
this.add( scrollPane, BorderLayout.CENTER );
tree.setTransferHandler( new TreeTransferHandler( tree ) );
tree.setDropMode( DropMode.ON_OR_INSERT ); // Drop between rows, or onto groups.
tree.setDragEnabled( true );
}
/**
* Returns all userObjects of nodes with ticked checkboxes (except root itself).
*/
public List<Object> getSelectedUserObjects() {
ChecklistTreeSelectionModel checklistSelectionModel = checklistManager.getSelectionModel();
List<Object> results = new ArrayList<Object>();
for ( Enumeration enumer = checklistSelectionModel.getAllSelectedPaths(); enumer.hasMoreElements(); ) {
DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)enumer.nextElement();
if ( !childNode.isRoot() && childNode.getUserObject() != null ) {
results.add( childNode.getUserObject() );
}
}
return results;
}
/**
* Returns all userObjects of all nodes (except root itself).
*/
public List<Object> getAllUserObjects() {
List<Object> results = new ArrayList<Object>();
DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)treeModel.getRoot();
getAllUserObjects( rootNode, results );
return results;
}
private void getAllUserObjects( DefaultMutableTreeNode currentNode, List<Object> results ) {
if ( !currentNode.isRoot() && currentNode.getUserObject() != null ) {
results.add( currentNode.getUserObject() );
}
for ( Enumeration enumer = currentNode.children(); enumer.hasMoreElements(); ) {
DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)enumer.nextElement();
getAllUserObjects( currentNode, results );
}
}
public void clear() {
DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)treeModel.getRoot();
rootNode.removeAllChildren();
treeModel.reload();
}
/**
* Adds a group to consolidate mods.
*
* TODO: Trigger a rename.
*/
public void addGroup() {
DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)treeModel.getRoot();
GroupAwareTreeNode groupNode = new GroupAwareTreeNode( "New Group", true );
rootNode.add( groupNode );
treeModel.nodesWereInserted( rootNode, new int[]{rootNode.getIndex( groupNode )} );
}
/**
* Disband selected groups.
*
* TODO
*/
public void removeSelectedGroups() {
}
/**
* Rename last selected group.
*
* TODO
*/
public void renameSelectedGroup() {
}
/**
* Cycles through ticking all checkboxes and clearing them.
*/
public void toggleAllNodeSelection() {
}
/**
* Cycles through expanding all nodes and collapsing them.
*/
public void toggleAllNodeExpansion() {
boolean canExpand = false;
boolean canCollapse = false;
for ( int i = tree.getRowCount()-1; i >= 0; i-- ) {
if ( tree.isCollapsed( i ) ) {
canExpand = true;
}
else if ( tree.isExpanded( i ) ) {
canCollapse = true;
}
}
if ( canExpand ) {
expandAllNodes( tree.getRowCount() );
}
else if ( canCollapse ) {
collapseAllNodes( new TreePath( treeModel.getRoot() ) );
}
}
/**
* Expands all nodes by repeatedly expanding until the row count stops
* growing.
*/
public void expandAllNodes( int prevRowCount ) {
for ( int i=0; i < prevRowCount; i++ ) {
tree.expandRow( i );
}
if ( tree.getRowCount() != prevRowCount ) {
expandAllNodes( tree.getRowCount() );
}
}
/**
* Collapses all nodes by walking the TreeModel.
*/
public void collapseAllNodes( TreePath currentPath ) {
Object currentNode = currentPath.getLastPathComponent();
for ( int i = treeModel.getChildCount( currentNode )-1; i >= 0; i-- ) {
Object childNode = treeModel.getChild( currentNode, i );
TreePath childPath = currentPath.pathByAddingChild( childNode );
collapseAllNodes( childPath );
}
if ( currentNode != treeModel.getRoot() ) tree.collapsePath( currentPath );
}
public JTree getTree() {
return tree;
}
public DefaultTreeModel getTreeModel() {
return treeModel;
}
public ChecklistTreeManager getChecklistManager() {
return checklistManager;
}
}

View file

@ -0,0 +1,13 @@
package net.vhati.modmanager.ui.tree;
import javax.swing.tree.TreePath;
/**
* Decides whether a given TreePath should have a checkbox.
*/
public interface ChecklistTreePathFilter {
public boolean isSelectable( TreePath path );
}

View file

@ -0,0 +1,258 @@
/*
* Based on CheckTreeSelectionModel (rev 120, 2007-07-20)
* By Santhosh Kumar T
* https://java.net/projects/myswing
*
* https://java.net/projects/myswing/sources/svn/content/trunk/src/skt/swing/tree/check/CheckTreeSelectionModel.java?rev=120
*/
/**
* MySwing: Advanced Swing Utilites
* Copyright (C) 2005 Santhosh Kumar T
* <p/>
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* <p/>
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package net.vhati.modmanager.ui.tree;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Stack;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import net.vhati.modmanager.ui.tree.PreorderEnumeration;
public class ChecklistTreeSelectionModel extends DefaultTreeSelectionModel {
private TreeModel model;
private boolean dig = true;
public ChecklistTreeSelectionModel( TreeModel model, boolean dig ) {
this.model = model;
this.dig = dig;
this.setSelectionMode( TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION );
}
public boolean isDigged() {
return dig;
}
/**
* Returns true if path1 is a descendant of path2.
*/
private boolean isDescendant( TreePath path1, TreePath path2 ) {
Object obj1[] = path1.getPath();
Object obj2[] = path2.getPath();
for ( int i=0; i < obj2.length; i++ ) {
if ( obj1[i] != obj2[i] ) return false;
}
return true;
}
/**
* Returns true a selected node exists in the subtree of a given unselected path.
* Returns false if the given path is itself selected.
*/
public boolean isPartiallySelected( TreePath path ) {
if ( isPathSelected( path, true ) ) return false;
TreePath[] selectionPaths = getSelectionPaths();
if( selectionPaths == null ) return false;
for ( int j=0; j < selectionPaths.length; j++ ) {
if ( isDescendant( selectionPaths[j], path ) ) {
return true;
}
}
return false;
}
/**
* Returns true if a given path is selected.
*
* If dig is true, then the path is assumed to be selected, if
* one of its ancestors is selected.
*/
public boolean isPathSelected( TreePath path, boolean dig ) {
if ( !dig ) return super.isPathSelected( path );
while ( path != null && !super.isPathSelected( path ) ) {
path = path.getParentPath();
}
return ( path != null );
}
@Override
public void setSelectionPaths( TreePath[] paths ) {
if ( dig ) {
throw new UnsupportedOperationException();
} else {
super.setSelectionPaths( paths );
}
}
@Override
public void addSelectionPaths( TreePath[] paths ) {
if ( !dig ) {
super.addSelectionPaths( paths );
return;
}
// Unselect all descendants of paths[].
for( int i=0; i < paths.length; i++ ) {
TreePath path = paths[i];
TreePath[] selectionPaths = getSelectionPaths();
if ( selectionPaths == null ) break;
ArrayList toBeRemoved = new ArrayList();
for ( int j=0; j < selectionPaths.length; j++ ) {
if ( isDescendant( selectionPaths[j], path ) ) {
toBeRemoved.add( selectionPaths[j] );
}
}
super.removeSelectionPaths( (TreePath[])toBeRemoved.toArray( new TreePath[0] ) );
}
// If all siblings are selected then unselect them and select parent recursively
// otherwize just select that path.
for ( int i=0; i < paths.length; i++ ) {
TreePath path = paths[i];
TreePath temp = null;
while ( areSiblingsSelected(path) ) {
temp = path;
if ( path.getParentPath() == null ) break;
path = path.getParentPath();
}
if ( temp != null ) {
if ( temp.getParentPath() != null ) {
addSelectionPath( temp.getParentPath() );
}
else {
if ( !isSelectionEmpty() ) {
removeSelectionPaths(getSelectionPaths());
}
super.addSelectionPaths( new TreePath[]{temp} );
}
}
else {
super.addSelectionPaths( new TreePath[]{path} );
}
}
}
@Override
public void removeSelectionPaths( TreePath[] paths ) {
if( !dig ) {
super.removeSelectionPaths( paths );
return;
}
for ( int i=0; i < paths.length; i++ ) {
TreePath path = paths[i];
if ( path.getPathCount() == 1 ) {
super.removeSelectionPaths( new TreePath[]{path} );
} else {
toggleRemoveSelection(path);
}
}
}
/**
* Returns true if all siblings of given path are selected.
*/
private boolean areSiblingsSelected( TreePath path ) {
TreePath parent = path.getParentPath();
if ( parent == null ) return true;
Object node = path.getLastPathComponent();
Object parentNode = parent.getLastPathComponent();
int childCount = model.getChildCount( parentNode );
for ( int i=0; i < childCount; i++ ) {
Object childNode = model.getChild( parentNode, i );
if ( childNode == node ) continue;
if ( !isPathSelected( parent.pathByAddingChild( childNode ) ) ) {
return false;
}
}
return true;
}
/**
* Unselects a given path, toggling ancestors if they were entirely selected.
*
* If any ancestor node of the given path is selected, it will be unselected,
* and all its descendants - except any within the given path - will be selected.
* The ancestor will have gone from fully selected to partially selected.
*
* Otherwise, the given path will be unselected, and nothing else will change.
*/
private void toggleRemoveSelection( TreePath path ) {
Stack stack = new Stack();
TreePath parent = path.getParentPath();
while ( parent != null && !isPathSelected( parent ) ) {
stack.push( parent );
parent = parent.getParentPath();
}
if ( parent != null ) {
stack.push( parent );
}
else {
super.removeSelectionPaths( new TreePath[]{path} );
return;
}
while ( !stack.isEmpty() ) {
TreePath temp = (TreePath)stack.pop();
TreePath peekPath = ( stack.isEmpty() ? path : (TreePath)stack.peek() );
Object node = temp.getLastPathComponent();
Object peekNode = peekPath.getLastPathComponent();
int childCount = model.getChildCount( node );
for ( int i=0; i < childCount; i++ ) {
Object childNode = model.getChild( node, i );
if ( childNode != peekNode ) {
super.addSelectionPaths( new TreePath[]{temp.pathByAddingChild( childNode )} );
}
}
}
super.removeSelectionPaths( new TreePath[]{parent} );
}
public Enumeration getAllSelectedPaths() {
TreePath[] treePaths = getSelectionPaths();
if ( treePaths == null ) {
return Collections.enumeration( Collections.EMPTY_LIST );
}
Enumeration enumer = Collections.enumeration( Arrays.asList( treePaths ) );
if ( dig ) {
enumer = new PreorderEnumeration( enumer, model );
}
return enumer;
}
}

View file

@ -0,0 +1,57 @@
/**
* Based on ChildrenEnumeration (rev 120, 2007-07-20)
* By Santhosh Kumar T
* https://java.net/projects/myswing
*
* https://java.net/projects/myswing/sources/svn/content/trunk/src/skt/swing/tree/ChildrenEnumeration.java?rev=120
*/
/**
* MySwing: Advanced Swing Utilites
* Copyright (C) 2005 Santhosh Kumar T
* <p/>
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* <p/>
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package net.vhati.modmanager.ui.tree;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
public class ChildrenEnumeration implements Enumeration {
private TreePath path;
private TreeModel model;
private int position = 0;
private int childCount;
public ChildrenEnumeration( TreePath path, TreeModel model ) {
this.path = path;
this.model = model;
childCount = model.getChildCount( path.getLastPathComponent() );
}
@Override
public boolean hasMoreElements() {
return position < childCount;
}
@Override
public Object nextElement() {
if( !hasMoreElements() ) throw new NoSuchElementException();
return path.pathByAddingChild( model.getChild( path.getLastPathComponent(), position++ ) );
}
}

View file

@ -0,0 +1,23 @@
package net.vhati.modmanager.ui.tree;
import javax.swing.tree.DefaultMutableTreeNode;
/**
* A TreeNode that remembers whether it allows children when cloned.
*/
public class GroupAwareTreeNode extends DefaultMutableTreeNode {
public GroupAwareTreeNode( Object userObject, boolean allowsChildren ) {
super( userObject, allowsChildren );
}
@Override
public Object clone() {
GroupAwareTreeNode newNode = (GroupAwareTreeNode)super.clone();
newNode.setAllowsChildren( this.getAllowsChildren() );
return newNode;
}
}

View file

@ -0,0 +1,39 @@
package net.vhati.modmanager.ui.tree;
import java.awt.Component;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
/**
* A renderer that sets icons based on whether children are allowed.
*
* A group with no children will still have a group icon.
*/
public class GroupTreeCellRenderer extends DefaultTreeCellRenderer {
@Override
public Component getTreeCellRendererComponent( JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus ) {
super.getTreeCellRendererComponent( tree, value, sel, expanded, leaf, row, hasFocus );
if ( value instanceof DefaultMutableTreeNode ) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
if ( node.getAllowsChildren() ) {
if ( expanded ) {
this.setIcon( this.getDefaultOpenIcon() );
this.setDisabledIcon( this.getDefaultOpenIcon() );
} else {
this.setIcon( this.getDefaultClosedIcon() );
this.setDisabledIcon( this.getDefaultClosedIcon() );
}
} else {
this.setIcon( this.getDefaultLeafIcon() );
this.setDisabledIcon( this.getDefaultLeafIcon() );
}
}
return this;
}
}

View file

@ -0,0 +1,67 @@
/**
* Based on PreorderEnumeration (rev 120, 2007-07-20)
* By Santhosh Kumar T
* https://java.net/projects/myswing
*
* https://java.net/projects/myswing/sources/svn/content/trunk/src/skt/swing/tree/PreorderEnumeration.java?rev=120
*/
/**
* MySwing: Advanced Swing Utilites
* Copyright (C) 2005 Santhosh Kumar T
* <p/>
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* <p/>
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package net.vhati.modmanager.ui.tree;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Stack;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
public class PreorderEnumeration implements Enumeration {
private TreeModel model;
protected Stack<Enumeration> stack = new Stack<Enumeration>();
public PreorderEnumeration( TreePath path, TreeModel model ) {
this( Collections.enumeration( Collections.singletonList( path ) ), model );
}
public PreorderEnumeration( Enumeration enumer, TreeModel model ){
this.model = model;
stack.push( enumer );
}
@Override
public boolean hasMoreElements() {
return ( !stack.empty() && stack.peek().hasMoreElements() );
}
@Override
public Object nextElement() {
Enumeration enumer = stack.peek();
TreePath path = (TreePath)enumer.nextElement();
if ( !enumer.hasMoreElements() ) stack.pop();
if ( model.getChildCount( path.getLastPathComponent() ) > 0 ) {
stack.push( new ChildrenEnumeration( path, model ) );
}
return path;
}
}

View file

@ -0,0 +1,257 @@
package net.vhati.modmanager.ui.tree;
import java.awt.Cursor;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DragSource;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.TransferHandler;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
/**
* A handler to enable drag-and-drop within a JTree.
*
* When dropped, copies of highlighted nodes will be made via clone() and
* inserted at the drop location, then the originals will be removed.
*
* Dragging onto a space between nodes will insert at that location.
* Dragging onto a node that allows children will insert into it.
* Dragging onto a node that doesn't allow children will insert after it.
*
* All nodes must be instances of DefaultMutableTreeNode (or subclasses).
* Set the Jtree's DropMode to ON_OR_INSERT.
* The root node must be hidden, to prevent it from being dragged.
* The tree's selection model may be set to single or multiple.
*/
public class TreeTransferHandler extends TransferHandler {
private DataFlavor localTreePathFlavor = null;
private JTree tree = null;
public TreeTransferHandler( JTree tree ) {
super();
this.tree = tree;
try {
localTreePathFlavor = new DataFlavor( DataFlavor.javaJVMLocalObjectMimeType + ";class=\""+ TreePath[].class.getName() +"\"" );
}
catch ( ClassNotFoundException e ) {
//log.error( e );
}
}
@Override
protected Transferable createTransferable( JComponent c ) {
assert ( c == tree );
TreePath[] highlightedPaths = tree.getSelectionPaths();
Map<Integer,List<TreePath>> pathsByLengthMap = new TreeMap<Integer,List<TreePath>>();
for ( TreePath path : highlightedPaths ) {
if ( path.getPath().length == 1 ) continue; // Omit root node (shouldn't drag it anyway).
Integer pathLength = new Integer( path.getPath().length );
if ( !pathsByLengthMap.containsKey( pathLength ) ) {
pathsByLengthMap.put( pathLength, new ArrayList<TreePath>() );
}
pathsByLengthMap.get( pathLength ).add( path );
}
// For each length (shortest-first), iterate its paths.
// For each of those paths, search longer lengths' lists,
// removing any paths that are descendants of those short ancestor nodes.
List<Integer> lengthsList = new ArrayList( pathsByLengthMap.keySet() );
for ( int i=0; i < lengthsList.size(); i++ ) {
for ( TreePath ancestorPath : pathsByLengthMap.get( lengthsList.get( i ) ) ) {
for ( int j=i+1; j < lengthsList.size(); j++ ) {
List<TreePath> childPaths = pathsByLengthMap.get( lengthsList.get( j ) );
for ( Iterator<TreePath> childIt = childPaths.iterator(); childIt.hasNext(); ) {
TreePath childPath = childIt.next();
if ( ancestorPath.isDescendant( childPath ) ) {
childIt.remove();
}
}
}
}
}
List<TreePath> uniquePathList = new ArrayList<TreePath>();
for ( List<TreePath> paths : pathsByLengthMap.values() ) {
uniquePathList.addAll( paths );
}
TreePath[] uniquePathsArray = uniquePathList.toArray( new TreePath[uniquePathList.size()] );
return new TreePathTransferrable( uniquePathsArray );
}
@Override
public boolean canImport( TransferHandler.TransferSupport ts ) {
boolean b = ( ts.getComponent() == tree && ts.isDrop() && ts.isDataFlavorSupported(localTreePathFlavor) );
tree.setCursor( b ? DragSource.DefaultMoveDrop : DragSource.DefaultMoveNoDrop );
return b;
}
@Override
public int getSourceActions( JComponent comp ) {
return TransferHandler.MOVE;
}
@Override
@SuppressWarnings("Unchecked")
public boolean importData( TransferHandler.TransferSupport ts ) {
if ( !canImport(ts) ) return false;
JTree dstTree = (JTree)ts.getComponent();
DefaultTreeModel dstTreeModel = (DefaultTreeModel)dstTree.getModel();
JTree.DropLocation dl = (JTree.DropLocation)ts.getDropLocation();
TreePath dropPath = dl.getPath(); // Dest parent node, or null.
int dropIndex = dl.getChildIndex(); // Insertion child index in the dest parent node,
// or -1 if dropped onto a group.
dstTree.setCursor( Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR) );
if ( dropPath == null ) return false;
DefaultMutableTreeNode dropParentNode = (DefaultMutableTreeNode)dropPath.getLastPathComponent();
// When dropping onto a non-group node, insert into the position after it instead.
if ( !dropParentNode.getAllowsChildren() ) {
DefaultMutableTreeNode prevParentNode = dropParentNode;
dropPath = dropPath.getParentPath();
dropParentNode = (DefaultMutableTreeNode)dropPath.getLastPathComponent();
dropIndex = dropParentNode.getIndex( prevParentNode ) + 1;
}
try {
TreePath[] draggedPaths = (TreePath[])ts.getTransferable().getTransferData( localTreePathFlavor );
// Bail if the dropPath was among those dragged.
boolean badDrop = false;
for ( TreePath path : draggedPaths ) {
if ( path.equals( dropPath ) ) {
badDrop = true;
break;
}
}
if ( !badDrop && dropParentNode.getAllowsChildren() ) {
for ( TreePath path : draggedPaths ) {
// Copy the dragged node and any children.
DefaultMutableTreeNode srcNode = (DefaultMutableTreeNode)path.getLastPathComponent();
DefaultMutableTreeNode newNode = (DefaultMutableTreeNode)cloneNodes( srcNode );
if ( dropIndex != -1 ) {
// Insert.
dropParentNode.insert( newNode, dropIndex );
dstTreeModel.nodesWereInserted( dropParentNode, new int[]{dropIndex} );
dropIndex++; // Next insertion will be after this node.
}
else {
// Add to the end.
dropParentNode.add( newNode );
dstTreeModel.nodesWereInserted( dropParentNode, new int[]{dropParentNode.getChildCount()-1} );
if ( !dstTree.isExpanded( dropPath ) ) dstTree.expandPath( dropPath );
}
}
return true;
}
}
catch ( Exception e ) {
// UnsupportedFlavorException: if Transferable.getTransferData() fails.
// IOException: if Transferable.getTransferData() fails.
// IllegalStateException: if insert/add fails because dropPath's node doesn't allow children.
//log.error( e );
}
return false;
}
@Override
protected void exportDone( JComponent source, Transferable data, int action ) {
if ( action == TransferHandler.MOVE || action == TransferHandler.NONE ) {
tree.setCursor( Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR) );
}
JTree srcTree = (JTree)source;
DefaultTreeModel srcTreeModel = (DefaultTreeModel)srcTree.getModel();
if ( action == TransferHandler.MOVE ) {
// Remove original dragged rows now that the move completed.
// Scan the tree checking equality is fine (DefaultMutableTreeNode's equals() does ==).
try {
TreePath[] draggedPaths = (TreePath[])data.getTransferData( localTreePathFlavor );
for ( TreePath path : draggedPaths ) {
DefaultMutableTreeNode doomedNode = (DefaultMutableTreeNode)path.getLastPathComponent();
TreeNode parentNode = doomedNode.getParent();
int doomedIndex = parentNode.getIndex( doomedNode );
doomedNode.removeFromParent();
srcTreeModel.nodesWereRemoved( parentNode, new int[]{doomedIndex}, new Object[]{doomedNode} );
}
}
catch ( Exception e ) {
//log.error( e );
}
}
}
/**
* Recursively clones a node and its descendants.
*
* The clone() methods will reuse userObjects, but the nodes themselves will be new.
*/
@SuppressWarnings("Unchecked")
private DefaultMutableTreeNode cloneNodes( DefaultMutableTreeNode srcNode ) {
DefaultMutableTreeNode resultNode = (DefaultMutableTreeNode)srcNode.clone();
Enumeration enumer = srcNode.children();
while ( enumer.hasMoreElements() ) {
DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)enumer.nextElement();
resultNode.add( cloneNodes( (DefaultMutableTreeNode)childNode ) );
}
return resultNode;
}
/**
* Drag and drop TreePath data, constructed with a raw object
* from a drag source, to be transformed into a flavor
* suitable for the drop target.
*/
private class TreePathTransferrable implements Transferable {
private TreePath[] data;
public TreePathTransferrable( TreePath[] data ) {
this.data = data;
}
@Override
public Object getTransferData( DataFlavor flavor ) {
if ( flavor.equals( localTreePathFlavor ) ) {
return data;
}
return null;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] {localTreePathFlavor};
}
@Override
public boolean isDataFlavorSupported( DataFlavor flavor ) {
return flavor.equals( localTreePathFlavor );
}
}
}

View file

@ -0,0 +1,105 @@
/**
* Copied from "TristateCheckBox Revisited" (2007-05-25)
* By Dr. Heinz M. Kabutz
* http://www.javaspecialists.co.za/archive/Issue145.html
*/
package net.vhati.modmanager.ui.tree;
import java.awt.event.ItemEvent;
import javax.swing.JToggleButton.ToggleButtonModel;
public class TristateButtonModel extends ToggleButtonModel {
private TristateState state = TristateState.DESELECTED;
public TristateButtonModel( TristateState state ) {
setState( state );
}
public TristateButtonModel() {
this( TristateState.DESELECTED );
}
public void setIndeterminate() {
setState( TristateState.INDETERMINATE );
}
public boolean isIndeterminate() {
return ( state == TristateState.INDETERMINATE );
}
@Override
public void setEnabled( boolean enabled ) {
super.setEnabled(enabled);
// Restore state display.
displayState();
}
@Override
public void setSelected( boolean selected ) {
setState( selected ? TristateState.SELECTED : TristateState.DESELECTED );
}
@Override
public void setArmed( boolean b ) {
}
@Override
public void setPressed( boolean b ) {
}
public void iterateState() {
setState( state.next() );
}
public void setState( TristateState state ) {
this.state = state;
displayState();
if ( state == TristateState.INDETERMINATE && isEnabled() ) {
// Send ChangeEvent.
fireStateChanged();
// Send ItemEvent.
int indeterminate = 3;
fireItemStateChanged(new ItemEvent( this, ItemEvent.ITEM_STATE_CHANGED, this, indeterminate ));
}
}
private void displayState() {
super.setSelected( state != TristateState.DESELECTED );
super.setArmed( state == TristateState.INDETERMINATE );
super.setPressed( state == TristateState.INDETERMINATE );
}
public TristateState getState() {
return state;
}
public static enum TristateState {
SELECTED {
public TristateState next() {
return INDETERMINATE;
}
},
INDETERMINATE {
public TristateState next() {
return DESELECTED;
}
},
DESELECTED {
public TristateState next() {
return SELECTED;
}
};
public abstract TristateState next();
}
}

View file

@ -0,0 +1,142 @@
/*
* Based on "TristateCheckBox Revisited" (2007-05-25)
* By Dr. Heinz M. Kabutz
* http://www.javaspecialists.co.za/archive/Issue145.html
*/
package net.vhati.modmanager.ui.tree;
import java.awt.AWTEvent;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.JCheckBox;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ActionMapUIResource;
import net.vhati.modmanager.ui.tree.TristateButtonModel;
import net.vhati.modmanager.ui.tree.TristateButtonModel.TristateState;
public class TristateCheckBox extends JCheckBox {
private final ChangeListener enableListener;
public TristateCheckBox( String text, Icon icon, TristateState initial ) {
super( text, icon );
setModel( new TristateButtonModel( initial ) );
enableListener = new ChangeListener() {
@Override
public void stateChanged( ChangeEvent e ) {
TristateCheckBox.this.setFocusable( TristateCheckBox.this.getModel().isEnabled() );
}
};
// Add a listener for when the mouse is pressed.
super.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed( MouseEvent e ) {
TristateCheckBox.this.iterateState();
}
});
// Reset the keyboard action map.
ActionMap map = new ActionMapUIResource();
map.put( "pressed", new AbstractAction() {
@Override
public void actionPerformed( ActionEvent e ) {
TristateCheckBox.this.iterateState();
}
});
map.put( "released", null );
SwingUtilities.replaceUIActionMap( this, map );
}
public TristateCheckBox( String text, TristateState initial ) {
this( text, null, initial );
}
public TristateCheckBox( String text ) {
this( text, null );
}
public TristateCheckBox() {
this( null );
}
public void setIndeterminate() {
getTristateModel().setIndeterminate();
}
public boolean isIndeterminate() {
return getTristateModel().isIndeterminate();
}
public void setState( TristateState state ) {
getTristateModel().setState( state );
}
public TristateState getState() {
return getTristateModel().getState();
}
@Override
public void setModel( ButtonModel newModel ) {
super.setModel( newModel );
// Listen for enable changes.
if ( model instanceof TristateButtonModel ) {
model.addChangeListener( enableListener );
}
}
@SuppressWarnings("unchecked")
public TristateButtonModel getTristateModel() {
return (TristateButtonModel)super.getModel();
}
/**
* No one may add mouse listeners, not even Swing!
*/
@Override
public void addMouseListener( MouseListener l ) {
}
private void iterateState() {
// Maybe do nothing at all?
if ( !super.getModel().isEnabled() ) return;
this.grabFocus();
// Iterate state.
getTristateModel().iterateState();
// Fire ActionEvent.
int modifiers = 0;
AWTEvent currentEvent = EventQueue.getCurrentEvent();
if ( currentEvent instanceof InputEvent ) {
modifiers = ((InputEvent)currentEvent).getModifiers();
}
else if ( currentEvent instanceof ActionEvent ) {
modifiers = ((ActionEvent)currentEvent).getModifiers();
}
fireActionPerformed(new ActionEvent( this, ActionEvent.ACTION_PERFORMED, this.getText(), System.currentTimeMillis(), modifiers ));
}
}