net.sourceforge.ganttproject.GanttTreeTableModel.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.ganttproject.GanttTreeTableModel.java

Source

/*
GanttProject is an opensource project management tool.
Copyright (C) 2011 Dmitry Barashev
    
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.model.task.TaskDefaultColumn;
import biz.ganttproject.core.option.DefaultBooleanOption;
import biz.ganttproject.core.option.ValidationException;
import biz.ganttproject.core.time.CalendarFactory;
import biz.ganttproject.core.time.GanttCalendar;
import biz.ganttproject.core.time.TimeDuration;
import biz.ganttproject.core.time.impl.GPTimeUnitStack;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import net.sourceforge.ganttproject.gui.UIFacade;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.task.CustomColumnsException;
import net.sourceforge.ganttproject.task.ResourceAssignment;
import net.sourceforge.ganttproject.task.Task;
import net.sourceforge.ganttproject.task.TaskManager;
import net.sourceforge.ganttproject.task.TaskNode;
import net.sourceforge.ganttproject.task.TaskProperties;
import net.sourceforge.ganttproject.task.dependency.TaskDependency;
import net.sourceforge.ganttproject.task.dependency.TaskDependencyException;

import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode;
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;

import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;

import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Comparator;

/**
 * This class is the model for GanttTreeTable to display tasks.
 *
 * @author bbaranne (Benoit Baranne)
 */
public class GanttTreeTableModel extends DefaultTreeTableModel implements TableColumnModelListener {
    private static class Icons {
        static ImageIcon ALERT_TASK_INPROGRESS = new ImageIcon(
                GanttTreeTableModel.class.getResource("/icons/alert1_16.gif"));
        static ImageIcon ALERT_TASK_OUTDATED = new ImageIcon(
                GanttTreeTableModel.class.getResource("/icons/alert2_16.gif"));
    }

    static Predicate<Task> NOT_SUPERTASK = new Predicate<Task>() {
        @Override
        public boolean apply(Task task) {
            return !task.isSupertask();
        }
    };
    static Predicate<Task> NOT_MILESTONE = new Predicate<Task>() {
        @Override
        public boolean apply(Task input) {
            return !input.isMilestone();
        }
    };
    static {
        new DefaultBooleanOption("");
        TaskDefaultColumn.setLocaleApi(new TaskDefaultColumn.LocaleApi() {
            @Override
            public String i18n(String key) {
                return GanttLanguage.getInstance().getText(key);
            }
        });
    }
    private static GanttLanguage language = GanttLanguage.getInstance();

    private final CustomPropertyManager myCustomColumnsManager;

    private final UIFacade myUiFacade;

    private final Runnable myDirtyfier;

    private static final int STANDARD_COLUMN_COUNT = TaskDefaultColumn.values().length;

    /**
     * Creates an instance of GanttTreeTableModel with a root.
     *
     * @param root
     *          The root.
     * @param customColumnsManager
     * @param dirtyfier
     */
    public GanttTreeTableModel(TaskManager taskManager, CustomPropertyManager customColumnsManager,
            UIFacade uiFacade, Runnable dirtyfier) {
        super(new TaskNode(taskManager.getRootTask()));
        TaskDefaultColumn.BEGIN_DATE.setIsEditablePredicate(NOT_SUPERTASK);
        TaskDefaultColumn.BEGIN_DATE.setSortComparator(new BeginDateComparator());
        TaskDefaultColumn.END_DATE.setIsEditablePredicate(Predicates.and(NOT_SUPERTASK, NOT_MILESTONE));
        TaskDefaultColumn.END_DATE.setSortComparator(new EndDateComparator());
        TaskDefaultColumn.DURATION.setIsEditablePredicate(Predicates.and(NOT_SUPERTASK, NOT_MILESTONE));
        myUiFacade = uiFacade;
        myDirtyfier = dirtyfier;
        myCustomColumnsManager = customColumnsManager;
    }

    private static class BeginDateComparator implements Comparator<Task> {
        @Override
        public int compare(Task t1, Task t2) {
            return t1.getStart().compareTo(t2.getStart());
        }
    }

    private static class EndDateComparator implements Comparator<Task> {
        @Override
        public int compare(Task t1, Task t2) {
            return t1.getEnd().compareTo(t2.getEnd());
        }
    }

    @Override
    public int getColumnCount() {
        return STANDARD_COLUMN_COUNT + myCustomColumnsManager.getDefinitions().size();
    }

    /**
     * Invoked this to insert newChild at location index in parents children. This
     * will then message nodesWereInserted to create the appropriate event. This
     * is the preferred way to add children as it will create the appropriate
     * event.
     */
    // @Override
    // public void insertNodeInto(MutableTreeTableNode newChild,
    // MutableTreeTableNode parent, int index) {
    // parent.insert(newChild, index);
    //
    // int[] newIndexs = new int[1];
    //
    // newIndexs[0] = index;
    // modelSupport.fireChildAdded(TreeUtil.createPath(parent), index, child);
    // }

    /**
     * Message this to remove node from its parent. This will message
     * nodesWereRemoved to create the appropriate event. This is the preferred way
     * to remove a node as it handles the event creation for you.
     */
    // @Override
    // public void removeNodeFromParent(MutableTreeTableNode node) {
    // MutableTreeTableNode parent = (MutableTreeTableNode) node.getParent();
    //
    // if (parent == null)
    // throw new IllegalArgumentException("node does not have a parent.");
    //
    // int[] childIndex = new int[1];
    // Object[] removedArray = new Object[1];
    //
    // childIndex[0] = parent.getIndex(node);
    // parent.remove(childIndex[0]);
    // removedArray[0] = node;
    // nodesWereRemoved(parent, childIndex, removedArray);
    // }

    @Override
    public String getColumnName(int column) {
        if (column >= 0 && column < STANDARD_COLUMN_COUNT) {
            return GanttLanguage.getInstance().getText(TaskDefaultColumn.values()[column].getNameKey());
        }
        CustomPropertyDefinition customColumn = getCustomProperty(column);
        return customColumn.getName();
    }

    @Override
    public int getHierarchicalColumn() {
        return TaskDefaultColumn.NAME.ordinal();
    }

    @Override
    public Class<?> getColumnClass(int column) {
        if (column < 0) {
            return null;
        }
        if (column >= 0 && column < STANDARD_COLUMN_COUNT) {
            return TaskDefaultColumn.values()[column].getValueClass();
        }
        CustomPropertyDefinition customColumn = getCustomProperty(column);
        Class<?> result = customColumn == null ? String.class : customColumn.getType();
        return result;
    }

    private CustomPropertyDefinition getCustomProperty(int columnIndex) {
        assert columnIndex >= STANDARD_COLUMN_COUNT : "We have " + STANDARD_COLUMN_COUNT
                + " default properties, and custom property index starts at " + STANDARD_COLUMN_COUNT
                + ". I've got index #" + columnIndex + ". Something must be wrong here";
        List<CustomPropertyDefinition> definitions = myCustomColumnsManager.getDefinitions();
        columnIndex -= STANDARD_COLUMN_COUNT;
        return columnIndex < definitions.size() ? definitions.get(columnIndex) : null;
    }

    @Override
    public boolean isCellEditable(Object node, int column) {
        if (node instanceof TaskNode) {
            Task task = (Task) ((TaskNode) node).getUserObject();
            if (column >= 0 && column < STANDARD_COLUMN_COUNT) {
                return TaskDefaultColumn.values()[column].isEditable(task);
            }
            return true;
        }
        return false;
    }

    @Override
    public Object getValueAt(Object node, int column) {
        if (column < 0) {
            return "";
        }
        if (!(node instanceof TaskNode)) {
            return null;
        }
        Object res = null;
        TaskNode tn = (TaskNode) node;
        Task t = (Task) tn.getUserObject();
        if (column < STANDARD_COLUMN_COUNT) {
            TaskDefaultColumn defaultColumn = TaskDefaultColumn.values()[column];
            switch (defaultColumn) {
            case TYPE:
                if (((Task) tn.getUserObject()).isProjectTask()) {
                    res = new ImageIcon(getClass().getResource("/icons/mproject.gif"));
                } else if (!tn.isLeaf())
                    res = new ImageIcon(getClass().getResource("/icons/mtask.gif"));
                else if (t.isMilestone()) {
                    res = new ImageIcon(getClass().getResource("/icons/meeting.gif"));
                } else {
                    res = new ImageIcon(getClass().getResource("/icons/tasks2.png"));
                }
                break;
            case PRIORITY:
                GanttTask task = (GanttTask) tn.getUserObject();
                res = new ImageIcon(getClass().getResource(task.getPriority().getIconPath()));
                break;
            case INFO:
                // TODO(dbarashev): implement alerts some other way
                if (t.getCompletionPercentage() < 100) {
                    Calendar c = GanttCalendar.getInstance();
                    if (t.getStart().before(c)) {
                        res = Icons.ALERT_TASK_INPROGRESS;
                    }
                    if (t.getEnd().before(GanttCalendar.getInstance())) {
                        res = Icons.ALERT_TASK_OUTDATED;
                    }
                }
                break;
            case NAME:
                res = tn.getName();
                break;
            case BEGIN_DATE:
                res = tn.getStart();
                break;
            case END_DATE:
                res = t.getDisplayEnd();
                break;
            case DURATION:
                res = new Integer(tn.getDuration());
                break;
            case COMPLETION:
                res = new Integer(tn.getCompletionPercentage());
                break;
            case COORDINATOR:
                ResourceAssignment[] tAssign = t.getAssignments();
                StringBuffer sb = new StringBuffer();
                int nb = 0;
                for (int i = 0; i < tAssign.length; i++) {
                    ResourceAssignment resAss = tAssign[i];
                    if (resAss.isCoordinator()) {
                        sb.append(nb++ == 0 ? "" : ", ").append(resAss.getResource().getName());
                    }
                }
                res = sb.toString();
                break;
            case PREDECESSORS:
                res = TaskProperties.formatPredecessors(t, ",", true);
                break;
            case ID:
                res = t.getTaskID();
                break;
            case OUTLINE_NUMBER:
                List<Integer> outlinePath = t.getManager().getTaskHierarchy().getOutlinePath(t);
                res = Joiner.on('.').join(outlinePath);
                break;
            case COST:
                res = t.getCost().getValue();
                break;
            case RESOURCES:
                List<String> resources = Lists.transform(Arrays.asList(t.getAssignments()),
                        new Function<ResourceAssignment, String>() {
                            @Override
                            public String apply(ResourceAssignment ra) {
                                return ra.getResource().getName();
                            }
                        });
                res = Joiner.on(',').join(resources);
                break;
            default:
                break;
            }

        } else {
            CustomPropertyDefinition customColumn = getCustomProperty(column);
            res = t.getCustomValues().getValue(customColumn);

        }
        // if(tn.getParent()!=null){
        return res;
    }

    @Override
    public void setValueAt(final Object value, final Object node, final int column) {
        if (value == null) {
            return;
        }
        if (isCellEditable(node, column) && !Objects.equal(value, getValueAt(node, column))) {
            // System.out.println("undoable column: " + column);
            myUiFacade.getUndoManager().undoableEdit("Change properties column", new Runnable() {
                @Override
                public void run() {
                    setValue(value, node, column);
                }
            });
        } else {
            // System.out.println("NOT undoable column: " + column);
            setValue(value, node, column);
        }
        myUiFacade.getActiveChart().reset();
    }

    /**
     * Set value in left pane cell
     *
     * @param value
     * @param node
     * @param column
     */
    private void setValue(final Object value, final Object node, final int column) {
        myDirtyfier.run();
        if (column >= STANDARD_COLUMN_COUNT) {
            setCustomPropertyValue(value, node, column);
            return;
        }
        assert node instanceof TaskNode : "Tree node=" + node + " is not a task node";

        final Task task = (Task) ((TaskNode) node).getUserObject();
        TaskDefaultColumn property = TaskDefaultColumn.values()[column];
        switch (property) {
        case NAME:
            ((TaskNode) node).setName(value.toString());
            break;
        case BEGIN_DATE:
            ((TaskNode) node).setStart((GanttCalendar) value);
            ((TaskNode) node).applyThirdDateConstraint();
            break;
        case END_DATE:
            ((TaskNode) node).setEnd(CalendarFactory
                    .createGanttCalendar(GPTimeUnitStack.DAY.adjustRight(((GanttCalendar) value).getTime())));
            break;
        case DURATION:
            TimeDuration tl = task.getDuration();
            ((TaskNode) node)
                    .setDuration(task.getManager().createLength(tl.getTimeUnit(), ((Integer) value).intValue()));
            break;
        case COMPLETION:
            ((TaskNode) node).setCompletionPercentage(((Integer) value).intValue());
            break;
        case PREDECESSORS:
            //List<Integer> newIds = Lists.newArrayList();
            List<String> specs = Lists.newArrayList();
            for (String s : String.valueOf(value).split(",")) {
                if (!s.trim().isEmpty()) {
                    specs.add(s.trim());
                }
            }
            Map<Integer, Supplier<TaskDependency>> promises;
            try {
                promises = TaskProperties.parseDependencies(specs, task, new Function<Integer, Task>() {
                    @Override
                    public Task apply(@Nullable Integer id) {
                        return task.getManager().getTask(id);
                    }
                });
                TaskManager taskManager = task.getManager();
                taskManager.getAlgorithmCollection().getScheduler().setEnabled(false);
                task.getDependenciesAsDependant().clear();
                for (Supplier<TaskDependency> promise : promises.values()) {
                    promise.get();
                }
                taskManager.getAlgorithmCollection().getScheduler().setEnabled(true);
            } catch (IllegalArgumentException | TaskDependencyException e) {
                throw new ValidationException(e);
            }
            break;
        case COST:
            try {
                BigDecimal cost = new BigDecimal(String.valueOf(value));
                task.getCost().setCalculated(false);
                task.getCost().setValue(cost);
            } catch (NumberFormatException e) {
                throw new ValidationException(MessageFormat.format("Can't parse {0} as number", value));
            }
            break;
        default:
            break;
        }

    }

    private void setCustomPropertyValue(Object value, Object node, int column) {
        try {
            ((Task) ((TaskNode) node).getUserObject()).getCustomValues().setValue(getCustomProperty(column), value);
        } catch (CustomColumnsException e) {
            if (!GPLogger.log(e)) {
                e.printStackTrace(System.err);
            }
        }
    }

    @Override
    public void columnAdded(TableColumnModelEvent arg0) {
    }

    @Override
    public void columnRemoved(TableColumnModelEvent arg0) {
    }

    @Override
    public void columnMoved(TableColumnModelEvent arg0) {
        // TODO Auto-generated method stub
    }

    @Override
    public void columnMarginChanged(ChangeEvent arg0) {
        // TODO Auto-generated method stub
    }

    @Override
    public void columnSelectionChanged(ListSelectionEvent arg0) {
        // TODO Auto-generated method stub
    }

    // public Task[] getNestedTasks(Task container) {
    // return null;
    // }
    //
    // public Task[] getDeepNestedTasks(Task container) {
    // // TODO Auto-generated method stub
    // return null;
    // }
    //
    // /**
    // * @return true if this Tasks has any nested subtasks.
    // */
    // public boolean hasNestedTasks(Task container) {
    // TaskNode r = (TaskNode) root;
    // if (r.getChildCount() > 0) {
    // return true;
    // } else {
    // return false;
    // }
    // }
    //
    // public Task getRootTask() {
    // return (Task) ((TaskNode) this.getRoot()).getUserObject();
    // }
    //
    // /**
    // * @return the corresponding task node according to the given task.
    // *
    // * @param task
    // * The task whose TaskNode we want to get.
    // * @return The corresponding TaskNode according to the given task.
    // */
    // public TaskNode getTaskNodeForTask(Task task) {
    // for (MutableTreeTableNode tn : TreeUtil.collectSubtree(getRootNode())) {
    // Task t = (Task) tn.getUserObject();
    // if (t.equals(task)) {
    // return tn;
    // }
    // }
    // return null;
    // }
    //
    // public Task getContainer(Task nestedTask) {
    // // TODO Auto-generated method stub
    // return null;
    // }
    //
    // public void move(Task whatMove, Task whereMove) {
    // // TODO Auto-generated method stub
    // }
    //
    // public boolean areUnrelated(Task dependant, Task dependee) {
    // // TODO Auto-generated method stub
    // return false;
    // }
    //
    // public int getDepth(Task task) {
    // // TODO Auto-generated method stub
    // return 0;
    // }

    public int compareDocumentOrder(Task next, Task dependeeTask) {
        throw new UnsupportedOperationException();
    }

    public boolean contains(Task task) {
        throw new UnsupportedOperationException();
    }

    public DefaultMutableTreeTableNode getRootNode() {
        return (DefaultMutableTreeTableNode) getRoot();
    }
}