/* GanttProject is an opensource project management tool. License: GPL3 Copyright (C) 2011 Dmitry Barashev, GanttProject Team 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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package net.sourceforge.ganttproject; import biz.ganttproject.core.table.ColumnList; import com.google.common.base.Predicate; import com.google.common.collect.Maps; import net.sourceforge.ganttproject.action.GPAction; import net.sourceforge.ganttproject.chart.Chart; import net.sourceforge.ganttproject.chart.overview.ToolbarBuilder; import net.sourceforge.ganttproject.gui.TreeUiFacade; import net.sourceforge.ganttproject.gui.UIUtil; import net.sourceforge.ganttproject.util.collect.Pair; import org.jdesktop.swingx.JXTreeTable; import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode; import org.jdesktop.swingx.treetable.DefaultTreeTableModel; import org.jdesktop.swingx.treetable.MutableTreeTableNode; import org.jdesktop.swingx.treetable.TreeTableNode; import javax.swing.*; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.event.*; import java.util.*; import java.util.List; /** * * @author dbarashev (Dmitry Barashev) * * @param <ModelObject> * @param <TreeTableClass> * @param <TreeTableModelClass> */ public abstract class TreeTableContainer<ModelObject, TreeTableClass extends GPTreeTableBase, TreeTableModelClass extends DefaultTreeTableModel> extends JPanel implements TreeUiFacade<ModelObject> { private final TreeTableClass myTreeTable; private final TreeTableModelClass myTreeTableModel; private GPAction myNewAction; private GPAction myPropertiesAction; private GPAction myDeleteAction; private class ExpandCollapseAction extends GPAction { ExpandCollapseAction() { super("tree.expand"); } @Override public void actionPerformed(ActionEvent e) { TreePath currentSelection = getTree().getTreeSelectionModel().getSelectionPath(); if (currentSelection != null) { if (getTree().isCollapsed(currentSelection)) { getTree().expandPath(currentSelection); } else { getTree().collapsePath(currentSelection); } } } } private class ExpandAllAction extends GPAction { ExpandAllAction() { super("tree.expandAll"); } @Override public void actionPerformed(ActionEvent e) { TreePath currentSelection = getTree().getTreeSelectionModel().getSelectionPath(); if (currentSelection != null) { expandAll(currentSelection); } } } private class CollapseAllAction extends GPAction { CollapseAllAction() { super("tree.collapseAll"); } @Override public void actionPerformed(ActionEvent e) { TreePath currentSelection = getTree().getTreeSelectionModel().getSelectionPath(); if (currentSelection != null) { collapseAll(currentSelection); } } } public TreeTableContainer(Pair<TreeTableClass, TreeTableModelClass> tableAndModel) { super(new BorderLayout()); myTreeTableModel = tableAndModel.second(); myTreeTable = tableAndModel.first(); myTreeTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); getTreeTable().setBackground(new Color(1.0f, 1.0f, 1.0f)); myTreeTable.getTree().getTreeTableModel().addTreeModelListener(new ChartUpdater()); GPAction[] nodeActions = new GPAction[] {new ExpandCollapseAction(), new ExpandAllAction(), new CollapseAllAction()}; for (GPAction nodeAction : nodeActions) { for (KeyStroke ks : GPAction.getAllKeyStrokes(nodeAction.getID())) { UIUtil.pushAction(myTreeTable, false, ks, nodeAction); } } ExpandAllAction expandAll = new ExpandAllAction(); this.addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { getTreeTable().getTable().requestFocusInWindow(); } }); } }); MouseListener ml = new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { handlePopupTrigger(e); } @Override public void mousePressed(MouseEvent e) { handlePopupTrigger(e); } @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) { if (getTreeTable().getTable().getSelectedRow() != -1) { e.consume(); getPropertiesAction().actionPerformed(null); } return; } handlePopupTrigger(e); } }; getTreeTable().addMouseListener(ml); getTree().addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { onSelectionChanged(Arrays.asList(getSelectedNodes())); } }); } public void expandAll(TreePath root) { getTree().expandPath(root); TreeTableNode node = (TreeTableNode) root.getLastPathComponent(); for (int i = 0; i < node.getChildCount(); i++) { expandAll(root.pathByAddingChild(node.getChildAt(i))); } } private void collapseAll(TreePath root) { TreeTableNode node = (TreeTableNode) root.getLastPathComponent(); for (int i = 0; i < node.getChildCount(); i++) { collapseAll(root.pathByAddingChild(node.getChildAt(i))); } getTree().collapsePath(root); } protected abstract void init(); protected void onSelectionChanged(List<DefaultMutableTreeTableNode> selection) { } protected abstract void handlePopupTrigger(MouseEvent e); protected JXTreeTable getTree() { return getTreeTable().getTree(); } protected TreeTableClass getTreeTable() { return myTreeTable; } protected TreeTableModelClass getTreeModel() { return myTreeTableModel; } @Override public Component getTreeComponent() { return this; } @Override public ColumnList getVisibleFields() { return myTreeTable.getVisibleFields(); } @Override public boolean isExpanded(ModelObject modelObject) { MutableTreeTableNode treeNode = getNode(modelObject); return treeNode == null ? false : !myTreeTable.getTree().isCollapsed(TreeUtil.createPath(treeNode)); } @Override public void setExpanded(ModelObject modelObject, boolean value) { MutableTreeTableNode treeNode = getNode(modelObject); if (treeNode != null) { if (value) { myTreeTable.getTree().expandPath(TreeUtil.createPath(treeNode)); } else { myTreeTable.getTree().collapsePath(TreeUtil.createPath(treeNode)); } } } @Override public void applyPreservingExpansionState(ModelObject rootObject, Predicate<ModelObject> callable) { MutableTreeTableNode rootNode = getNode(rootObject); List<MutableTreeTableNode> subtree = TreeUtil.collectSubtree(rootNode); Collections.reverse(subtree); LinkedHashMap<ModelObject, Boolean> states = Maps.newLinkedHashMap(); for (MutableTreeTableNode node : subtree) { int row = myTreeTable.getTree().getRowForPath(TreeUtil.createPath(node)); states.put((ModelObject)node.getUserObject(), myTreeTable.getTree().isExpanded(row)); } callable.apply(rootObject); for (Map.Entry<ModelObject, Boolean> state : states.entrySet()) { setExpanded(state.getKey(), state.getValue()); } } @Override public boolean isVisible(ModelObject modelObject) { MutableTreeTableNode node = getNode(modelObject); if (node == null) { return false; } return getTreeTable().getTree().isVisible(TreeUtil.createPath(node)); } @Override public void makeVisible(ModelObject modelObject) { MutableTreeTableNode node = getNode(modelObject); if (node != null) { getTree().scrollPathToVisible(TreeUtil.createPath(node)); } } public void commitIfEditing() { if (myTreeTable.getTable().isEditing()) { myTreeTable.getTable().getCellEditor().stopCellEditing(); } } public int getRowHeight() { return myTreeTable.getTable().getRowHeight(); } protected MutableTreeTableNode getNode(ModelObject modelObject) { for (MutableTreeTableNode nextNode : TreeUtil.collectSubtree(getRootNode())) { if (nextNode.getUserObject() != null && nextNode.getUserObject().equals(modelObject)) { return nextNode; } } return null; } protected DefaultMutableTreeTableNode getSelectedNode() { TreePath currentSelection = getTree().getTreeSelectionModel().getSelectionPath(); return (currentSelection == null) ? null : (DefaultMutableTreeTableNode) currentSelection.getLastPathComponent(); } public DefaultMutableTreeTableNode[] getSelectedNodes() { TreePath[] currentSelection = getTree().getTreeSelectionModel().getSelectionPaths(); if (currentSelection == null || currentSelection.length == 0) { return new DefaultMutableTreeTableNode[0]; } DefaultMutableTreeTableNode[] result = new DefaultMutableTreeTableNode[currentSelection.length]; for (int i = 0; i < currentSelection.length; i++) { result[i] = (DefaultMutableTreeTableNode) currentSelection[i].getLastPathComponent(); } return result; } protected abstract DefaultMutableTreeTableNode getRootNode(); protected abstract Chart getChart(); private class ChartUpdater implements TreeModelListener { @Override public void treeNodesChanged(TreeModelEvent e) { getChart().reset(); } @Override public void treeNodesInserted(TreeModelEvent e) { getChart().reset(); } @Override public void treeNodesRemoved(TreeModelEvent e) { getChart().reset(); } @Override public void treeStructureChanged(TreeModelEvent e) { getChart().reset(); } } @Override public GPAction getNewAction() { return myNewAction; } @Override public GPAction getPropertiesAction() { return myPropertiesAction; } @Override public GPAction getDeleteAction() { return myDeleteAction; } protected void setArtefactActions(GPAction newAction, GPAction propertiesAction, GPAction deleteAction) { myNewAction = newAction; myPropertiesAction = propertiesAction; myDeleteAction = deleteAction; } public abstract void addToolbarActions(ToolbarBuilder builder); }