/* * This library is part of OpenCms - * the Open Source Content Management System * * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com) * * 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. * * For further information about Alkacon Software GmbH, please see the * company website: http://www.alkacon.com * * For further information about OpenCms, please see the * project website: http://www.opencms.org * * 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.opencms.util.ant; import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.StringTokenizer; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.border.Border; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; /** * This is a highly configurable Swing GUI dialog for selection. * <p> * * @since 6.1.6 * * @see CmsAntTaskSelectionPrompt */ public class CmsAntTaskSelectionTreeDialog extends JDialog implements ActionListener { /** Constant for border width. */ private static final int C_BORDER_SIZE = 10; /** Serial version UID required for safe serialization. */ private static final long serialVersionUID = -8439685952987222098L; /** Aborted flag. */ protected boolean m_aborted; /** The list of all module names. * */ private List m_allModuleList; /** Border. */ private final Border m_border = BorderFactory.createEmptyBorder(C_BORDER_SIZE, C_BORDER_SIZE, 0, C_BORDER_SIZE); /** Panel for buttons. */ private final JPanel m_buttons = new JPanel(); /** Cancel button. */ private final JButton m_cancel = new JButton("Cancel"); /** Main Panel. */ private final JPanel m_content = new JPanel(); /** Label for prompt. */ private JLabel m_label; /** Ok button. */ private final JButton m_ok = new JButton("Ok"); /** Associated ant task. */ private final CmsAntTaskSelectionTreePrompt m_promptTask; /** Select all button. */ private final JButton m_selAll = new JButton("All"); /** Select none button. */ private final JButton m_selNone = new JButton("None"); /** The tree for selection of sets of modudles. . */ private JTree m_tree; /** * Default Constructor. * <p> * * @param promptTask the <code>{@link CmsAntTaskSelectionPrompt}</code> object. * <p> */ public CmsAntTaskSelectionTreeDialog(CmsAntTaskSelectionTreePrompt promptTask) { super((JFrame)null, promptTask.getTitle(), true); m_promptTask = promptTask; m_label = new JLabel(m_promptTask.getPrompt()); addWindowListener(new WindowAdapter() { public void windowClosed(WindowEvent e) { setVisible(false); } }); getRootPane().setDefaultButton(m_ok); Container contentPane = getContentPane(); contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); m_label.setBorder(m_border); if (!m_promptTask.isSingleSelection()) { JPanel p1 = new JPanel(); p1.add(new JLabel("Select: ")); m_selAll.addActionListener(this); p1.add(m_selAll); m_selNone.addActionListener(this); p1.add(m_selNone); JPanel p = new JPanel(new BorderLayout()); p.add(m_label, BorderLayout.NORTH); p.add(p1, BorderLayout.SOUTH); contentPane.add(p); } else { getContentPane().add(m_label); } JScrollPane scrollpane = new JScrollPane(m_content); scrollpane.setBorder(m_border); scrollpane.setPreferredSize(new Dimension(300, 800)); // parse the String-list to a clean list as it is not only used for tree-creation but for // tree-selection determination too: this.parseModuleList(); TreeModel treeModel = createTree(); m_tree = new SelectionTree(); m_tree.setModel(treeModel); m_tree.setRootVisible(false); m_tree.setShowsRootHandles(true); expandTree(new TreePath(treeModel.getRoot())); selectDefaultNodes((DefaultMutableTreeNode)treeModel.getRoot(), "", new TreePath(treeModel.getRoot())); // layout: let the tree start in upper left edge instead of being centered within the pane // - it would start to move as it expands and changes bounds. m_content.setLayout(new GridLayout(1, 1)); m_content.add(m_tree); m_content.setBorder(BorderFactory.createLoweredBevelBorder()); contentPane.add(scrollpane); m_buttons.setBorder(BorderFactory.createEmptyBorder( C_BORDER_SIZE, C_BORDER_SIZE, C_BORDER_SIZE / 2, C_BORDER_SIZE)); m_ok.addActionListener(this); m_buttons.add(m_ok); m_cancel.addActionListener(this); m_buttons.add(m_cancel); getContentPane().add(m_buttons, BorderLayout.SOUTH); pack(); } /** * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ public void actionPerformed(ActionEvent e) { Object source = e.getSource(); if (source == m_ok) { m_aborted = false; setVisible(false); } else if (source == m_cancel) { m_aborted = true; setVisible(false); } else if (source == m_selAll) { m_tree.setSelectionPath(new TreePath(m_tree.getModel().getRoot())); } else if (source == m_selNone) { m_tree.clearSelection(); } m_tree.invalidate(); // m_tree.cancelEditing(); m_tree.repaint(); } /** * Returns <code>null</code> if the dialog was canceled, or a list of selected items if not. * <p> * * @return the user selection */ public String getSelection() { center(); setVisible(true); // Ret is the complete String with all modules separated by ... look at that constant StringBuffer ret = new StringBuffer(); // TODO query the selected paths for all subnodes. TreePath[] pathArr = m_tree.getSelectionPaths(); // avoid NPE but skip loop: if (pathArr == null) { pathArr = new TreePath[0]; } TreePath path; StringBuffer pathString; DefaultMutableTreeNode node; // pathString is the path string of the selected TreePath from the tree. // it may be a leaf (single selection) or not. In the latter case iteration // continues to all subnodes to add all reachable leafs as module names. // Furthermore every non-leaf-node may be a module name like: org.opencms.workplace and // org.opencms.workplace.tools.content... for (int i = 0; i < pathArr.length; i++) { pathString = new StringBuffer(); path = pathArr[i]; // build the path string to the selected path: Object[] entries = path.getPath(); // skip "root" for (int j = 1; j < entries.length; j++) { pathString.append(entries[j]); if (j < entries.length - 1) { pathString.append("."); } } node = (DefaultMutableTreeNode)path.getLastPathComponent(); if (node.isLeaf()) { ret.append(pathString.toString()); ret.append(CmsAntTaskSelectionTreePrompt.LIST_SEPARATOR); } else { // first look, wether this is already a module, even if not leaf, (e.g. // org.opencms.workplace <-> org.opencms.workplace.tools.accounts...) if (m_allModuleList.contains(pathString.toString())) { ret.append(pathString.toString()); ret.append(CmsAntTaskSelectionTreePrompt.LIST_SEPARATOR); } else { // nop } // search all leaf nodes and append subpaths: ret.append(getSubpaths(node, pathString.toString())); } } dispose(); if (m_aborted || (ret.toString().trim().length() < CmsAntTaskSelectionTreePrompt.LIST_SEPARATOR.length())) { return null; } else { return ret.toString(); } } /** * Centers the dialog on the screen. * <p> * * If the size of the dialog exceeds that of the screen, then the size of the dialog is reset to * the size of the screen. * <p> */ private void center() { Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); Dimension window = getSize(); // ensure that no parts of the dialog will be off-screen int height = window.height; int width = window.width; if (window.height > screen.height) { window.height = screen.height; height = screen.height - 50; width = width + 50; } if (window.width > screen.width) { window.width = screen.width; } int xCoord = (screen.width / 2 - window.width / 2); int yCoord = (screen.height / 2 - window.height / 2); setLocation(xCoord, yCoord); setSize(width, height); } private TreeModel createTree() { // for every entry: cut into paths, build a tree path that collates equal paths. DefaultMutableTreeNode root = new DefaultMutableTreeNode("root"); TreeModel tm = new DefaultTreeModel(root); Iterator itModules = m_allModuleList.iterator(); StringTokenizer itPath; Enumeration childEnum; String pathElement; DefaultMutableTreeNode node, child; boolean found = false; while (itModules.hasNext()) { itPath = new StringTokenizer((String)itModules.next(), "."); node = root; while (itPath.hasMoreTokens()) { // is this node already there? pathElement = itPath.nextToken(); childEnum = node.children(); found = false; while (childEnum.hasMoreElements()) { child = (DefaultMutableTreeNode)childEnum.nextElement(); if (pathElement.equals(child.getUserObject())) { // found node for path String // reuse old path, descend and continue with next path element. node = child; found = true; break; } } if (!found) { // did not break, node was not found child = new DefaultMutableTreeNode(); child.setUserObject(pathElement); node.add(child); node = child; } } } return tm; } /** * Recursive depth first traversal that stops at expansion level and expands those paths. * <p> * * @param treePath the current path in recursion */ private void expandTree(TreePath treePath) { if (treePath.getPathCount() == m_promptTask.getExpansionLevels()) { m_tree.expandPath(treePath); } else { DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)treePath.getLastPathComponent(); Enumeration children = treeNode.children(); while (children.hasMoreElements()) { expandTree(treePath.pathByAddingChild(children.nextElement())); } } } private String getSubpaths(TreeNode node, String parentPath) { Enumeration children = node.children(); TreeNode child; String path = parentPath; StringBuffer ret = new StringBuffer(); while (children.hasMoreElements()) { child = (TreeNode)children.nextElement(); if (parentPath.length() == 0) { path = child.toString(); } else { path = parentPath + "." + child.toString(); } if (child.isLeaf()) { ret.append(path); ret.append(CmsAntTaskSelectionTreePrompt.LIST_SEPARATOR); } else { ret.append(getSubpaths(child, path)); } } return ret.toString(); } /** * Parses the string of comma separated module names obtained from the ant script into the * internal list of module names for better access in several locations. * <p> * */ private void parseModuleList() { m_allModuleList = new LinkedList(); StringTokenizer itPaths = new StringTokenizer( m_promptTask.getAllValues(), CmsAntTaskSelectionPrompt.LIST_SEPARATOR); String token; while (itPaths.hasMoreElements()) { token = itPaths.nextToken().trim(); m_allModuleList.add(token); } } /** * Recursivley selects the nodes that are qualified by the default selections. * * @param node the current Node * @param path the node path * @param treePath the tree path */ private void selectDefaultNodes(DefaultMutableTreeNode node, String path, TreePath treePath) { // allow root property to be set: String defaultString = m_promptTask.getDefaultValue(); if ("root".equalsIgnoreCase(defaultString.trim())) { if (node == m_tree.getModel().getRoot()) { m_tree.setSelectionPath(treePath); } } else { StringTokenizer tokenizer = new StringTokenizer(defaultString, CmsAntTaskSelectionTreePrompt.LIST_SEPARATOR); String defaultEntry; while (tokenizer.hasMoreTokens()) { defaultEntry = tokenizer.nextToken(); // don't print in recursions for path if (node.getLevel() == 0) { System.out.println("Preselection: " + defaultEntry); } if (defaultEntry.equals(path)) { m_tree.addSelectionPath(treePath); return; } } Enumeration children = node.children(); DefaultMutableTreeNode subNode; String subPath; while (children.hasMoreElements()) { subPath = path; if (subPath.length() != 0) { subPath += "."; } subNode = (DefaultMutableTreeNode)children.nextElement(); subPath += subNode.toString(); selectDefaultNodes(subNode, subPath, treePath.pathByAddingChild(subNode)); } } } }