/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
// www.projectforge.org
//
// Copyright (C) 2001-2014 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);
}
}
}
}