/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * PropertyNode.java * Copyright (C) 2006 Robert Jung * */ package weka.gui.ensembleLibraryEditor.tree; import weka.gui.GenericObjectEditor; import weka.gui.ensembleLibraryEditor.AddModelsPanel; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import java.beans.PropertyEditor; import java.util.Vector; /** * This node class represents individual parameters of generic objects * (in practice this means classifiers). So all direct children of a * classifier or other generic objects in the tree are going to be * property nodes. Note that these nodes do not themselves have editors * all editing in the user interface actaully happens in the child * nodes of this class that it controls. On top of creating these * child nodes and initializing them with the correct editing * configuration, this class is also responsible for obtaining all of * the possible values from the child nodes. * * @author Robert Jung (mrbobjung@gmail.com) * @version $Revision: 1.1 $ */ public class PropertyNode extends DefaultMutableTreeNode { /** for serialization */ private static final long serialVersionUID = 8179038568780212829L; /** this is a reference to the parent panel of the JTree which is * needed to display correctly anchored JDialogs*/ private final AddModelsPanel m_ParentPanel; /** the name of the node to be displayed */ private String m_Name; /** the node's tip text*/ private String m_ToolTipText; /** The propertyEditor created for the node, this is very useful in * figuring out exactly waht kind of child editor nodes to create */ private PropertyEditor m_PropertyEditor; /** a reference to the tree model is necessary to be able to add and * remove nodes in the tree */ private DefaultTreeModel m_TreeModel; /** a reference to the tree */ private JTree m_Tree; /** * The constructor initialiazes the member variables of this node, * Note that the "value" of this generic object is stored as the treeNode * user object. After the values are initialized the constructor calls * the addEditorNodes to create all the child nodes necessary to allow * users to specify ranges of parameter values meaningful for the * parameter that this node represents. * * @param tree the tree to use * @param panel the pabel * @param name the name * @param toolTipText the tooltip * @param value the actual value * @param pe the property editor */ public PropertyNode(JTree tree, AddModelsPanel panel, String name, String toolTipText, Object value, PropertyEditor pe) { super(value); m_Tree = tree; m_TreeModel = (DefaultTreeModel) m_Tree.getModel(); m_ParentPanel = panel; m_Name = name; m_ToolTipText = toolTipText; m_PropertyEditor = pe; addEditorNodes(name, toolTipText); } /** * getter for the tooltip text * * @return tooltip text */ public String getToolTipText() { return m_ToolTipText; } /** * getter for the name to be displayed for this node * * @return the name */ public String getName() { return m_Name; } /** * this returns the property editor that was provided for this object. This * propertyEditor object is initially chosen inside of the GenericObjectNode * updateTree() method if you are interested in where it comes from. * * @return the default editor for this node */ public PropertyEditor getPropertyEditor() { return m_PropertyEditor; } /** * returns a string representation * * @return a string representation */ public String toString() { return getClass().getName() + "[" + getUserObject().toString() + "]"; } /** * This method figures out what kind of parameter type this node * represents and then creates the appropriate set of child nodes * for editing. * * @param name the name * @param toolTipText the tooltip */ public void addEditorNodes(String name, String toolTipText) { Object value = getUserObject(); if (value instanceof Number) { NumberNode minNode = new NumberNode("min: ", (Number) value, NumberNode.NOT_ITERATOR, false, toolTipText); m_TreeModel.insertNodeInto(minNode, this, 0); Number one = null; try { one = minNode.getOneValue(); } catch (NumberClassNotFoundException e) { e.printStackTrace(); } NumberNode iteratorNode = new NumberNode("iterator: ", one, NumberNode.PLUS_EQUAL, true, toolTipText); m_TreeModel.insertNodeInto(iteratorNode, this, 1); NumberNode maxNode = new NumberNode("max: ", (Number) value, NumberNode.NOT_ITERATOR, true, toolTipText); m_TreeModel.insertNodeInto(maxNode, this, 2); } else if (m_PropertyEditor instanceof GenericObjectEditor) { GenericObjectNode classifierNode = new GenericObjectNode( m_ParentPanel, value, (GenericObjectEditor) m_PropertyEditor, toolTipText); m_TreeModel.insertNodeInto(classifierNode, this, 0); classifierNode.setTree(m_Tree); classifierNode.updateTree(); } else if (m_PropertyEditor.getTags() != null) { String selected = m_PropertyEditor.getAsText(); String tags[] = m_PropertyEditor.getTags(); if (tags != null) for (int i = 0; i < tags.length; i++) { CheckBoxNode checkBoxNode = new CheckBoxNode(tags[i], selected.equals(tags[i]), toolTipText); m_TreeModel.insertNodeInto(checkBoxNode, this, i); } } else { DefaultNode defaultNode = new DefaultNode(name, toolTipText, value, m_PropertyEditor); m_TreeModel.insertNodeInto(defaultNode, this, 0); } } /** * This method gets the range of values as specified by the * child editor nodes. * * @return all values */ public Vector getAllValues() { Vector values = new Vector(); //OK, there are four type of nodes that can branch off of a propertyNode DefaultMutableTreeNode child = (DefaultMutableTreeNode) m_TreeModel.getChild(this, 0); if (child instanceof GenericObjectNode) { //Here we let the generic object class handles this for us values = ((GenericObjectNode) child).getValues(); } else if (child instanceof DefaultNode) { //This is perhaps the easiest case. GenericNodes are only responsible //for a single value values.add(((DefaultNode) child).getUserObject()); } else if (child instanceof CheckBoxNode) { //Iterate through all of the children add their //value if they're selected int childCount = m_TreeModel.getChildCount(this); for (int i = 0; i < childCount; i++) { CheckBoxNode currentChild = (CheckBoxNode) m_TreeModel .getChild(this, i); if (currentChild.getSelected()) values.add(currentChild.getUserObject()); } } else if (child instanceof NumberNode) { //here we need to handle some weird cases for inpout validation NumberNode minChild = (NumberNode) m_TreeModel.getChild(this, 0); NumberNode iteratorChild = (NumberNode) m_TreeModel.getChild(this, 1); NumberNode maxChild = (NumberNode) m_TreeModel.getChild(this, 2); boolean ignoreIterator = false; try { if (iteratorChild.getSelected()) { //first we check to see if the min value is greater than the max value //if so then we gotta problem if (maxChild.lessThan(maxChild.getValue(), minChild.getValue())) { ignoreIterator = true; throw new InvalidInputException( "Invalid numeric input for node " + getName() + ": min > max. "); } //Make sure that the iterator value will actually "iterate" between the //min and max values if ((iteratorChild.getIteratorType() == NumberNode.PLUS_EQUAL) && (iteratorChild.lessThan( iteratorChild.getValue(), iteratorChild .getZeroValue()) || (iteratorChild .equals(iteratorChild.getValue(), iteratorChild.getZeroValue())))) { ignoreIterator = true; throw new InvalidInputException( "Invalid numeric input for node " + getName() + ": += iterator <= 0. "); } else if ((iteratorChild.getIteratorType() == NumberNode.TIMES_EQUAL) && (iteratorChild.lessThan( iteratorChild.getValue(), iteratorChild .getOneValue()) || (iteratorChild .equals(iteratorChild.getValue(), iteratorChild.getOneValue())))) { ignoreIterator = true; throw new InvalidInputException( "Invalid numeric input for node " + getName() + ": *= iterator <= 1. "); } } } catch (InvalidInputException e) { JRootPane parent = m_ParentPanel.getRootPane(); JOptionPane.showMessageDialog(parent, "Invalid Input: " + e.getMessage(), "Input error", JOptionPane.ERROR_MESSAGE); e.printStackTrace(); } catch (NumberClassNotFoundException e) { e.printStackTrace(); } if (!iteratorChild.getSelected() || ignoreIterator) { //easiest case - if we don't care about the Iterator then we just throw //in the min value along with the max value(if its selected) values.add(minChild.getUserObject()); if (maxChild.getSelected() && (!maxChild.getValue().equals(minChild.getValue()))) values.add(maxChild.getUserObject()); } else { //here we need to cycle through all of the values from min to max in //increments specified by the inrement value. Number current = minChild.getValue(); try { values.add(minChild.getValue()); do { Number newNumber = null; if (iteratorChild.getIteratorType() == NumberNode.PLUS_EQUAL) { newNumber = iteratorChild.addNumbers(iteratorChild.getValue(), current); } else if (iteratorChild.getIteratorType() == NumberNode.TIMES_EQUAL) { newNumber = iteratorChild.multiplyNumbers( iteratorChild.getValue(), current); } current = newNumber; if (iteratorChild .lessThan(current, maxChild.getValue()) && (!iteratorChild.equals(current, maxChild.getValue()))) { values.add(newNumber); } } while (iteratorChild.lessThan(current, maxChild.getValue()) && (!iteratorChild.equals(current, maxChild.getValue()))); if (maxChild.getSelected() && (!maxChild.getValue().equals(minChild.getValue()))) values.add(maxChild.getUserObject()); } catch (Exception e) { e.printStackTrace(); } } } return values; } /** * This method informs a child number node whether or not it is * allowed to be selected. NumberNodes are the only ones that need * to ask permission first. This simply makes sure that iterator * nodes can't be selected when the max node is not selected. * * @param node the node to check * @return true of the node can be selected */ public boolean canSelect(NumberNode node) { boolean permission = true; NumberNode iteratorChild = (NumberNode) m_TreeModel.getChild(this, 1); NumberNode maxChild = (NumberNode) m_TreeModel.getChild(this, 2); //the one case where we want to say no: you can not have an iterator //without a maximum value if (node == iteratorChild && (maxChild.getSelected() == false)) permission = false; return permission; } /** * informs a requesting child node whether or not it has permission * to be deselected. Note that only NumberNodes and CheckBoxNodes * are the only one's that have any notion of being deselected and * therefore should be the only one's calling this method. * * @param node the node to check * @return true if it can be de-selected */ public boolean canDeselect(DefaultMutableTreeNode node) { boolean permission = true; if (node instanceof NumberNode) { NumberNode iteratorChild = (NumberNode) m_TreeModel.getChild(this, 1); NumberNode maxChild = (NumberNode) m_TreeModel.getChild(this, 2); //the one case where we want to say no for number nodes: you can //not have an iterator without a maximum value if (node == maxChild && (iteratorChild.getSelected() == true)) permission = false; } else if (node instanceof CheckBoxNode) { //For check box nodes, we only want to say no if there's only one //box currently selected - because at least one box needs to be //checked. int totalSelected = 0; int childCount = m_TreeModel.getChildCount(this); for (int i = 0; i < childCount; i++) { CheckBoxNode currentChild = (CheckBoxNode) m_TreeModel .getChild(this, i); if (currentChild.getSelected()) totalSelected++; } if (totalSelected == 1) permission = false; } return permission; } }