/* GanttProject is an opensource project management tool. License: GPL3 Copyright (C) 2002-2011 Thomas Alexandre, 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.Lists; import com.google.common.collect.Maps; import net.sourceforge.ganttproject.action.GPAction; import net.sourceforge.ganttproject.action.task.*; import net.sourceforge.ganttproject.chart.Chart; import net.sourceforge.ganttproject.chart.VisibleNodesFilter; import net.sourceforge.ganttproject.chart.overview.ToolbarBuilder; import net.sourceforge.ganttproject.gui.TaskTreeUIFacade; import net.sourceforge.ganttproject.gui.UIFacade; import net.sourceforge.ganttproject.task.Task; import net.sourceforge.ganttproject.task.TaskManager; import net.sourceforge.ganttproject.task.TaskNode; import net.sourceforge.ganttproject.task.TaskSelectionManager; import net.sourceforge.ganttproject.task.TaskSelectionManager.Listener; import net.sourceforge.ganttproject.task.event.TaskHierarchyEvent; import net.sourceforge.ganttproject.task.event.TaskListenerAdapter; import net.sourceforge.ganttproject.util.collect.Pair; import org.jdesktop.swingx.JXTreeTable; import org.jdesktop.swingx.decorator.ColorHighlighter; import org.jdesktop.swingx.decorator.ComponentAdapter; import org.jdesktop.swingx.decorator.HighlightPredicate; import org.jdesktop.swingx.decorator.Highlighter; import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode; import org.jdesktop.swingx.treetable.MutableTreeTableNode; import org.jdesktop.swingx.treetable.TreeTableNode; import javax.swing.*; import javax.swing.Timer; import javax.swing.event.*; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.dnd.DropTargetAdapter; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.event.*; import java.util.*; import java.util.List; import java.util.logging.Level; /** * Class that generate the JTree */ public class GanttTree2 extends TreeTableContainer<Task, GanttTreeTable, GanttTreeTableModel> implements /*DragSourceListener, DragGestureListener,*/ TaskTreeUIFacade { private GanttProjectBase.RowHeightAligner myRowHeightAligner; private UIFacade myUIFacade; /** Pointer on graphic area */ private ChartComponentBase area = null; // TODO Replace with IGanttProject and facade classes /** Pointer of application */ private final GanttProject myProject; private final TaskManager myTaskManager; private final TaskSelectionManager mySelectionManager; private final GPAction myIndentAction; private final GPAction myUnindentAction; private final GPAction myMoveUpAction; private final GPAction myMoveDownAction; private final GPAction myLinkTasksAction; private final GPAction myUnlinkTasksAction; private boolean isOnTaskSelectionEventProcessing; private Highlighter myDragHighlighter; private static Runnable createDirtyfier(final GanttProjectBase project) { return new Runnable() { @Override public void run() { project.setModified(); } }; } private static Pair<GanttTreeTable, GanttTreeTableModel> createTreeTable( IGanttProject project, Runnable dirtyfier, UIFacade uiFacade) { GanttTreeTableModel tableModel = new GanttTreeTableModel(project.getTaskManager(), project.getTaskCustomColumnManager(), uiFacade, dirtyfier); return Pair.create(new GanttTreeTable(project, uiFacade, tableModel), tableModel); } public GanttTree2(final GanttProject project, TaskManager taskManager, TaskSelectionManager selectionManager, final UIFacade uiFacade) { super(createTreeTable(project.getProject(), createDirtyfier(project), uiFacade)); myUIFacade = uiFacade; myProject = project; myTaskManager = taskManager; mySelectionManager = selectionManager; myTaskManager.addTaskListener(new TaskListenerAdapter() { @Override public void taskModelReset() { clearTree(); } @Override public void taskRemoved(TaskHierarchyEvent e) { MutableTreeTableNode node = getNode(e.getTask()); if (node == null) { return; } TreeNode parent = node.getParent(); if (parent != null) { getTreeModel().removeNodeFromParent(node); } } }); mySelectionManager.addSelectionListener(new TaskSelectionManager.Listener() { @Override public void userInputConsumerChanged(Object newConsumer) { } @Override public void selectionChanged(List<Task> currentSelection) { onTaskSelectionChanged(currentSelection); } }); // Create Actions GPAction propertiesAction = new TaskPropertiesAction(project.getProject(), selectionManager, uiFacade); GPAction deleteAction = new TaskDeleteAction(taskManager, selectionManager, uiFacade, this); GPAction newAction = new TaskNewAction(project.getProject(), uiFacade); setArtefactActions(newAction, propertiesAction, deleteAction); myLinkTasksAction = new TaskLinkAction(taskManager, selectionManager, uiFacade); myUnlinkTasksAction = new TaskUnlinkAction(taskManager, selectionManager, uiFacade); myIndentAction = new TaskIndentAction(taskManager, selectionManager, uiFacade, this); myUnindentAction = new TaskUnindentAction(taskManager, selectionManager, uiFacade, this); myMoveUpAction = new TaskMoveUpAction(taskManager, selectionManager, uiFacade, this); myMoveDownAction = new TaskMoveDownAction(taskManager, selectionManager, uiFacade, this); getTreeTable().setupActionMaps(myMoveUpAction, myMoveDownAction, myIndentAction, myUnindentAction, newAction, myProject.getCutAction(), myProject.getCopyAction(), myProject.getPasteAction(), propertiesAction, deleteAction); } @Override protected void init() { getTreeTable().initTreeTable(); // Create the root node initRootNode(); getTreeTable().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.ALT_DOWN_MASK), "cutTask"); getTreeTable().getTree().addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { } }); getTreeTable().getTree().addTreeExpansionListener(new GanttTreeExpansionListener()); ToolTipManager.sharedInstance().registerComponent(getTreeTable()); getTreeTable().insertWithLeftyScrollBar(this); mySelectionManager.addSelectionListener(new Listener() { @Override public void selectionChanged(List<Task> currentSelection) { } @Override public void userInputConsumerChanged(Object newConsumer) { if (getTreeTable().getTable().isEditing()) { getTreeTable().getTable().editingStopped(new ChangeEvent(getTreeTable().getTreeTable())); } } }); getTreeTable().getTree().addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { super.focusGained(e); mySelectionManager.setUserInputConsumer(this); } }); try { GanttTreeDropListener dropListener = new GanttTreeDropListener(); getTreeTable().getDropTarget().addDropTargetListener(dropListener); Color selectionBackground = UIManager.getColor("Table.selectionBackground"); Color dropBackground = new Color(selectionBackground.getRed(), selectionBackground.getGreen(), selectionBackground.getBlue(), 64); Color foreground = UIManager.getColor("Table.selectionForeground"); myDragHighlighter = new ColorHighlighter(dropListener, dropBackground, foreground); } catch (TooManyListenersException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } @Override protected void handlePopupTrigger(MouseEvent e) { if (e.isPopupTrigger() || e.getButton() == MouseEvent.BUTTON3) { TreePath selPath = getTreeTable().getTreeTable().getPathForLocation(e.getX(), e.getY()); if (selPath != null) { DefaultMutableTreeTableNode treeNode = (DefaultMutableTreeTableNode) selPath.getLastPathComponent(); Task task = (Task) treeNode.getUserObject(); if (!getTaskSelectionManager().isTaskSelected(task)) { getTaskSelectionManager().clear(); getTaskSelectionManager().addTask(task); } } createPopupMenu(e.getX(), e.getY()); e.consume(); } } @Override protected void onSelectionChanged(List<DefaultMutableTreeTableNode> selection) { if (isOnTaskSelectionEventProcessing) { return; } List<Task> selectedTasks = Lists.newArrayList(); for (DefaultMutableTreeTableNode node : selection) { if (node instanceof TaskNode) { selectedTasks.add((Task) node.getUserObject()); } } mySelectionManager.setSelectedTasks(selectedTasks); } private TaskSelectionManager getTaskSelectionManager() { return mySelectionManager; } /** * Edits the <code>t</code> task name in the treetable. */ public void setEditingTask(Task t) { getTreeTable().getTreeTable().editingStopped(new ChangeEvent(getTreeTable().getTreeTable())); TaskSelectionManager taskSelectionManager = getTaskSelectionManager(); taskSelectionManager.clear(); taskSelectionManager.addTask(t); getTreeTable().editSelectedTask(); getTreeTable().centerViewOnSelectedCell(); } public void stopEditing() { getTreeTable().getTable().editingCanceled(new ChangeEvent(getTreeTable().getTreeTable())); getTreeTable().getTreeTable().editingCanceled(new ChangeEvent(getTreeTable().getTreeTable())); } private void initRootNode() { getRootNode().setUserObject(myTaskManager.getRootTask()); } public Action[] getPopupMenuActions() { List<Action> actions = new ArrayList<Action>(); actions.add(getNewAction()); if (!getTaskSelectionManager().getSelectedTasks().isEmpty()) { actions.add(getPropertiesAction()); actions.add(null); for (AbstractAction a : getTreeActions()) { actions.add(a); } actions.add(null); actions.add(myProject.getCutAction()); actions.add(myProject.getCopyAction()); actions.add(myProject.getPasteAction()); actions.add(getDeleteAction()); } return actions.toArray(new Action[0]); } /** Create a popup menu when mouse click */ private void createPopupMenu(int x, int y) { Action[] popupMenuActions = getPopupMenuActions(); JScrollBar vbar = getTreeTable().getVerticalScrollBar(); myUIFacade.showPopupMenu(this, popupMenuActions, x - getTreeTable().getHorizontalScrollBar().getValue() + (vbar.isVisible() ? vbar.getWidth() : 0), y - vbar.getValue() + getTreeTable().getTable().getTableHeader().getHeight()); } /** Change graphic part */ public void setGraphicArea(ChartComponentBase area) { this.area = area; myRowHeightAligner = new GanttProjectBase.RowHeightAligner(this, this.area.getChartModel()); } @Override public void applyPreservingExpansionState(Task rootTask, Predicate<Task> callable) { MutableTreeTableNode rootNode = getNode(rootTask); List<MutableTreeTableNode> subtree = TreeUtil.collectSubtree(rootNode); Collections.reverse(subtree); LinkedHashMap<Task, Boolean> states = Maps.newLinkedHashMap(); for (MutableTreeTableNode node : subtree) { Task t = (Task)node.getUserObject(); states.put(t, t.getExpand()); } callable.apply(rootTask); for (Map.Entry<Task, Boolean> state : states.entrySet()) { setExpanded(state.getKey(), state.getValue()); } } /** add an object with the expand information */ DefaultMutableTreeTableNode addObjectWithExpand(Object child, MutableTreeTableNode parent) { DefaultMutableTreeTableNode childNode = new TaskNode((Task) child); if (parent == null) { parent = getRootNode(); } getTreeModel().insertNodeInto(childNode, parent, parent.getChildCount()); myProject.refreshProjectInformation(); return childNode; } static List<Task> convertNodesListToItemList(List<DefaultMutableTreeTableNode> nodesList) { List<Task> res = new ArrayList<Task>(nodesList.size()); Iterator<DefaultMutableTreeTableNode> itNodes = nodesList.iterator(); while (itNodes.hasNext()) { res.add((Task) itNodes.next().getUserObject()); } return res; } /** @return an ArrayList with all tasks. */ List<MutableTreeTableNode> getAllTasks() { return TreeUtil.collectSubtree(getRootNode()); } /** Clear the JTree. */ private void clearTree() { // expand.clear(); TreeUtil.removeAllChildren(getRootNode()); initRootNode(); getTreeModel().setRoot(getRootNode()); // getTreeModel().reload(); } @Override public void setSelected(Task task, boolean clear) { if (clear) { clearSelection(); } getTaskSelectionManager().addTask(task); } @Override public void clearSelection() { getTaskSelectionManager().clear(); } private void onTaskSelectionChanged(List<Task> tasks) { isOnTaskSelectionEventProcessing = true; List<TreePath> paths = new ArrayList<TreePath>(); for (Task t : tasks) { if (t == null) { GPLogger.getLogger(getClass()).log(Level.SEVERE, "Found null task in the selection. Full selection=" + tasks, new NullPointerException()); continue; } MutableTreeTableNode treeNode = getNode(t); assert treeNode != null : "Failed to find tree node for task=" + t; paths.add(TreeUtil.createPath(treeNode)); } getTreeTable().getTreeSelectionModel().setSelectionPaths(paths.toArray(new TreePath[paths.size()])); isOnTaskSelectionEventProcessing = false; } /** @return the mother task. */ public static DefaultMutableTreeTableNode getParentNode(DefaultMutableTreeTableNode node) { if (node == null) { return null; } return (DefaultMutableTreeTableNode) node.getParent(); } /** @return the JTree. */ JXTreeTable getJTree() { return getTreeTable(); } JTable getTable() { return getTreeTable().getTable(); } /** @return the root node */ public DefaultMutableTreeTableNode getRoot() { return getRootNode(); } /** Refresh the expansion (recursive function) */ public void expandRefresh(TreeTableNode moved) { if (moved instanceof TaskNode) { Task movedTask = (Task) moved.getUserObject(); if (movedTask.getExpand()) { getTreeTable().getTree().expandPath(TreeUtil.createPath(moved)); } for (int i = 0; i < moved.getChildCount(); i++) { expandRefresh(moved.getChildAt(i)); } } } public GanttProjectBase.RowHeightAligner getRowHeightAligner() { return myRowHeightAligner; } /** Class for expansion and collapse of node */ private class GanttTreeExpansionListener implements TreeExpansionListener { @Override public void treeExpanded(TreeExpansionEvent e) { if (area != null) { area.repaint(); } DefaultMutableTreeTableNode node = (DefaultMutableTreeTableNode) (e.getPath().getLastPathComponent()); Task task = (Task) node.getUserObject(); task.setExpand(true); myProject.setAskForSave(true); } @Override public void treeCollapsed(TreeExpansionEvent e) { if (area != null) { area.repaint(); } DefaultMutableTreeTableNode node = (DefaultMutableTreeTableNode) (e.getPath().getLastPathComponent()); Task task = (Task) node.getUserObject(); task.setExpand(false); myProject.setAskForSave(true); } } // //////////////////////////////////////////////////////////////////////////////////////// private class GanttTreeDropListener extends DropTargetAdapter implements HighlightPredicate { private TreePath lastPath = null; private Point lastEventPoint = new Point(); private Timer hoverTimer; private int myOverRow = -1; public GanttTreeDropListener() { // Set up a hover timer, so that a node will be automatically // expanded or collapsed if the user lingers on it for more than a // short time hoverTimer = new Timer(1000, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (!getTreeTable().getTree().isExpanded(lastPath)) { getTreeTable().getTree().expandPath(lastPath); } } }); // Set timer to one-shot mode - it will be restarted when the // cursor is over a new node hoverTimer.setRepeats(false); } @Override public void dragOver(DropTargetDragEvent dtde) { Point pt = dtde.getLocation(); if (pt.equals(lastEventPoint)) { return; } lastEventPoint = pt; TreePath path = getTreeTable().getTree().getPathForLocation(pt.x, pt.y); myOverRow = getTreeTable().getRowForPath(path); if (path != lastPath) { getTreeTable().removeHighlighter(myDragHighlighter); lastPath = path; hoverTimer.restart(); getTreeTable().addHighlighter(myDragHighlighter); } } @Override public void dragExit(DropTargetEvent dte) { super.dragExit(dte); myOverRow = -1; } @Override public void drop(DropTargetDropEvent arg0) { myOverRow = -1; } @Override public boolean isHighlighted(Component arg0, ComponentAdapter adapter) { return myOverRow == adapter.row; } } private AbstractAction[] myTreeActions; GanttTreeTableModel getModel() { return getTreeModel(); } public void setSelectionPaths(TreePath[] selectedPaths) { getTree().getTreeSelectionModel().setSelectionPaths(selectedPaths); } // ////////////////////////////////////////////////////////////////////// // TaskTreeUIFacade @Override public AbstractAction[] getTreeActions() { if (myTreeActions == null) { myTreeActions = new AbstractAction[] { myUnindentAction, myIndentAction, myMoveUpAction, myMoveDownAction, myLinkTasksAction, myUnlinkTasksAction }; } return myTreeActions; } @Override public void addToolbarActions(ToolbarBuilder builder) { builder.addButton(myUnindentAction.asToolbarAction()).addButton(myIndentAction.asToolbarAction()) .addButton(myMoveUpAction.asToolbarAction()).addButton(myMoveDownAction.asToolbarAction()) .addButton(myLinkTasksAction.asToolbarAction()).addButton(myUnlinkTasksAction.asToolbarAction()); } @Override public ColumnList getVisibleFields() { return getTreeTable().getVisibleFields(); } public List<Task> getVisibleNodes(VisibleNodesFilter visibleNodesFilter) { return visibleNodesFilter.getVisibleNodes(getJTree(), getTreeTable().getVerticalScrollBar().getValue(), getHeight(), getTreeTable().getRowHeight()); } @Override public void startDefaultEditing(Task modelElement) { if (getTable().isEditing()) { getTable().getCellEditor().stopCellEditing(); } setEditingTask(modelElement); } @Override protected DefaultMutableTreeTableNode getRootNode() { return getTreeModel().getRootNode(); } @Override protected Chart getChart() { return myUIFacade.getGanttChart(); } }