Added tree UI classes (not used yet)
This commit is contained in:
parent
6ca3fad77b
commit
71aabb6205
12 changed files with 1377 additions and 0 deletions
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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++ ) );
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
142
src/main/java/net/vhati/modmanager/ui/tree/TristateCheckBox.java
Normal file
142
src/main/java/net/vhati/modmanager/ui/tree/TristateCheckBox.java
Normal 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 ));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue