/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.tools.workbench.framework.ui.chooser; import java.awt.Component; import java.awt.Dialog; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.SwingConstants; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.plaf.basic.BasicArrowButton; import javax.swing.tree.DefaultTreeSelectionModel; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import org.eclipse.persistence.tools.workbench.framework.action.AbstractFrameworkAction; import org.eclipse.persistence.tools.workbench.framework.context.WorkbenchContext; import org.eclipse.persistence.tools.workbench.framework.ui.dialog.AbstractDialog; import org.eclipse.persistence.tools.workbench.framework.uitools.SwingComponentFactory; import org.eclipse.persistence.tools.workbench.uitools.app.swing.TreeModelAdapter; import org.eclipse.persistence.tools.workbench.uitools.cell.DisplayableTreeCellRenderer; import org.eclipse.persistence.tools.workbench.utility.CollectionTools; import org.eclipse.persistence.tools.workbench.utility.iterators.EnumerationIterator; import org.eclipse.persistence.tools.workbench.utility.iterators.SingleElementIterator; /** * This dialog presents two trees to the user: * - the left hand tree holds the "available" classes * - the right hand tree holds the "selected" classes * * Typically the dialog opens with an empty "selected" classes tree. * The two arrow buttons are used to move the classes back * and forth between the two trees. Once the user presses * the OK button, clients of this dialog can retrieve the * "selected" classes by calling #selectedClassDescriptions(). * * The "classes" can be anything the resembles a class and can * be wrapped with a MetaClassAdapter (e.g. java.lang.Class, * java.lang.String, org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.ExternalClass). */ public class MultipleClassChooserDialog extends AbstractDialog { /** * we need to hold the trees, so we can tweak their behavior; * see #moveClassNodes() Rats! ~bjv */ private JTree availableTree; private JTree selectedTree; /** the repository that holds all the "class descriptions" */ private ClassDescriptionRepository repository; /** an adapter for displaying the "class descriptions" */ private ClassDescriptionAdapter adapter; /** internally, the "class descriptions" are stored in package pools */ private ClassDescriptionPackagePoolNode availableRootNode; private ClassDescriptionPackagePoolNode selectedRootNode; /** these are the "class descriptions" the user has to choose among (the left-hand tree) */ private TreeModel availableTreeModel; TreeSelectionModel availableTreeSelectionModel; /** these are the "class descriptions" the user has already chosen (the right-hand tree) */ private TreeModel selectedTreeModel; TreeSelectionModel selectedTreeSelectionModel; /** this will move "class descriptions" from the available list to the selected list */ private Action selectAction; /** this will move "class descriptions" from the selected list back to the available list */ private Action deselectAction; // ********** constructors ********** private MultipleClassChooserDialog(WorkbenchContext context) { super(context); } /** * use the default adapter, which expects the class descriptions to be strings */ public MultipleClassChooserDialog(WorkbenchContext context, ClassDescriptionRepository repository) { this(context, repository, Collections.EMPTY_LIST, DefaultClassDescriptionAdapter.instance()); } public MultipleClassChooserDialog(WorkbenchContext context, ClassDescriptionRepository repository, ClassDescriptionAdapter adapter) { this(context, repository, Collections.EMPTY_LIST, adapter); } public MultipleClassChooserDialog(WorkbenchContext context, ClassDescriptionRepository repository, Collection preSelectedMetaClasses, ClassDescriptionAdapter adapter) { this(context); this.repository = repository; this.adapter = adapter; this.initialize(preSelectedMetaClasses); } // repetition of constructors taking a Dialog owner private MultipleClassChooserDialog(WorkbenchContext context, Dialog owner) { super(context, owner); } /** * use the default adapter, which expects the class descriptions to be strings */ public MultipleClassChooserDialog(WorkbenchContext context, ClassDescriptionRepository repository, ClassDescriptionAdapter adapter, Dialog owner) { this(context, repository, Collections.EMPTY_LIST, adapter, owner); } public MultipleClassChooserDialog(WorkbenchContext context, ClassDescriptionRepository repository, Collection preSelectedMetaClasses, ClassDescriptionAdapter adapter, Dialog owner) { this(context, owner); this.repository = repository; this.adapter = adapter; this.initialize(preSelectedMetaClasses); } // ********** initialization ********** protected void initialize() { super.initialize(); this.setSize(800, 400); } protected void prepareToShow() { this.setLocationRelativeTo(this.getParent()); } protected String helpTopicId() { return "dialog.mutipleClassChooser"; } private Action buildRefreshAction() { return new AbstractFrameworkAction(getWorkbenchContext()) { protected void initialize() { this.initializeText("refresh"); } public void actionPerformed(ActionEvent e) { MultipleClassChooserDialog.this.refresh(); } }; } private void initialize(Collection preSelectedMetaClasses) { this.availableRootNode = this.buildAvailableRootNode(preSelectedMetaClasses); this.availableTreeModel = new TreeModelAdapter(this.availableRootNode); this.availableTreeSelectionModel = this.buildAvailableTreeSelectionModel(); this.selectedRootNode = this.buildSelectedRootNode(preSelectedMetaClasses); this.selectedTreeModel = new TreeModelAdapter(this.selectedRootNode); this.selectedTreeSelectionModel = this.buildSelectedTreeSelectionModel(); this.selectAction = this.buildSelectAction(); this.deselectAction = this.buildDeselectAction(); this.setTitle(resourceRepository().getString("selectClasses.title")); } private ClassDescriptionPackagePoolNode buildAvailableRootNode(Collection preSelectedMetaClasses) { return new ClassDescriptionPackagePoolNode("available", this.repository.classDescriptions(), this.adapter, preSelectedMetaClasses, getWorkbenchContext()); } private ClassDescriptionPackagePoolNode buildSelectedRootNode(Collection preSelectedMetaClasses) { return new ClassDescriptionPackagePoolNode("selected", preSelectedMetaClasses.iterator(), this.adapter, getWorkbenchContext()); } private TreeSelectionModel buildTreeSelectionModel(TreeSelectionListener listener) { TreeSelectionModel treeSelectionModel = new DefaultTreeSelectionModel(); treeSelectionModel.addTreeSelectionListener(listener); return treeSelectionModel; } private TreeSelectionModel buildAvailableTreeSelectionModel() { return this.buildTreeSelectionModel(new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { MultipleClassChooserDialog.this.availableTreeSelectionChanged(e); } }); } private TreeSelectionModel buildSelectedTreeSelectionModel() { return this.buildTreeSelectionModel(new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { MultipleClassChooserDialog.this.selectedTreeSelectionChanged(e); } }); } private Action buildSelectAction() { Action action = new AbstractFrameworkAction(getWorkbenchContext()) { public void actionPerformed(ActionEvent event) { MultipleClassChooserDialog.this.selectClassNodes(); } @Override protected void initialize() { super.initializeIcon("shuttle.right"); } }; action.setEnabled(false); return action; } private Action buildDeselectAction() { Action action = new AbstractFrameworkAction(getWorkbenchContext()) { public void actionPerformed(ActionEvent event) { MultipleClassChooserDialog.this.deselectClassNodes(); } @Override protected void initialize() { super.initializeIcon("shuttle.left"); } }; action.setEnabled(false); return action; } // ********** queries ********** private ClassDescriptionPackagePoolNode getAvailableRootNode() { return this.availableRootNode; } private ClassDescriptionPackagePoolNode getSelectedRootNode() { return this.selectedRootNode; } public Iterator selectedClassDescriptions() { return this.getSelectedRootNode().userClassDescriptions(); } // ********** behavior ********** void availableTreeSelectionChanged(TreeSelectionEvent e) { // only enable the select button when something is selected on the left this.selectAction.setEnabled(this.availableTreeSelectionModel.getSelectionCount() != 0); } void selectedTreeSelectionChanged(TreeSelectionEvent e) { // only enable the deselect button when something is selected on the right this.deselectAction.setEnabled(this.selectedTreeSelectionModel.getSelectionCount() != 0); } private Collection expandedPaths(JTree tree, Object root) { Enumeration stream = tree.getExpandedDescendants(new TreePath(root)); if (stream == null) { return Collections.EMPTY_LIST; } return CollectionTools.list(new EnumerationIterator(stream)); } private void moveClassNodes(ClassDescriptionPackagePoolNode source, ClassDescriptionPackagePoolNode target, TreeSelectionModel sourceSelectionModel, TreeSelectionModel targetSelectionModel, JTree targetTree) { // unsorted trees: if the root node is hidden and it does not have any children // (which is how the "selected" tree typically starts out), // then we need to programmatically force it to expand after adding children boolean expandTargetRootNode = target.isLeaf(); // sorted trees: try to preserve the expansion state of target tree Collection expandedPaths = this.expandedPaths(targetTree, target); TreePath[] selectedPaths = sourceSelectionModel.getSelectionPaths(); // if a package is selected, all its classes are considered selected also; // use a HashSet because there will be duplicates // if both the class and its containing package are selected Collection selectedClassNodes = new HashSet(); for (int i = selectedPaths.length; i-- > 0; ) { ClassDescriptionNodeContainer selectedNode = (ClassDescriptionNodeContainer) selectedPaths[i].getLastPathComponent(); selectedNode.addClassDescriptionNodesTo(selectedClassNodes); } Collection selectedPackageNodes = new HashSet(); for (Iterator stream = selectedClassNodes.iterator(); stream.hasNext(); ) { ClassDescriptionNode classNode = (ClassDescriptionNode) stream.next(); source.removeClassNode(classNode); target.addClassNode(classNode); selectedPackageNodes.add(classNode.getPackageNode()); } // expand any packages that had classes added to them for (Iterator stream = selectedPackageNodes.iterator(); stream.hasNext(); ) { ClassDescriptionPackageNode packageNode = (ClassDescriptionPackageNode) stream.next(); targetTree.expandPath(new TreePath(packageNode.path())); } // sorted trees: re-expand nodes that were closed during the update for (Iterator stream = expandedPaths.iterator(); stream.hasNext(); ) { targetTree.expandPath((TreePath) stream.next()); } // unsorted trees: force the hidden root node to expand, so its children are visible if (expandTargetRootNode) { targetTree.expandPath(new TreePath(target)); } // now, select the classes that were just moved TreePath[] newSelectedPaths = new TreePath[selectedClassNodes.size()]; Iterator stream = selectedClassNodes.iterator(); for (int i = 0; i < newSelectedPaths.length; i++) { ClassDescriptionNode classNode = (ClassDescriptionNode) stream.next(); newSelectedPaths[i] = new TreePath(classNode.path()); } targetSelectionModel.setSelectionPaths(newSelectedPaths); } void selectClassNodes() { this.moveClassNodes(this.getAvailableRootNode(), this.getSelectedRootNode(), this.availableTreeSelectionModel, this.selectedTreeSelectionModel, this.selectedTree); } void deselectClassNodes() { this.moveClassNodes(this.getSelectedRootNode(), this.getAvailableRootNode(), this.selectedTreeSelectionModel, this.availableTreeSelectionModel, this.availableTree); } void refresh() { this.repository.refreshClassDescriptions(); this.availableRootNode = this.buildAvailableRootNode(Collections.EMPTY_LIST); this.availableTreeModel = new TreeModelAdapter(this.availableRootNode); this.availableTree.setModel(this.availableTreeModel); this.selectedRootNode = this.buildSelectedRootNode(Collections.EMPTY_LIST); this.selectedTreeModel = new TreeModelAdapter(this.selectedRootNode); this.selectedTree.setModel(this.selectedTreeModel); } // ********** trees ********** private JTree buildTree(TreeModel treeModel, TreeSelectionModel treeSelectionModel) { JTree tree = SwingComponentFactory.buildTree(treeModel); tree.setSelectionModel(treeSelectionModel); tree.setCellRenderer(new DisplayableTreeCellRenderer()); tree.setRootVisible(false); tree.setShowsRootHandles(true); tree.setRowHeight(20); tree.setDoubleBuffered(true); return tree; } private JTree buildAvailableTree() { JTree tree = this.buildTree(this.availableTreeModel, this.availableTreeSelectionModel); tree.addMouseListener(this.buildDoubleClickSelect()); return tree; } /** * double-click short-cut for selecting nodes */ private MouseListener buildDoubleClickSelect() { return new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { // check whether anything is actually selected; // nothing will be selected if the user double-clicks on a blank part of the tree if (MultipleClassChooserDialog.this.availableTreeSelectionModel.getSelectionPaths() != null) { MultipleClassChooserDialog.this.selectClassNodes(); } } } }; } private JTree buildSelectedTree() { JTree tree = this.buildTree(this.selectedTreeModel, this.selectedTreeSelectionModel); tree.addMouseListener(this.buildDoubleClickDeselect()); return tree; } /** * double-click short-cut for deselecting nodes */ private MouseListener buildDoubleClickDeselect() { return new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { // check whether anything is actually selected; // nothing will be selected if the user double-clicks on a blank part of the tree if (MultipleClassChooserDialog.this.selectedTreeSelectionModel.getSelectionPaths() != null) { MultipleClassChooserDialog.this.deselectClassNodes(); } } } }; } // ********** layout ********** protected Component buildMainPanel() { JPanel mainPanel = new JPanel(new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); // available packages and classes label... JLabel availableLabel = new JLabel(resourceRepository().getString("availablePackages/classes")); availableLabel.setDisplayedMnemonic(resourceRepository().getMnemonic("availablePackages/classes")); constraints.gridx = 0; constraints.gridy = 0; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.weightx = 0; constraints.weighty = 0; constraints.anchor = GridBagConstraints.LINE_START; constraints.fill = GridBagConstraints.NONE; constraints.insets = new Insets(5, 5, 0, 0); mainPanel.add(availableLabel, constraints); // ...and tree this.availableTree = this.buildAvailableTree(); helpManager().addTopicID(this.availableTree, helpTopicId() + ".available"); JScrollPane availablePane = new JScrollPane(this.availableTree); availablePane.setPreferredSize(new Dimension(200, 100)); availablePane.setMinimumSize(new Dimension(200, 100)); constraints.gridx = 0; constraints.gridy = 1; constraints.gridwidth = 1; constraints.gridheight = 3; // make it 3, so we can swap in the test button constraints.weightx = 1; constraints.weighty = 1; constraints.fill = GridBagConstraints.BOTH; constraints.anchor = GridBagConstraints.CENTER; constraints.insets = new Insets(1, 5, 0, 0); mainPanel.add(availablePane, constraints); availableLabel.setLabelFor(this.availableTree); // selected packages and classes label... JLabel selectedLabel = new JLabel(resourceRepository().getString("selectedClasses")); selectedLabel.setDisplayedMnemonic(resourceRepository().getMnemonic("selectedClasses")); constraints.gridx = 2; constraints.gridy = 0; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.weightx = 0; constraints.weighty = 0; constraints.fill = GridBagConstraints.NONE; constraints.anchor = GridBagConstraints.LINE_START; constraints.insets = new Insets(5, 5, 0, 5); mainPanel.add(selectedLabel, constraints); // ...and tree this.selectedTree = this.buildSelectedTree(); helpManager().addTopicID(this.selectedTree, helpTopicId() + ".selected"); JScrollPane selectedPane = new JScrollPane(this.selectedTree); selectedPane.setPreferredSize(new Dimension(200, 100)); selectedPane.setMinimumSize(new Dimension(200, 100)); constraints.gridx = 2; constraints.gridy = 1; constraints.gridwidth = 1; constraints.gridheight = 3; // make it 3, so we can swap in the test button constraints.weightx = 1; constraints.weighty = 1; constraints.fill = GridBagConstraints.BOTH; constraints.anchor = GridBagConstraints.CENTER; constraints.insets = new Insets(1, 5, 0, 5); mainPanel.add(selectedPane, constraints); selectedLabel.setLabelFor(this.selectedTree); // select button JButton selectButton = new JButton(this.selectAction); selectButton.setToolTipText(resourceRepository().getString("addSelectedClassesToList")); constraints.gridx = 1; constraints.gridy = 1; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.weightx = 0; constraints.weighty = 0; constraints.fill = GridBagConstraints.NONE; constraints.anchor = GridBagConstraints.CENTER; constraints.insets = new Insets(1, 5, 0, 0); mainPanel.add(selectButton, constraints); // deselect button JButton deselectButton = new JButton(this.deselectAction); deselectButton.setToolTipText(resourceRepository().getString("removeSelectedClassesFromList")); constraints.gridx = 1; constraints.gridy = 2; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.weightx = 0; constraints.weighty = 0; constraints.fill = GridBagConstraints.NONE; constraints.anchor = GridBagConstraints.PAGE_START; constraints.insets = new Insets(5, 5, 0, 0); mainPanel.add(deselectButton, constraints); // test button // Action testAction = new AbstractAction() { // public void actionPerformed(ActionEvent event) { // // mess around with the model to see if the tree updates properly // ClassDescriptionPackageNode selectedRootNode = MultipleClassChooserDialog.this.getSelectedRootNode(); // Collection expandedPaths = MultipleClassChooserDialog.this.expandedPaths(selectedTree, selectedRootNode); // Iterator selectedClassNodes = MultipleClassChooserDialog.this.getSelectedRootNode().classNodes(); // if (selectedClassNodes.hasNext()) { // ClassNode classNode = (ClassNode) selectedClassNodes.next(); // classNode.setUserMetaClass(classNode.packageName() + '.' + "AAAAA"); // } // Iterator selectedPackageNodes = MultipleClassChooserDialog.this.getSelectedRootNode().packageNodes(); // if (selectedPackageNodes.hasNext()) { // PackageNode packageNode = (PackageNode) selectedPackageNodes.next(); // packageNode.setName("XXXXX"); // } // for (Iterator stream = expandedPaths.iterator(); stream.hasNext(); ) { // selectedTree.expandPath((TreePath) stream.next()); // } // } // }; // JButton testButton = new JButton(testAction); // testButton.setText("test"); // testButton.setMinimumSize(buttonSize); // testButton.setMaximumSize(buttonSize); // testButton.setPreferredSize(buttonSize); // // constraints.gridx = 1; // constraints.gridy = 3; // constraints.gridwidth = 1; // constraints.gridheight = 1; // constraints.weightx = 0; // constraints.weighty = 1; // constraints.fill = GridBagConstraints.NONE; // constraints.anchor = GridBagConstraints.PAGE_START; // constraints.insets = new Insets(5, 5, 0, 0); // // this.getMainPanel().add(testButton, constraints); return mainPanel; } protected Iterator buildCustomActions() { return new SingleElementIterator(this.buildRefreshAction()); } // ******************** static helper method ******************** public static void gc() { ClassChooserDialog.gc(); } }