org.projectforge.web.gantt.GanttChartEditTreeTablePanel.java Source code

Java tutorial

Introduction

Here is the source code for org.projectforge.web.gantt.GanttChartEditTreeTablePanel.java

Source

/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
//         www.projectforge.org
//
// Copyright (C) 2001-2013 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition 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; version 3 of the License.
//
// This community edition 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, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////

package org.projectforge.web.gantt;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.AbstractSubmitLink;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.SubmitLink;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.RefreshingView;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.markup.repeater.ReuseIfModelsEqualStrategy;
import org.apache.wicket.markup.repeater.util.ModelIteratorAdapter;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.apache.wicket.util.convert.IConverter;
import org.projectforge.common.DateHelper;
import org.projectforge.common.NumberHelper;
import org.projectforge.core.NumberFormatter;
import org.projectforge.core.UserException;
import org.projectforge.database.HibernateUtils;
import org.projectforge.gantt.GanttChartDO;
import org.projectforge.gantt.GanttChartData;
import org.projectforge.gantt.GanttObjectType;
import org.projectforge.gantt.GanttRelationType;
import org.projectforge.gantt.GanttTask;
import org.projectforge.gantt.GanttTaskImpl;
import org.projectforge.gantt.Task2GanttTaskConverter;
import org.projectforge.task.TaskDO;
import org.projectforge.task.TaskDao;
import org.projectforge.task.TaskTree;
import org.projectforge.user.PFUserContext;
import org.projectforge.web.CSSColor;
import org.projectforge.web.calendar.DateTimeFormatter;
import org.projectforge.web.fibu.ISelectCallerPage;
import org.projectforge.web.task.TaskEditForm;
import org.projectforge.web.task.TaskEditPage;
import org.projectforge.web.task.TaskFormatter;
import org.projectforge.web.task.TaskTreePage;
import org.projectforge.web.tree.DefaultTreeTablePanel;
import org.projectforge.web.tree.TreeIconsActionPanel;
import org.projectforge.web.tree.TreeTable;
import org.projectforge.web.tree.TreeTableFilter;
import org.projectforge.web.tree.TreeTableNode;
import org.projectforge.web.wicket.AbstractEditPage;
import org.projectforge.web.wicket.AbstractSecuredPage;
import org.projectforge.web.wicket.AbstractUnsecureBasePage;
import org.projectforge.web.wicket.EqualsDecorator;
import org.projectforge.web.wicket.ListSelectActionPanel;
import org.projectforge.web.wicket.PresizedImage;
import org.projectforge.web.wicket.WebConstants;
import org.projectforge.web.wicket.WicketUtils;
import org.projectforge.web.wicket.components.AjaxRequiredMaxLengthEditableLabel;
import org.projectforge.web.wicket.components.DatePanel;
import org.projectforge.web.wicket.components.DatePanelSettings;
import org.projectforge.web.wicket.components.ImageSubmitLinkPanel;
import org.projectforge.web.wicket.components.LabelValueChoiceRenderer;
import org.projectforge.web.wicket.components.MinMaxNumberField;
import org.projectforge.web.wicket.components.SingleImagePanel;
import org.projectforge.web.wicket.converter.IntegerPercentConverter;
import org.projectforge.web.wicket.flowlayout.IconLinkPanel;
import org.projectforge.web.wicket.flowlayout.IconType;

public class GanttChartEditTreeTablePanel extends DefaultTreeTablePanel<GanttTreeTableNode>
        implements ISelectCallerPage {
    private static final long serialVersionUID = -184278934597477820L;

    private static final int NUMBER_OF_REJECT_SAVE_COLS = 9;

    private static final org.apache.log4j.Logger log = org.apache.log4j.Logger
            .getLogger(GanttChartEditTreeTablePanel.class);

    @SpringBean(name = "taskDao")
    private TaskDao taskDao;

    @SpringBean(name = "taskFormatter")
    private TaskFormatter taskFormatter;

    private boolean[] rejectSaveColumnVisible;

    private GanttChartData ganttChartData;

    private final GanttChartEditForm form;

    private GanttTask clipboard;

    private final Map<Serializable, DatePanel> startDatePanelMap = new HashMap<Serializable, DatePanel>();

    private final Map<Serializable, DatePanel> endDatePanelMap = new HashMap<Serializable, DatePanel>();

    private final Map<Serializable, CheckBox> visibleCheckboxMap = new HashMap<Serializable, CheckBox>();

    private RefreshingView<GanttTreeTableNode> refreshingView;

    private final Component[] rejectSaveColHeads = new Component[NUMBER_OF_REJECT_SAVE_COLS];

    GanttChartEditTreeTablePanel(final String id, final GanttChartEditForm form, final GanttChartDO ganttChartDO) {
        super(id);
        this.form = form;
        clickRows = false;
        final StringBuffer buf = new StringBuffer();
        buf.append("function showSaveAsTaskQuestionDialog() {\n").append("  return window.confirm('");
        buf.append(PFUserContext.getLocalizedString("gantt.question.saveGanttObjectAsTask"));
        buf.append("');\n}\n");
        buf.append("function showMoveTaskQuestionDialog() {\n").append("  return window.confirm('");
        buf.append(PFUserContext.getLocalizedString("gantt.question.moveTask"));
        buf.append("');\n}\n");
        add(new Label("questionDialogsMethods", buf.toString()).setEscapeModelStrings(false));
    }

    @Override
    protected void initializeColumnHeads() {
        int col = 0;
        colHeadRepeater = new RepeatingView("cols");
        treeTableHead.add(colHeadRepeater);
        colHeadRepeater.add(createColHead("task"));
        colHeadRepeater.add(createEmtpyColumnHead(16)); // Column for edit icon.
        colHeadRepeater.add(SingleImagePanel
                .createTooltipImage(colHeadRepeater.newChildId(), WebConstants.IMAGE_EYE,
                        getString("gantt.tooltip.isVisible"))
                .add(AttributeModifier.replace("style", "width: 16px;")).setRenderBodyOnly(false));
        addColumnHead(col++, "title");
        addColumnHead(col++, "gantt.startDate");
        addColumnHead(col++, "gantt.duration");
        addColumnHead(col++, "gantt.endDate");
        addColumnHead(col++, "task.progress");
        addColumnHead(col++, "gantt.predecessor");
        addColumnHead(col++, "gantt.predecessorOffset");
        addColumnHead(col++, "gantt.relationType.short");
        addColumnHead(col++, "gantt.objectType.short");
    }

    /**
     * Adds a column head for reject-save icons (only visible if modified elements exist).
     * @param rejectSaveIndex
     * @param i18nKey
     */
    private void addColumnHead(final int rejectSaveIndex, final String i18nKey) {
        colHeadRepeater.add(createColHead(i18nKey));
        rejectSaveColHeads[rejectSaveIndex] = createEmtpyColumnHead(32);
        colHeadRepeater.add(rejectSaveColHeads[rejectSaveIndex]);
    }

    GanttChartEditTreeTablePanel setGanttChartData(final GanttChartData ganttChartData) {
        this.ganttChartData = ganttChartData;
        return this;
    }

    public String getImageUrl(final String image) {
        return ((AbstractUnsecureBasePage) getPage()).getImageUrl(image);
    }

    @Override
    protected TreeTable<GanttTreeTableNode> buildTreeTable() {
        if (ganttChartData == null) {
            return null;
        }
        final GanttTreeTable ganttTreeTable = new GanttTreeTable(ganttChartData.getRootObject());
        return ganttTreeTable;
    }

    @Override
    protected List<GanttTreeTableNode> buildTreeList() {
        rejectSaveColumnVisible = new boolean[NUMBER_OF_REJECT_SAVE_COLS];
        if (ganttChartData == null) {
            return null;
        }
        final TreeTableFilter<TreeTableNode> filter = new TreeTableFilter<TreeTableNode>() {
            public boolean match(final TreeTableNode name) {
                return true;
            }
        };
        final List<GanttTreeTableNode> treeList = getTreeTable().getNodeList(filter);
        return treeList;
    }

    @Override
    protected void onBeforeRender() {
        super.onBeforeRender();
        final TaskTree taskTree = taskDao.getTaskTree();
        final List<GanttTreeTableNode> treeList = getTreeList();
        for (int i = 0; i < NUMBER_OF_REJECT_SAVE_COLS; i++) {
            rejectSaveColumnVisible[i] = false;
        }
        if (treeList != null) {
            for (final GanttTreeTableNode node : treeList) {
                final GanttTask ganttObject = node.getGanttObject();
                final TaskDO task = taskTree.getTaskById((Integer) ganttObject.getId());
                if (task != null) {
                    int col = 0;
                    if (rejectSaveColumnVisible[col] == false && isTitleModified(ganttObject, task) == true) {
                        rejectSaveColumnVisible[col] = true;
                    }
                    if (rejectSaveColumnVisible[++col] == false && isStartDateModified(ganttObject, task) == true) {
                        rejectSaveColumnVisible[col] = true;
                    }
                    if (rejectSaveColumnVisible[++col] == false && isDurationModified(ganttObject, task) == true) {
                        rejectSaveColumnVisible[col] = true;
                    }
                    if (rejectSaveColumnVisible[++col] == false && isEndDateModified(ganttObject, task) == true) {
                        rejectSaveColumnVisible[col] = true;
                    }
                    if (rejectSaveColumnVisible[++col] == false && isProgressModified(ganttObject, task) == true) {
                        rejectSaveColumnVisible[col] = true;
                    }
                    if (rejectSaveColumnVisible[++col] == false
                            && isPredecessorModified(ganttObject, task) == true) {
                        rejectSaveColumnVisible[col] = true;
                    }
                    if (rejectSaveColumnVisible[++col] == false
                            && isPredecessorOffsetModified(ganttObject, task) == true) {
                        rejectSaveColumnVisible[col] = true;
                    }
                    if (rejectSaveColumnVisible[++col] == false
                            && isRelationTypeModified(ganttObject, task) == true) {
                        rejectSaveColumnVisible[col] = true;
                    }
                    if (rejectSaveColumnVisible[++col] == false && isTypeModified(ganttObject, task) == true) {
                        rejectSaveColumnVisible[col] = true;
                    }
                }
            }
        }
        for (int i = 0; i < NUMBER_OF_REJECT_SAVE_COLS; i++) {
            rejectSaveColHeads[i].setVisible(rejectSaveColumnVisible[i]);
        }
        createTreeRows();
        final Iterator<Item<GanttTreeTableNode>> it = refreshingView.getItems();
        while (it.hasNext() == true) {
            final Item<GanttTreeTableNode> row = it.next();
            final GanttTask ganttObject = row.getModelObject().getGanttObject();
            boolean visible = true;
            if (form.getSettings().isShowOnlyVisibles() == true) {
                final GanttTaskImpl root = (GanttTaskImpl) ganttChartData.getRootObject();
                GanttTask current = ganttObject;
                int i = 10;
                while (current != null && current != root) {
                    if (i-- < 0) {
                        break; // Endless loop protection.
                    }
                    if (current.isVisible() == false) {
                        visible = false;
                        break;
                    }
                    current = root.findParent(current.getId());
                }
            }
            row.setVisible(visible);
            if (visible == true) {
                final TaskDO task = taskTree.getTaskById((Integer) ganttObject.getId());
                int col = 0;
                setRejectSaveLinksFragmentVisibility("rejectSaveTitle", row, col++,
                        isTitleModified(ganttObject, task));
                setRejectSaveLinksFragmentVisibility("rejectSaveStartDate", row, col++,
                        isStartDateModified(ganttObject, task));
                setRejectSaveLinksFragmentVisibility("rejectSaveDuration", row, col++,
                        isDurationModified(ganttObject, task));
                setRejectSaveLinksFragmentVisibility("rejectSaveEndDate", row, col++,
                        isEndDateModified(ganttObject, task));
                setRejectSaveLinksFragmentVisibility("rejectSaveProgress", row, col++,
                        isProgressModified(ganttObject, task));
                setRejectSaveLinksFragmentVisibility("rejectSavePredecessor", row, col++,
                        isPredecessorModified(ganttObject, task));
                setRejectSaveLinksFragmentVisibility("rejectSavePredecessorOffset", row, col++,
                        isPredecessorOffsetModified(ganttObject, task));
                setRejectSaveLinksFragmentVisibility("rejectSaveRelationType", row, col++,
                        isRelationTypeModified(ganttObject, task));
                setRejectSaveLinksFragmentVisibility("rejectSaveType", row, col++,
                        isTypeModified(ganttObject, task));
            }
        }
    }

    private void setRejectSaveLinksFragmentVisibility(final String id, final Item<GanttTreeTableNode> row,
            final int col, final boolean visible) {
        final RejectSaveLinksFragment rejectSaveFragment = (RejectSaveLinksFragment) row.get(id);
        if (rejectSaveColumnVisible[col] == false) {
            rejectSaveFragment.setVisible(false);
        } else {
            rejectSaveFragment.setVisible(true);
            rejectSaveFragment.setIconsVisible(visible);
        }
    }

    private boolean isTitleModified(final GanttTask ganttObject, final TaskDO task) {
        return task != null && StringUtils.equals(ganttObject.getTitle(), task.getTitle()) == false;
    }

    private boolean isStartDateModified(final GanttTask ganttObject, final TaskDO task) {
        return task != null && DateHelper.isSameDay(ganttObject.getStartDate(), task.getStartDate()) == false;
    }

    private boolean isDurationModified(final GanttTask ganttObject, final TaskDO task) {
        return task != null && NumberHelper.isEqual(ganttObject.getDuration(), task.getDuration()) == false;
    }

    private boolean isEndDateModified(final GanttTask ganttObject, final TaskDO task) {
        return task != null && DateHelper.isSameDay(ganttObject.getEndDate(), task.getEndDate()) == false;
    }

    private boolean isProgressModified(final GanttTask ganttObject, final TaskDO task) {
        return task != null && NumberHelper.isEqual(ganttObject.getProgress(), task.getProgress()) == false;
    }

    private boolean isPredecessorModified(final GanttTask ganttObject, final TaskDO task) {
        return task != null
                && ObjectUtils.equals(ganttObject.getPredecessorId(), task.getGanttPredecessorId()) == false;
    }

    private boolean isPredecessorOffsetModified(final GanttTask ganttObject, final TaskDO task) {
        return task != null && NumberHelper.isEqual(ganttObject.getPredecessorOffset(),
                task.getGanttPredecessorOffset()) == false;
    }

    private boolean isRelationTypeModified(final GanttTask ganttObject, final TaskDO task) {
        return task != null && ganttObject.getRelationType() != task.getGanttRelationType();
    }

    private boolean isTypeModified(final GanttTask ganttObject, final TaskDO task) {
        return task != null && ganttObject.getType() != task.getGanttObjectType();
    }

    @SuppressWarnings("serial")
    @Override
    protected void createTreeRows() {
        if (refreshingView != null) {
            // Already initialized.
            return;
        }

        refreshingView = new RefreshingView<GanttTreeTableNode>("rows") {

            @SuppressWarnings("unchecked")
            @Override
            protected Iterator<IModel<GanttTreeTableNode>> getItemModels() {
                List<GanttTreeTableNode> treeList = getTreeList();
                if (treeList == null) {
                    treeList = new ArrayList<GanttTreeTableNode>();
                }
                return new ModelIteratorAdapter(treeList.iterator()) {
                    @Override
                    protected IModel<?> model(final Object obj) {
                        return EqualsDecorator.decorate(new CompoundPropertyModel(obj));
                    }
                };
            }

            @Override
            protected void populateItem(final Item<GanttTreeTableNode> item) {
                final GanttTreeTableNode node = item.getModelObject();
                final GanttTask ganttObject = node.getGanttObject();
                final TaskDO task = taskDao.getTaskTree().getTaskById((Integer) ganttObject.getId());
                if (item.getIndex() % 2 == 0) {
                    item.add(AttributeModifier.replace("class", "even"));
                } else {
                    item.add(AttributeModifier.replace("class", "odd"));
                }
                final Label formattedLabel = new Label(ListSelectActionPanel.LABEL_ID, new Model<String>() {
                    @Override
                    public String getObject() {
                        if (NumberHelper.greaterZero((Integer) ganttObject.getId()) == true) {
                            return ganttObject.getTitle();
                        } else {
                            return "*" + ganttObject.getTitle() + "*";
                        }
                    };
                }) {
                    @Override
                    protected void onBeforeRender() {
                        final boolean clipboarded = clipboard != null && clipboard.getId() == ganttObject.getId();
                        if (clipboarded == true) {
                            add(AttributeModifier.replace("style", "font-weight: bold; color:red;"));
                        } else {
                            final Behavior behavior = WicketUtils.getAttributeModifier(this, "style");
                            if (behavior != null) {
                                this.remove(behavior);
                            }
                        }
                        super.onBeforeRender();
                    }
                };

                final TreeIconsActionPanel<? extends TreeTableNode> treeIconsActionPanel = new TreeIconsActionPanel<GanttTreeTableNode>(
                        "tree", new Model<GanttTreeTableNode>(node), formattedLabel, getTreeTable());
                treeIconsActionPanel.setUseAjaxAtDefault(false).setUseSubmitLinkImages(true);
                addColumn(item, treeIconsActionPanel, null);
                treeIconsActionPanel.init(GanttChartEditTreeTablePanel.this, node);
                treeIconsActionPanel
                        .add(AttributeModifier.append("style", new Model<String>("white-space: nowrap;")));
                treeIconsActionPanel.setUseAjaxAtDefault(false);
                {
                    final WebMarkupContainer dropDownMenu = new WebMarkupContainer("dropDownMenu");
                    addColumn(item, dropDownMenu, "white-space: nowrap; width: 32px;");
                    dropDownMenu.add(new PresizedImage("cogImage", WebConstants.IMAGE_COG));
                    dropDownMenu.add(new PresizedImage("arrowDownImage", WebConstants.IMAGE_ARROW_DOWN));
                    final RepeatingView menuRepeater = new RepeatingView("menuEntriesRepeater");
                    dropDownMenu.add(menuRepeater);
                    menuRepeater.add(new ContextMenuEntry(menuRepeater.newChildId(), "mark") {
                        @Override
                        void onSubmit() {
                            if (clipboard != null && clipboard == node.getGanttObject()) {
                                clipboard = null;
                            } else {
                                clipboard = node.getGanttObject();
                            }
                        };
                    });
                    menuRepeater.add(new ContextMenuEntry(menuRepeater.newChildId(), "gantt.predecessor.paste") {
                        @Override
                        public boolean isVisible() {
                            return clipboard != null && clipboard != ganttObject;
                        }

                        @Override
                        void onSubmit() {
                            ganttObject.setPredecessor(clipboard);
                        };
                    }.addTooltip(new Model<String>() {
                        @Override
                        public String getObject() {
                            return getString("paste") + ": " + (clipboard != null ? clipboard.getTitle() : "-");
                        }
                    }));
                    menuRepeater.add(
                            new ContextMenuEntry(menuRepeater.newChildId(), "gantt.contextMenu.newSubActivity") {
                                @Override
                                void onSubmit() {
                                    final GanttTaskImpl root = (GanttTaskImpl) ganttChartData.getRootObject();
                                    final Integer nextId = root.getNextId();
                                    ganttObject.addChild(new GanttTaskImpl(nextId).setVisible(true)
                                            .setTitle(getString("untitled")));
                                    final Set<Serializable> openNodes = getOpenNodes();
                                    openNodes.add(ganttObject.getId());
                                    refreshTreeTable();
                                    setOpenNodes(openNodes);
                                    form.getParentPage().refresh();
                                };
                            });
                    menuRepeater.add(new ContextMenuEntry(menuRepeater.newChildId(), new Model<String>() {
                        @Override
                        public String getObject() {
                            if (clipboard == ganttObject) {
                                return PFUserContext.getLocalizedString("gantt.action.moveToTop");
                            } else {
                                return PFUserContext.getLocalizedString("gantt.action.move");
                            }
                        };
                    }) {
                        @Override
                        public boolean isVisible() {
                            if (clipboard == null) {
                                return false;
                            }
                            if (clipboard != ganttObject) {
                                return true;
                            }
                            final GanttTaskImpl root = (GanttTaskImpl) ganttChartData.getRootObject();
                            final GanttTask parent = root.findParent(ganttObject.getId());
                            return (root != parent);
                        }

                        @Override
                        void onSubmit() {
                            if (clipboard == null) {
                                return;
                            }
                            final GanttTaskImpl root = (GanttTaskImpl) ganttChartData.getRootObject();
                            final GanttTask parent = root.findParent(clipboard.getId());
                            final TaskDO task = taskDao.getTaskTree().getTaskById((Integer) clipboard.getId());
                            parent.removeChild(clipboard);
                            if (clipboard == ganttObject) {
                                // Move to top level:
                                root.addChild(ganttObject);
                                final TaskDO rootTask = form.getData().getTask();
                                if (rootTask != null && task != null) {
                                    task.setParentTask(rootTask);
                                    taskDao.update(task);
                                }
                            } else {
                                // Move as a child of this Gantt activity:
                                ganttObject.addChild(clipboard);
                                final TaskDO parentTask = taskDao.getTaskTree()
                                        .getTaskById((Integer) ganttObject.getId());
                                if (parentTask != null && task != null) {
                                    task.setParentTask(parentTask);
                                    taskDao.update(task);
                                }
                                getOpenNodes().add(ganttObject.getId());
                            }
                            final Set<Serializable> openNodes = getOpenNodes();
                            refreshTreeTable();
                            setOpenNodes(openNodes);
                            form.getParentPage().refresh();
                        };

                        @Override
                        protected void onBeforeRender() {
                            if (clipboard != null) {
                                final TaskDO task = taskDao.getTaskTree().getTaskById((Integer) clipboard.getId());
                                if (task != null && onClick == null) {
                                    // Question for safety before moving a task.
                                    setOnClick("if (!showMoveTaskQuestionDialog()) return;");
                                }
                            }
                            super.onBeforeRender();
                        }
                    });
                    menuRepeater.add(new ContextMenuEntry(menuRepeater.newChildId(), "delete") {
                        @Override
                        public boolean isVisible() {
                            return task == null;
                        }

                        @Override
                        void onSubmit() {
                            final GanttTaskImpl root = (GanttTaskImpl) ganttChartData.getRootObject();
                            final GanttTask parent = root.findParent(ganttObject.getId());
                            parent.removeChild(ganttObject);
                            final Set<Serializable> openNodes = getOpenNodes();
                            refreshTreeTable();
                            setOpenNodes(openNodes);
                            form.getParentPage().refresh();
                        };
                    }.setOnClick("if (!showDeleteQuestionDialog()) return;"));
                    menuRepeater
                            .add(new ContextMenuEntry(menuRepeater.newChildId(), "gantt.contextMenu.saveAsTask") {
                                @Override
                                public boolean isVisible() {
                                    return task == null;
                                }

                                @Override
                                void onSubmit() {
                                    final GanttTaskImpl root = (GanttTaskImpl) ganttChartData.getRootObject();
                                    final GanttTask parent = root.findParent(ganttObject.getId());
                                    final TaskDO parentTask = taskDao.getTaskTree()
                                            .getTaskById((Integer) parent.getId());
                                    if (parentTask == null) {
                                        throw new UserException("gantt.error.parentObjectIsNotAPFTask");
                                    }
                                    TaskDO task = taskDao.getTaskTree().getTaskById((Integer) ganttObject.getId());
                                    if (task != null) {
                                        // Oups, Gantt object is already a ProjectForge task.
                                        return;
                                    }
                                    task = Task2GanttTaskConverter.convertToTask(ganttObject);
                                    task.setParentTask(parentTask);
                                    final GanttTask predecessor = ganttObject.getPredecessor();
                                    if (predecessor != null) {
                                        final TaskDO predecessorTask = taskDao.getTaskTree()
                                                .getTaskById((Integer) predecessor.getId());
                                        if (predecessorTask != null) {
                                            task.setGanttPredecessor(predecessorTask);
                                        }
                                    }
                                    final Set<Serializable> openNodes = getOpenNodes();
                                    final Serializable id = taskDao.save(task);
                                    openNodes.remove(ganttObject.getId());
                                    ganttObject.setId(id);
                                    openNodes.add(id);
                                    refreshTreeTable();
                                    setOpenNodes(openNodes);
                                    form.getParentPage().refresh();
                                };
                            }.setOnClick("if (!showSaveAsTaskQuestionDialog()) return;"));
                    menuRepeater.add(new ContextMenuEntry(menuRepeater.newChildId(), "task.title.edit") {
                        @Override
                        public boolean isVisible() {
                            return task != null;
                        }

                        @Override
                        void onSubmit() {
                            final PageParameters pageParams = new PageParameters();
                            pageParams.add(AbstractEditPage.PARAMETER_KEY_ID, String.valueOf(task.getId()));
                            final TaskEditPage editPage = new TaskEditPage(pageParams);
                            editPage.setReturnToPage((AbstractSecuredPage) getPage());
                            setResponsePage(editPage);
                        };
                    });
                    menuRepeater
                            .add(new ContextMenuEntry(menuRepeater.newChildId(), "gantt.contextMenu.setInvisible") {
                                @Override
                                void onSubmit() {
                                    ((GanttTaskImpl) ganttObject).setInvisible();
                                    for (final CheckBox visibleCheckBox : visibleCheckboxMap.values()) {
                                        visibleCheckBox.modelChanged();
                                    }
                                };
                            });
                    menuRepeater.add(new ContextMenuEntry(menuRepeater.newChildId(),
                            "gantt.contextMenu.setSubTasksVisible") {
                        @Override
                        void onSubmit() {
                            ganttObject.setVisible(true);
                            if (ganttObject.getChildren() != null) {
                                for (final GanttTask child : ganttObject.getChildren()) {
                                    child.setVisible(true);
                                }
                            }
                            for (final CheckBox visibleCheckBox : visibleCheckboxMap.values()) {
                                visibleCheckBox.modelChanged();
                            }
                        };
                    });
                }
                final CheckBox visibleCheckBox = (CheckBox) new CheckBox("visible",
                        new PropertyModel<Boolean>(ganttObject, "visible")).setRenderBodyOnly(false);
                visibleCheckboxMap.put(ganttObject.getId(), visibleCheckBox);
                addColumn(item, visibleCheckBox, "width: 16px;");
                addTitleColumns(item, node, ganttObject, task);
                addStartDateColumns(item, node, ganttObject, task);
                addDurationColumns(item, node, ganttObject, task);
                addEndDateColumns(item, node, ganttObject, task);
                addProgressColumns(item, node, ganttObject, task);
                addPredecessorColumns(item, node, ganttObject, task);
                addPredecessorOffsetColumns(item, node, ganttObject, task);
                addRelationTypeColumns(item, node, ganttObject, task);
                addTypeColumns(item, node, ganttObject, task);
            }
        };
        refreshingView.setItemReuseStrategy(ReuseIfModelsEqualStrategy.getInstance());
        treeTableBody.add(refreshingView);
    }

    @SuppressWarnings("serial")
    private abstract class ContextMenuEntry extends WebMarkupContainer {
        private final AbstractSubmitLink link;

        String onClick;

        private ContextMenuEntry(final String id) {
            super(id);
            link = new SubmitLink("menuEntry") {
                @Override
                public void onSubmit() {
                    ContextMenuEntry.this.onSubmit();
                }
            }.setDefaultFormProcessing(false);
            add(link);
        }

        public ContextMenuEntry(final String id, final String labelKey) {
            this(id);
            link.add(new Label("label", PFUserContext.getLocalizedString(labelKey)).setRenderBodyOnly(true));
        }

        public ContextMenuEntry(final String id, final Model<String> label) {
            this(id);
            link.add(new Label("label", label).setRenderBodyOnly(true));
        }

        abstract void onSubmit();

        public ContextMenuEntry addTooltip(final Model<String> model) {
            WicketUtils.addTooltip(link, model);
            return this;
        }

        public ContextMenuEntry setOnClick(final String value) {
            this.onClick = value;
            link.add(AttributeModifier.prepend("onclick", value));
            return this;
        }
    };

    private void addColumn(final Item<GanttTreeTableNode> item, final Component component, final String cssStyle) {
        if (cssStyle != null) {
            component.add(AttributeModifier.append("style", new Model<String>(cssStyle)));
        }
        item.add(component);
    }

    @SuppressWarnings("serial")
    private void addTitleColumns(final Item<GanttTreeTableNode> item, final GanttTreeTableNode node,
            final GanttTask ganttObject, final TaskDO task) {
        final AjaxRequiredMaxLengthEditableLabel titleField = new AjaxRequiredMaxLengthEditableLabel("title",
                new PropertyModel<String>(ganttObject, "title"),
                HibernateUtils.getPropertyLength(TaskDO.class.getName(), "title"));
        titleField.setOutputMarkupId(true);
        // final RequiredMaxLengthTextField titleField = new RequiredMaxLengthTextField("title", new PropertyModel<String>(ganttObject,
        // "title"),
        // HibernateUtils.getPropertyLength(TaskDO.class.getName(), "title"));
        addColumn(item, titleField, null);
        new RejectSaveLinksFragment("rejectSaveTitle", item, titleField, task,
                task != null ? task.getTitle() : "") {
            @Override
            protected void onSave() {
                task.setTitle(ganttObject.getTitle());
                taskDao.update(task);
            }

            @Override
            protected void onReject() {
                ganttObject.setTitle(task.getTitle());
            }
        };
    }

    @SuppressWarnings("serial")
    private void addStartDateColumns(final Item<GanttTreeTableNode> item, final GanttTreeTableNode node,
            final GanttTask ganttObject, final TaskDO task) {
        final DatePanel startDatePanel = new DatePanel("startDate",
                new PropertyModel<Date>(ganttObject, "startDate"),
                DatePanelSettings.get().withSelectProperty("startDate:" + node.getHashId()));
        addColumn(item, startDatePanel, "white-space: nowrap;");
        startDatePanelMap.put(ganttObject.getId(), startDatePanel);
        new RejectSaveLinksFragment("rejectSaveStartDate", item, startDatePanel, task,
                task != null ? DateTimeFormatter.instance().getFormattedDate(task.getStartDate()) : "") {
            @Override
            protected void onSave() {
                task.setStartDate(ganttObject.getStartDate());
                taskDao.update(task);
            }

            @Override
            protected void onReject() {
                ganttObject.setStartDate(task.getStartDate());
            }
        };
    }

    @SuppressWarnings("serial")
    private void addDurationColumns(final Item<GanttTreeTableNode> item, final GanttTreeTableNode node,
            final GanttTask ganttObject, final TaskDO task) {
        final MinMaxNumberField<BigDecimal> durationField = new MinMaxNumberField<BigDecimal>("duration",
                new PropertyModel<BigDecimal>(ganttObject, "duration"), BigDecimal.ZERO,
                TaskEditForm.MAX_DURATION_DAYS);
        addColumn(item, durationField, null);
        new RejectSaveLinksFragment("rejectSaveDuration", item, durationField, task,
                task != null ? NumberFormatter.format(task.getDuration(), 2) : "") {
            @Override
            protected void onSave() {
                task.setDuration(ganttObject.getDuration());
                taskDao.update(task);
            }

            @Override
            protected void onReject() {
                ganttObject.setDuration(task.getDuration());
            }
        };
    }

    @SuppressWarnings("serial")
    private void addEndDateColumns(final Item<GanttTreeTableNode> item, final GanttTreeTableNode node,
            final GanttTask ganttObject, final TaskDO task) {
        final DatePanel endDatePanel = new DatePanel("endDate", new PropertyModel<Date>(ganttObject, "endDate"),
                DatePanelSettings.get().withSelectProperty("endDate:" + node.getHashId()));
        addColumn(item, endDatePanel, "white-space: nowrap;");
        endDatePanelMap.put(ganttObject.getId(), endDatePanel);
        new RejectSaveLinksFragment("rejectSaveEndDate", item, endDatePanel, task,
                task != null ? DateTimeFormatter.instance().getFormattedDate(task.getEndDate()) : "") {
            @Override
            protected void onSave() {
                task.setEndDate(ganttObject.getEndDate());
                taskDao.update(task);
            }

            @Override
            protected void onReject() {
                ganttObject.setEndDate(task.getEndDate());
            }
        };
    }

    @SuppressWarnings("serial")
    private void addProgressColumns(final Item<GanttTreeTableNode> item, final GanttTreeTableNode node,
            final GanttTask ganttObject, final TaskDO task) {
        final MinMaxNumberField<Integer> progressField = new MinMaxNumberField<Integer>("progress",
                new PropertyModel<Integer>(ganttObject, "progress"), 0, 100) {
            @Override
            public IConverter getConverter(final Class type) {
                return new IntegerPercentConverter(0);
            }
        };
        addColumn(item, progressField, null);
        new RejectSaveLinksFragment("rejectSaveProgress", item, progressField, task,
                task != null ? NumberHelper.getAsString(task.getProgress()) : "") {
            @Override
            protected void onSave() {
                task.setProgress(ganttObject.getProgress());
                taskDao.update(task);
            }

            @Override
            protected void onReject() {
                ganttObject.setProgress(task.getProgress());
            }
        };
    }

    @SuppressWarnings("serial")
    private void addPredecessorColumns(final Item<GanttTreeTableNode> item, final GanttTreeTableNode node,
            final GanttTask ganttObject, final TaskDO task) {
        final WebMarkupContainer panel = new WebMarkupContainer("predecessor");
        addColumn(item, panel, "white-space: nowrap;");
        final GanttTask predecessor = ganttObject.getPredecessor();
        final TaskDO predecessorTask = predecessor != null
                ? taskDao.getTaskTree().getTaskById((Integer) predecessor.getId())
                : null;
        final Label asStringLabel = new Label("asString", new Model<String>() {
            @Override
            public String getObject() {
                final GanttTask predecessor = ganttObject.getPredecessor();
                return predecessor != null ? predecessor.getTitle() : "";
            };
        });
        panel.add(asStringLabel);
        final String taskSelectProperty = "predecessorId:" + ganttObject.getId();
        final IconLinkPanel selectSubmitLink = new IconLinkPanel("select", IconType.TASK,
                new SubmitLink(IconLinkPanel.LINK_ID) {
                    @Override
                    public void onSubmit() {
                        final TaskTreePage taskTreePage = new TaskTreePage(GanttChartEditTreeTablePanel.this,
                                taskSelectProperty);
                        if (predecessorTask != null) {
                            taskTreePage.setHighlightedRowId(predecessorTask.getId()); // Preselect node for highlighting.
                        } else if (task != null) {
                            taskTreePage.setHighlightedRowId(task.getId()); // Preselect node for highlighting.
                        }
                        setResponsePage(taskTreePage);
                    }
                }.setDefaultFormProcessing(false));
        selectSubmitLink.setTooltip(new ResourceModel("tooltip.selectTask"));
        panel.add(selectSubmitLink);
        final ImageSubmitLinkPanel unselectSubmitLink = new ImageSubmitLinkPanel("unselect", form,
                WebConstants.IMAGE_TASK_UNSELECT, getString("tooltip.unselectTask")) {
            @Override
            public void onSubmit() {
                ganttObject.setPredecessor(null);
            }

            @Override
            public boolean isVisible() {
                return ganttObject.getPredecessor() != null;
            }
        }.setDefaultFormProcessing(false);
        panel.add(unselectSubmitLink);

        new RejectSaveLinksFragment("rejectSavePredecessor", item, panel, task,
                task != null ? taskFormatter.getTaskPath(getRequestCycle(), task.getGanttPredecessorId()) : "") {
            @Override
            protected void onSave() {
                taskDao.setGanttPredecessor(task, (Integer) ganttObject.getPredecessorId());
                taskDao.update(task);
            }

            @Override
            protected void onReject() {
                ganttObject.setPredecessor(findById(task.getGanttPredecessorId()));
            }
        };
    }

    @SuppressWarnings("serial")
    private void addPredecessorOffsetColumns(final Item<GanttTreeTableNode> item, final GanttTreeTableNode node,
            final GanttTask ganttObject, final TaskDO task) {
        final MinMaxNumberField<Integer> offsetField = new MinMaxNumberField<Integer>("predecessorOffset",
                new PropertyModel<Integer>(ganttObject, "predecessorOffset"), Integer.MIN_VALUE, Integer.MAX_VALUE);
        addColumn(item, offsetField, null);
        new RejectSaveLinksFragment("rejectSavePredecessorOffset", item, offsetField, task,
                task != null ? NumberHelper.getAsString(task.getGanttPredecessorOffset()) : "") {
            @Override
            protected void onSave() {
                task.setGanttPredecessorOffset(ganttObject.getPredecessorOffset());
                taskDao.update(task);
            }

            @Override
            protected void onReject() {
                ganttObject.setPredecessorOffset(task.getGanttPredecessorOffset());
            }
        };
    }

    @SuppressWarnings("serial")
    private void addRelationTypeColumns(final Item<GanttTreeTableNode> item, final GanttTreeTableNode node,
            final GanttTask ganttObject, final TaskDO task) {
        final LabelValueChoiceRenderer<GanttRelationType> relationTypeChoiceRenderer = new LabelValueChoiceRenderer<GanttRelationType>();
        relationTypeChoiceRenderer.addValue(GanttRelationType.START_START,
                getString(GanttRelationType.START_START.getI18nKey() + ".short"));
        relationTypeChoiceRenderer.addValue(GanttRelationType.START_FINISH,
                getString(GanttRelationType.START_FINISH.getI18nKey() + ".short"));
        relationTypeChoiceRenderer.addValue(GanttRelationType.FINISH_START,
                getString(GanttRelationType.FINISH_START.getI18nKey() + ".short"));
        relationTypeChoiceRenderer.addValue(GanttRelationType.FINISH_FINISH,
                getString(GanttRelationType.FINISH_FINISH.getI18nKey() + ".short"));
        final DropDownChoice<GanttRelationType> relationTypeChoice = new DropDownChoice<GanttRelationType>(
                "relationType", new PropertyModel<GanttRelationType>(ganttObject, "relationType"),
                relationTypeChoiceRenderer.getValues(), relationTypeChoiceRenderer);
        relationTypeChoice.setNullValid(true);
        addColumn(item, relationTypeChoice, null);
        final GanttRelationType relationType = task != null ? task.getGanttRelationType() : null;
        new RejectSaveLinksFragment("rejectSaveRelationType", item, relationTypeChoice, task,
                relationType != null ? getString(relationType.getI18nKey()) : "") {
            @Override
            protected void onSave() {
                task.setGanttRelationType(ganttObject.getRelationType());
                taskDao.update(task);
            }

            @Override
            protected void onReject() {
                ganttObject.setRelationType(task.getGanttRelationType());
            }
        };
    }

    @SuppressWarnings("serial")
    private void addTypeColumns(final Item<GanttTreeTableNode> item, final GanttTreeTableNode node,
            final GanttTask ganttObject, final TaskDO task) {
        final LabelValueChoiceRenderer<GanttObjectType> objectTypeChoiceRenderer = new LabelValueChoiceRenderer<GanttObjectType>();
        objectTypeChoiceRenderer.addValue(GanttObjectType.ACTIVITY,
                getString(GanttObjectType.ACTIVITY.getI18nKey() + ".short"));
        objectTypeChoiceRenderer.addValue(GanttObjectType.MILESTONE,
                getString(GanttObjectType.MILESTONE.getI18nKey() + ".short"));
        objectTypeChoiceRenderer.addValue(GanttObjectType.SUMMARY,
                getString(GanttObjectType.SUMMARY.getI18nKey() + ".short"));
        final DropDownChoice<GanttObjectType> objectTypeChoice = new DropDownChoice<GanttObjectType>("type",
                new PropertyModel<GanttObjectType>(ganttObject, "type"), objectTypeChoiceRenderer.getValues(),
                objectTypeChoiceRenderer);
        objectTypeChoice.setNullValid(true);
        addColumn(item, objectTypeChoice, null);
        final GanttObjectType type = task != null ? task.getGanttObjectType() : null;
        new RejectSaveLinksFragment("rejectSaveType", item, objectTypeChoice, task,
                type != null ? getString(type.getI18nKey()) : "") {
            @Override
            protected void onSave() {
                task.setGanttObjectType(ganttObject.getType());
                taskDao.update(task);
            }

            @Override
            protected void onReject() {
                ganttObject.setType(task.getGanttObjectType());
            }
        };
    }

    /**
     * Creates an empty label with style="width: <size>px;" for reject-save-column heads and
     * @return
     */
    private Component createEmtpyColumnHead(final int size) {
        return new Label(colHeadRepeater.newChildId(), "")
                .add(AttributeModifier.replace("style", "width: " + size + "px;"));
    }

    @Override
    protected TreeIconsActionPanel<? extends TreeTableNode> createTreeIconsActionPanel(
            final GanttTreeTableNode node) {
        throw new UnsupportedOperationException(
                "Please, don't use ajax for tree browsing (otherwise user inputs will be lost if you close trees");
    }

    public void cancelSelection(final String property) {
    }

    private void markStartDateModelAsChanged(final Serializable id) {
        final DatePanel startDatePanel = startDatePanelMap.get(id);
        if (startDatePanel != null) {
            startDatePanel.markModelAsChanged();
        } else {
            log.error("Oups, startDatePanel not found.");
        }
    }

    private void markEndDateModelAsChanged(final Serializable id) {
        final DatePanel endDatePanel = endDatePanelMap.get(id);
        if (endDatePanel != null) {
            endDatePanel.markModelAsChanged();
        } else {
            log.error("Oups, endDatePanel not found.");
        }
    }

    public void select(final String property, final Object selectedValue) {
        if (property.startsWith("startDate:") == true) {
            final GanttTask obj = getIndexedGanttObject(property);
            if (obj == null) {
                log.error("GanttObject not found: + " + property);
            } else {
                final Date date = (Date) selectedValue;
                obj.setStartDate(date);
                markStartDateModelAsChanged(obj.getId());
            }
        } else if (property.startsWith("endDate:") == true) {
            final GanttTask obj = getIndexedGanttObject(property);
            if (obj == null) {
                log.error("GanttObject not found: + " + property);
            } else {
                final Date date = (Date) selectedValue;
                obj.setEndDate(date);
                markEndDateModelAsChanged(obj.getId());
            }
        } else if (property.startsWith("predecessorId") == true) {
            final GanttTask obj = getIndexedGanttObject(property);
            if (obj == null) {
                log.error("GanttObject not found: + " + property);
            } else {
                final Integer intValue = (Integer) selectedValue;
                GanttTask predecessor = findById(intValue);
                if (predecessor != null) {
                    obj.setPredecessor(predecessor);
                } else {
                    // OK, maybe an external reference (meaning a reference to a task outside the current Gantt object tree.
                    final TaskDO task = taskDao.getTaskTree().getTaskById(intValue);
                    if (task == null) {
                        log.error("Task not found: + " + property);
                    } else {
                        predecessor = ganttChartData.ensureAndGetExternalGanttObject(task);
                        obj.setPredecessor(predecessor);
                    }
                }
            }
        } else {
            log.error("Property '" + property + "' not supported for selection.");
        }
    }

    public void unselect(final String property) {
    };

    private GanttTask findById(final Serializable id) {
        if (id == null || ganttChartData == null) {
            return null;
        }
        final GanttTask root = ganttChartData.getRootObject();
        if (root == null) {
            return null;
        }
        return root.findById(id);
    }

    private GanttTask getIndexedGanttObject(final String property) {
        final Integer id = NumberHelper.parseInteger(property.substring(property.indexOf(':') + 1));
        final GanttTask obj = ganttChartData.getRootObject().findById(id);
        if (obj == null) {
            log.error("Oups, can't find Gantt object with hash id: " + id);
        }
        return obj;
    }

    private abstract class RejectSaveLinksFragment extends Fragment {
        private static final long serialVersionUID = 2462093138788881814L;

        private final IconLinkPanel rejectSubmitLink;

        private final IconLinkPanel saveSubmitLink;

        @SuppressWarnings("unused")
        private final Component dataComponent;

        private boolean hasTaskUpdateAccess = true;

        protected abstract void onReject();

        protected abstract void onSave();

        @SuppressWarnings("serial")
        private RejectSaveLinksFragment(final String id, final WebMarkupContainer parent,
                final Component dataComponent, final TaskDO task, final String taskValueAsString) {
            super(id, "rejectSaveFragment", GanttChartEditTreeTablePanel.this);
            if (task != null) {
                hasTaskUpdateAccess = taskDao.hasLoggedInUserUpdateAccess(task, task, false);
            }
            this.dataComponent = dataComponent;
            addColumn(parent, this, "white-space: nowrap; width: 32px;");
            rejectSubmitLink = new IconLinkPanel("reject", IconType.DENY,
                    new SubmitLink(IconLinkPanel.LINK_ID, form) {
                        @Override
                        public void onSubmit() {
                            onReject();
                            if (dataComponent instanceof DatePanel) {
                                ((DatePanel) dataComponent).markModelAsChanged();
                            } else {
                                dataComponent.modelChanged();
                            }
                        }
                    }.setDefaultFormProcessing(false)).setColor(CSSColor.RED);
            rejectSubmitLink.setTooltip(
                    Model.of(PFUserContext.getLocalizedMessage("gantt.tooltip.rejectValue", taskValueAsString)));
            add(rejectSubmitLink);
            saveSubmitLink = new IconLinkPanel("save", IconType.ACCEPT,
                    new SubmitLink(IconLinkPanel.LINK_ID, form) {
                        @Override
                        public void onSubmit() {
                            onSave();
                        }
                    }.setDefaultFormProcessing(false)).setColor(CSSColor.GREEN);
            add(saveSubmitLink);

        }

        private void setIconsVisible(final boolean visible) {
            rejectSubmitLink.setVisible(visible);
            if (hasTaskUpdateAccess == false) {
                saveSubmitLink.setVisible(false);
            } else {
                saveSubmitLink.setVisible(visible);
            }
        }
    }
}