/*
Copyright (C) 2003 EBI, GRL
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.
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.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ensembl.mart.explorer;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import org.ensembl.mart.lib.config.DatasetConfig;
/**
* Abstract generic tree option widget. It presents the user with set of
* options organised as a tree.
*
* <p>
* Developers who wish to extend this component should implement the update()
* method. This is called after the change button is pressed but before the
* menu is displayed. This enables the available options in the menu to be
* updated before it is displayed. Typical usage: <code>
* <ol>
* <li>clear();
* <li>construct tree from the rootNode down.
* </ol>
* </code>
* </p>
*/
public abstract class PopUpTreeCombo extends JPanel {
private String lastLabel;
private Object lastObject;
private JMenuItem oldItem;
// --- state
protected LabelledTreeNode rootNode = new LabelledTreeNode(null, null);
private List listeners = new ArrayList();
// --- UI
private JButton button = new JButton("change");
private JTextField selectedTextField = new JTextField(30);
private JMenuBar treeMenu = new JMenuBar();
private JPopupMenu firstTier = new JPopupMenu();
private Feedback feedback = new Feedback(this);
private JLabel jlabel;
public PopUpTreeCombo(String label) {
super();
createUI(label);
}
public void addActionListener(ActionListener listener) {
listeners.add(listener);
}
public void removeActionListener(ActionListener listener) {
listeners.remove(listener);
}
/**
* To be overidden be implementing classes who wish to changed the model
* before the tree is displayed.
*/
public abstract void update();
private void createUI(String label) {
selectedTextField.setEditable(false);
selectedTextField.setMaximumSize(new Dimension(400, 27));
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
showTree();
}
});
// make the menu appear beneath the row of components
// containing the label, textField and button when displayed.
treeMenu.setMaximumSize(new Dimension(0, 100));
treeMenu.add(firstTier);
jlabel = new JLabel(label);
add(jlabel);
add(treeMenu);
add(button);
add(selectedTextField);
}
public void showTree() {
update();
updateMenu();
final Component parent = this;
if (rootNode.getChildCount() > 0) {
new Thread() {
public void run() {
try {
while (!parent.isShowing())
Thread.sleep(100);
// Most of these are "apparently"
// pointless lines
// of code but are needed to make
// the popup menu appear in the
// correct place on screen.
firstTier.updateUI();
firstTier.setVisible(true);
firstTier.setVisible(false);
firstTier.updateUI();
firstTier.show(parent, button.getX()
+ button.getWidth() + 5, button.getY()
+ button.getHeight() + 5);
firstTier.repaint();
} catch (InterruptedException e) {
// do nothing
}
}
}.start();
}
}
/**
* @param displayName
* display name of the selected datasetConfig.
*/
private void doSelect(String label, Object o) {
if (label == lastLabel && o == lastObject)
return;
lastLabel = label;
lastObject = o;
selectedTextField.setText(label);
ActionEvent event = new ActionEvent(this, 0, null);
for (Iterator iter = listeners.iterator(); iter.hasNext();) {
ActionListener l = (ActionListener) iter.next();
l.actionPerformed(event);
}
}
private JMenuItem noneMenuItem = new JMenuItem("None");
private Map datasetNameToDatasetConfig = new HashMap();
/**
* Unpacks the datasetConfigs into several sets and maps that enable easy
* lookup of information.
*
* displayName -> shortName datasetName -> datasetConfig | List-of-datasetConfigs
* displayName -> datasetConfig | List-of-datasetConfigs
*
* @param datasetConfigs
* dataset configs, should be sorted by displayNames.
*/
private void unpack(DatasetConfig[] datasetConfigs) {
Set availableDisplayNames = new HashSet();
Set availableDatasetNames = new HashSet();
Map displayNameToDatasetConfig = new HashMap();
Map displayNameToShortName = new HashMap();
if (datasetConfigs == null)
return;
Set clashingDisplayNames = new HashSet();
Set clashingDatasetNames = new HashSet();
for (int i = 0; i < datasetConfigs.length; i++) {
DatasetConfig config = datasetConfigs[i];
String displayName = config.getDisplayName();
if (availableDisplayNames.contains(displayName))
clashingDisplayNames.add(config);
else
availableDisplayNames.add(displayName);
String datasetName = config.getInternalName();
if (availableDatasetNames.contains(datasetName))
clashingDatasetNames.add(config);
else
availableDatasetNames.add(datasetName);
String[] elements = displayName.split("__");
String shortName = elements[elements.length - 1];
displayNameToShortName.put(displayName, shortName);
}
for (int i = 0; i < datasetConfigs.length; i++) {
DatasetConfig config = datasetConfigs[i];
String displayName = config.getDisplayName();
if (clashingDisplayNames.contains(config)) {
List list = (List) displayNameToDatasetConfig.get(displayName);
if (list == null) {
list = new LinkedList();
displayNameToDatasetConfig.put(displayName, list);
}
list.add(config);
} else {
displayNameToDatasetConfig.put(displayName, config);
}
String datasetName = config.getInternalName();
if (clashingDatasetNames.contains(config)) {
List list = (List) datasetNameToDatasetConfig.get(datasetName);
if (list == null) {
list = new LinkedList();
datasetNameToDatasetConfig.put(datasetName, list);
}
list.add(config);
} else {
datasetNameToDatasetConfig.put(datasetName, config);
}
}
}
/**
* Update the menu to reflect the model.
*/
private void updateMenu() {
firstTier.removeAll();
int n = rootNode.getChildCount();
for (int i = 0; i < n; i++)
addToMenu(firstTier, (LabelledTreeNode) rootNode.getChildAt(i));
}
/**
* @param rootNode
*/
private void addToMenu(JMenu menu, final LabelledTreeNode node) {
if (node.getChildCount() == 0) {
JMenuItem item = new JMenuItem(node.getLabel());
menu.add(item);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
doSelect(node.getLabel(), node.getUserObject());
}
});
} else {
JMenu m = new JMenu(node.getLabel());
menu.add(m);
int n = node.getChildCount();
for (int i = 0; i < n; i++)
addToMenu(m, (LabelledTreeNode) node.getChildAt(i));
}
}
private void addToMenu(JPopupMenu menu, final LabelledTreeNode node) {
if (node.getChildCount() == 0) {
JMenuItem item = new JMenuItem(node.getLabel());
menu.add(item);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
doSelect(node.getLabel(), node.getUserObject());
}
});
} else {
JMenu m = new JMenu(node.getLabel());
menu.add(m);
int n = node.getChildCount();
for (int i = 0; i < n; i++)
addToMenu(m, (LabelledTreeNode) node.getChildAt(i));
}
}
public static void main(String[] args) {
final PopUpTreeCombo pu = new PopUpTreeCombo("Test") {
public void update() {
// Create a sample tree for rendering
rootNode.removeAllChildren();
rootNode.add(new LabelledTreeNode("a", "a"));
rootNode.add(new LabelledTreeNode("b", "b"));
LabelledTreeNode c = new LabelledTreeNode("c", "c");
c.add(new LabelledTreeNode("c1", "c1"));
rootNode.add(c);
rootNode.add(new LabelledTreeNode("d", "d"));
}
};
// test the listener support
pu.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Selection changed to : "
+ pu.getSelectedLabel());
}
});
Box p = Box.createVerticalBox();
p.add(pu);
JFrame f = new JFrame(PopUpTreeCombo.class.getName() + " (Test Frame)");
f.getContentPane().add(p);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//f.setSize(250, 100);
f.pack();
f.setVisible(true);
}
/**
* @return selected label, null if none selected.
*/
public String getSelectedLabel() {
return lastLabel;
}
/**
* @return selected user object, null if none set.
*/
public Object getSelectedUserObject() {
return lastObject;
}
public void setSelected(LabelledTreeNode node) {
doSelect(node.getLabel(), node.getUserObject());
}
}