Package org.libreplan.web.tree

Source Code of org.libreplan.web.tree.TreeController$TreeViewStateSnapshot

/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
*                         Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2012 Igalia, S.L.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.web.tree;

import static org.libreplan.web.I18nHelper._;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.libreplan.business.orders.entities.SchedulingState;
import org.libreplan.business.orders.entities.SchedulingState.ITypeChangedListener;
import org.libreplan.business.orders.entities.SchedulingState.Type;
import org.libreplan.business.trees.ITreeNode;
import org.libreplan.web.common.IMessagesForUser;
import org.libreplan.web.common.Level;
import org.libreplan.web.common.MessagesForUser;
import org.libreplan.web.common.Util;
import org.libreplan.web.common.Util.Getter;
import org.libreplan.web.common.Util.Setter;
import org.libreplan.web.orders.DynamicDatebox;
import org.libreplan.web.orders.SchedulingStateToggler;
import org.libreplan.web.tree.TreeComponent.Column;
import org.zkoss.zk.ui.AbstractComponent;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.DropEvent;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.KeyEvent;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Button;
import org.zkoss.zul.Constraint;
import org.zkoss.zul.Decimalbox;
import org.zkoss.zul.Intbox;
import org.zkoss.zul.RendererCtrl;
import org.zkoss.zul.Textbox;
import org.zkoss.zul.Tree;
import org.zkoss.zul.TreeModel;
import org.zkoss.zul.Treecell;
import org.zkoss.zul.Treechildren;
import org.zkoss.zul.Treeitem;
import org.zkoss.zul.TreeitemRenderer;
import org.zkoss.zul.Treerow;
import org.zkoss.zul.api.Hbox;
import org.zkoss.zul.impl.api.InputElement;


/**
* Tree controller for project WBS structures
*
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Lorenzo Tilve Álvaro <ltilve@igalia.com>
* @author Manuel Rego Casasnovas <mrego@igalia.com>
* @author Diego Pino García <dpino@igalia.com>
*/
public abstract class TreeController<T extends ITreeNode<T>> extends
        GenericForwardComposer {

    private static final ValidatorFactory validatorFactory = Validation
            .buildDefaultValidatorFactory();

    private static final Validator validator = validatorFactory.getValidator();

    private static final Log LOG = LogFactory.getLog(TreeController.class);

    private IMessagesForUser messagesForUser;

    private Component messagesContainer;

    protected Tree tree;

    protected TreeViewStateSnapshot viewStateSnapshot;

    private final Class<T> type;

    public abstract Renderer getRenderer();

    protected TreeController(Class<T> type) {
        this.type = type;
    }

    public void indent() {
        if (tree.getSelectedCount() == 1) {
            indent(getSelectedNode());
        }
    }

    public void indent(T element) {
        viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
        getModel().indent(element);
        reloadTreeUIAfterChanges();
        updateControlButtons();
    }

    public TreeModel getTreeModel() {
        return (getModel() != null) ? getModel().asTree() : null;
    }

    public void bindModelIfNeeded() {
        if (tree.getModel() != getTreeModel()) {
            tree.setModel(getTreeModel());
        }
    }

    protected abstract EntitiesTree<T> getModel();

    public void unindent() {
        if (tree.getSelectedCount() == 1) {
            unindent(getSelectedNode());
        }
    }

    public void unindent(T element) {
        viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
        getModel().unindent(element);
        reloadTreeUIAfterChanges();
        updateControlButtons();
    }

    public void up() {
        viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
        if (tree.getSelectedCount() == 1) {
            up(getSelectedNode());
        }
    }

    public void up(T element) {
        viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
        getModel().up(element);
        reloadTreeUIAfterChanges();
        updateControlButtons();
    }

    public void down() {
        if (tree.getSelectedCount() == 1) {
            down(getSelectedNode());
        }
    }

    public void down(T element) {
        viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
        getModel().down(element);
        reloadTreeUIAfterChanges();
        updateControlButtons();
    }

    public T getSelectedNode() {
        Treeitem item = tree.getSelectedItem();
        if (item != null) {
            Object value = item.getValue();
            return value != null ? type.cast(value) : null;
        }
        return null;
    }

    public void move(Component dropedIn, Component dragged) {
        if ((isPredicateApplied())
                || (dropedIn.getUuid().equals(dragged.getUuid()))) {
            return;
        }

        viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);

        T fromNode = null;
        if (dragged instanceof Treerow) {
            Treerow from = (Treerow) dragged;
            fromNode = type.cast(((Treeitem) from.getParent()).getValue());
        }
        if (dragged instanceof Treeitem) {
            Treeitem from = (Treeitem) dragged;
            fromNode = type.cast(from.getValue());
        }
        if (dropedIn instanceof Tree) {
            getModel().moveToRoot(fromNode);
        }
        if (dropedIn instanceof Treerow) {
            Treerow to = (Treerow) dropedIn;
            T toNode = type.cast(((Treeitem) to.getParent()).getValue());

            getModel().move(fromNode, toNode);
        }
        reloadTreeUIAfterChanges();
    }

    public void addElement() {
        viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
        try {
            if (tree.getSelectedCount() == 1) {
                getModel().addElementAt(getSelectedNode());
            } else {
                getModel().addElement();
            }
            reloadTreeUIAfterChanges();
        } catch (IllegalStateException e) {
            LOG.warn("exception ocurred adding element", e);
            messagesForUser.showMessage(Level.ERROR, e.getMessage());
        }

    }

    public void addElement(Component cmp) {
        viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
        Textbox name = (Textbox) cmp.getFellow("newOrderElementName");
        Intbox hours = (Intbox) cmp.getFellow("newOrderElementHours");

        if (StringUtils.isEmpty(name.getValue())) {
            throw new WrongValueException(name, _("cannot be empty"));
        }

        if (hours.getValue() == null) {
            hours.setValue(0);
        }

        Textbox nameTextbox = null;

        // Parse hours
        try {
            if (tree.getSelectedCount() == 1) {
                T node = getSelectedNode();

                T newNode = getModel().addElementAt(node, name.getValue(),
                        hours.getValue());
                getRenderer().refreshHoursValueForThisNodeAndParents(newNode);
                getRenderer().refreshBudgetValueForThisNodeAndParents(newNode);

                // Moved here in order to have items already renderer in order
                // to select the proper element to focus
                reloadTreeUIAfterChanges();

                if (node.isLeaf() && !node.isEmptyLeaf()) {
                    // Then a new container will be created
                    nameTextbox = getRenderer().getNameTextbox(node);
                } else {
                    // select the parent row to add new children ASAP
                    tree.setSelectedItem(getRenderer().getTreeitemForNode(
                            newNode.getParent().getThis()));
                }
            } else {
                getModel().addElement(name.getValue(), hours.getValue());

                // This is needed in both parts of the if, but it's repeated in
                // order to simplify the code
                reloadTreeUIAfterChanges();
            }
        } catch (IllegalStateException e) {
            LOG.warn("exception ocurred adding element", e);
            messagesForUser.showMessage(Level.ERROR, e.getMessage());
        }

        name.setValue("");
        hours.setValue(0);

        if (nameTextbox != null) {
            nameTextbox.focus();
        } else {
            name.focus();
        }
    }

    protected abstract void reloadTreeUIAfterChanges();

    protected static class TreeViewStateSnapshot {
        private final Set<Object> all;
        private final Set<Object> dataOpen;
        private final Object selected;

        private TreeViewStateSnapshot(Set<Object> dataOpen, Set<Object> all,
                Object selected) {
            this.dataOpen = dataOpen;
            this.all = all;
            this.selected = selected;
        }

        public static TreeViewStateSnapshot takeSnapshot(Tree tree) {
            Set<Object> dataOpen = new HashSet<Object>();
            Set<Object> all = new HashSet<Object>();
            Object selected = null;
            if (tree != null && tree.getTreechildrenApi() != null) {
                final Iterator<Treeitem> itemsIterator = tree
                        .getTreechildrenApi().getItems().iterator();
                while (itemsIterator.hasNext()) {
                    Treeitem treeitem = (Treeitem) itemsIterator.next();
                    Object value = getAssociatedValue(treeitem);
                    if (treeitem.isOpen()) {
                        dataOpen.add(value);
                    }
                    if (treeitem.isSelected()) {
                        selected = value;
                    }
                    all.add(value);
                }
            }
            return new TreeViewStateSnapshot(dataOpen, all, selected);
        }

        private static Object getAssociatedValue(Treeitem treeitem) {
            return treeitem.getValue();
        }

        public void restorePreviousViewState(Treeitem item) {
            Object value = getAssociatedValue(item);
            item.setOpen(isNewlyCreated(value) || wasOpened(value));
            item.setSelected(value == selected);
        }

        private boolean wasOpened(Object value) {
            return dataOpen.contains(value);
        }

        private boolean isNewlyCreated(Object value) {
            return !all.contains(value);
        }
    }

    public void removeElement() {
        Set<Treeitem> selectedItems = tree.getSelectedItems();
        for (Treeitem treeItem : selectedItems) {
            remove(type.cast(treeItem.getValue()));
        }
        reloadTreeUIAfterChanges();
    }

    public void remove(T element) {
        List<T> parentNodes = getModel().getParents(element);
        try {
            getModel().removeNode(element);
        } catch (NullPointerException e) {
            LOG.error("Trying to delete an already removed node", e);
        }
        getRenderer().refreshHoursValueForNodes(parentNodes);
        getRenderer().refreshBudgetValueForNodes(parentNodes);
    }

    @Override
    public void doAfterCompose(Component comp) throws Exception {
        super.doAfterCompose(comp);
        messagesForUser = new MessagesForUser(messagesContainer);
    }

    public boolean isItemSelected() {
        return tree.getSelectedItem() != null;
    }

    public boolean isNotItemSelected() {
        return !isItemSelected();
    }

    protected Button btnNew;

    private List<Column> columns;

    private Button btnNewFromTemplate;

    private Button downButton;

    private Button upButton;

    private Button leftButton;

    private Button rightButton;

    protected TreeViewStateSnapshot getSnapshotOfOpenedNodes() {
        return viewStateSnapshot;
    }

    private void resetControlButtons() {
        btnNew.setDisabled(isNewButtonDisabled());
        btnNewFromTemplate.setDisabled(isNewButtonDisabled());

        boolean disabled = readOnly || isPredicateApplied();
        downButton.setDisabled(disabled);
        upButton.setDisabled(disabled);
        leftButton.setDisabled(disabled);
        rightButton.setDisabled(disabled);
    }

    protected abstract boolean isNewButtonDisabled();

    protected boolean isFirstLevelElement(Treeitem item) {
        return item.getLevel() == 0;
    }

    protected boolean isFirstItem(T element) {
        List children = element.getParent().getChildren();
        return (children.get(0).equals(element));
    }

    protected boolean isLastItem(T element) {
        List children = element.getParent().getChildren();
        return (children.get(children.size() - 1).equals(element));
    }

    private enum Navigation {
        LEFT, UP, RIGHT, DOWN;
        public static Navigation getIntentFrom(KeyEvent keyEvent) {
            return values()[keyEvent.getKeyCode() - 37];
        }
    }

    public abstract class Renderer implements TreeitemRenderer,
            RendererCtrl {

        private class KeyboardNavigationHandler {

            private Map<Treerow, List<InputElement>> navigableElementsByRow = new HashMap<Treerow, List<InputElement>>();

            void register(final InputElement inputElement) {
                inputElement.setCtrlKeys("#up#down");
                registerNavigableElement(inputElement);
                inputElement.addEventListener("onCtrlKey", new EventListener() {
                    private Treerow treerow = getCurrentTreeRow();

                    @Override
                    public void onEvent(Event event) {
                        Navigation navigation = Navigation
                                .getIntentFrom((KeyEvent) event);
                        moveFocusTo(inputElement, navigation, treerow);
                    }
                });
            }

            private void registerNavigableElement(InputElement inputElement) {
                Treerow treeRow = getCurrentTreeRow();
                if (!navigableElementsByRow.containsKey(treeRow)) {
                    navigableElementsByRow.put(treeRow,
                            new ArrayList<InputElement>());
                }
                navigableElementsByRow.get(treeRow).add(inputElement);
            }

            private void moveFocusTo(InputElement inputElement,
                    Navigation navigation, Treerow treerow) {
                List<InputElement> boxes = getNavigableElements(treerow);
                int position = boxes.indexOf(inputElement);
                if (position > boxes.size() - 1) {
                    return;
                }
                switch (navigation) {
                case UP:
                    focusGoUp(treerow, position);
                    break;
                case DOWN:
                    focusGoDown(treerow, position);
                    break;
                case LEFT:
                    if (position == 0) {
                        focusGoUp(treerow, boxes.size() - 1);
                    } else {
                        if (boxes.get(position - 1).isDisabled()) {
                            moveFocusTo(boxes.get(position - 1),
                                    Navigation.LEFT, treerow);
                        } else {
                            boxes.get(position - 1).focus();
                        }
                    }
                    break;
                case RIGHT:
                    if (position == boxes.size() - 1) {
                        focusGoDown(treerow, 0);
                    } else {
                        if (boxes.get(position + 1).isDisabled()) {
                            moveFocusTo(boxes.get(position + 1),
                                    Navigation.RIGHT, treerow);
                        } else {
                            boxes.get(position + 1).focus();
                        }
                    }
                    break;
                }
            }

            private void focusGoUp(Treerow treerow, int position) {
                Treeitem parent = (Treeitem) treerow.getParent();
                @SuppressWarnings("unchecked")
                List<Treeitem> treeItems = parent.getParent().getChildren();
                int myPosition = parent.indexOf();

                if (myPosition > 0) {
                    // the current node is not the first brother
                    Treechildren treechildren = treeItems.get(myPosition - 1)
                            .getTreechildren();
                    if (treechildren == null
                            || treechildren.getChildren().size() == 0) {
                        // the previous brother doesn't have children,
                        // or it has children but they are unloaded
                        Treerow upTreerow = treeItems.get(myPosition - 1)
                                .getTreerow();

                        focusCorrectBox(upTreerow, position, Navigation.LEFT);
                    } else {
                        // we have to move to the last child of the previous
                        // brother
                        Treerow upTreerow = findLastTreerow(treeItems
                                .get(myPosition - 1));

                        while (!upTreerow.isVisible()) {
                            upTreerow = ((Treeitem) upTreerow.getParent()
                                    .getParent().getParent()).getTreerow();
                        }

                        focusCorrectBox(upTreerow, position, Navigation.LEFT);
                    }
                } else {
                    // the node is the first brother
                    if (parent.getParent().getParent() instanceof Treeitem) {
                        // the node has a parent, so we move up to it
                        Treerow upTreerow = ((Treeitem) parent.getParent()
                                .getParent()).getTreerow();

                        focusCorrectBox(upTreerow, position, Navigation.LEFT);
                    }
                }
            }

            private Treerow findLastTreerow(Treeitem item) {
                if (item.getTreechildren() == null) {
                    return item.getTreerow();
                }
                @SuppressWarnings("unchecked")
                List<Treeitem> children = item.getTreechildren().getChildren();
                Treeitem lastchild = children.get(children.size() - 1);

                return findLastTreerow(lastchild);
            }

            private void focusGoDown(Treerow treerow, int position) {
                Treeitem parent = (Treeitem) treerow.getParent();
                focusGoDown(parent, position, false);
            }

            private void focusGoDown(Treeitem parent, int position,
                    boolean skipChildren) {
                if (parent.getTreechildren() == null || skipChildren) {
                    // Moving from a node to its brother
                    @SuppressWarnings("unchecked")
                    List<Treeitem> treeItems = parent.getParent().getChildren();
                    int myPosition = parent.indexOf();

                    if (myPosition < treeItems.size() - 1) {
                        // the current node is not the last one
                        Treerow downTreerow = treeItems.get(myPosition + 1)
                                .getTreerow();

                        focusCorrectBox(downTreerow, position, Navigation.RIGHT);
                    } else {
                        // the node is the last brother
                        if (parent.getParent().getParent() instanceof Treeitem) {
                            focusGoDown((Treeitem) parent.getParent()
                                    .getParent(), position, true);
                        }
                    }
                } else {
                    // Moving from a parent node to its children
                    Treechildren treechildren = parent.getTreechildren();

                    if (treechildren.getChildren().size() == 0) {
                        // the children are unloaded yet
                        focusGoDown(parent, position, true);
                        return;
                    }
                    Treerow downTreerow = ((Treeitem) treechildren
                            .getChildren().get(0)).getTreerow();

                    if (!downTreerow.isVisible()) {
                        // children are loaded but not visible
                        focusGoDown(parent, position, true);
                        return;
                    }

                    focusCorrectBox(downTreerow, position, Navigation.RIGHT);
                }
            }

            private void focusCorrectBox(Treerow treerow, int position,
                    Navigation whereIfDisabled) {
                List<InputElement> boxes = getNavigableElements(treerow);
                if (position < boxes.size() - 1) {
                    if (boxes.get(position).isDisabled()) {
                        moveFocusTo(boxes.get(position), whereIfDisabled,
                                treerow);
                    } else {
                        boxes.get(position).focus();
                    }
                }
            }

            private List<InputElement> getNavigableElements(Treerow row) {
                if (!navigableElementsByRow.containsKey(row)) {
                    return Collections.emptyList();
                }
                return Collections.unmodifiableList(navigableElementsByRow
                        .get(row));
            }

        }

        private Map<T, Textbox> codeTextboxByElement = new HashMap<T, Textbox>();

        private Map<T, Textbox> nameTextboxByElement = new HashMap<T, Textbox>();

        private Map<T, Intbox> hoursIntBoxByElement = new HashMap<T, Intbox>();

        private Map<T, Decimalbox> budgetDecimalboxByElement = new HashMap<T, Decimalbox>();

        private Map<T, DynamicDatebox> initDateDynamicDateboxByElement = new HashMap<T, DynamicDatebox>();

        private Map<T, DynamicDatebox> endDateDynamicDateboxByElement = new HashMap<T, DynamicDatebox>();

        private KeyboardNavigationHandler navigationHandler = new KeyboardNavigationHandler();

        private Treerow currentTreeRow;

        public Treerow getCurrentTreeRow() {
            return currentTreeRow;
        }

        public Renderer() {
        }

        protected Textbox getNameTextbox(T key) {
            return nameTextboxByElement.get(key);
        }

        public Map<T, Textbox> getCodeTextboxByElement() {
            return Collections.unmodifiableMap(codeTextboxByElement);
        }

        protected void putCodeTextbox(T key, Textbox textbox) {
            codeTextboxByElement.put(key, textbox);
        }

        protected void removeCodeTextbox(T key) {
            codeTextboxByElement.remove(key);
        }

        protected void putNameTextbox(T key, Textbox textbox) {
            nameTextboxByElement.put(key, textbox);
        }

        protected void putInitDateDynamicDatebox(T key,
                DynamicDatebox dynamicDatebox) {
            initDateDynamicDateboxByElement.put(key, dynamicDatebox);
        }

        protected void putEndDateDynamicDatebox(T key,
                DynamicDatebox dynamicDatebox) {
            endDateDynamicDateboxByElement.put(key, dynamicDatebox);
        }

        protected void registerFocusEvent(final InputElement inputElement) {
            inputElement.addEventListener(Events.ON_FOCUS,
                    new EventListener() {

                private Treeitem item = (Treeitem) getCurrentTreeRow().getParent();

                @Override
                public void onEvent(Event event) {
                    item.setSelected(true);
                    Util.reloadBindings(item.getParent());
                }
            });
        }

        protected void addDateCell(final DynamicDatebox dinamicDatebox,
                final String dateboxName) {

            Component cell = Executions.getCurrent().createComponents(
                    "/common/components/dynamicDatebox.zul", null, null);
            try {
                dinamicDatebox.doAfterCompose(cell);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            addCell(cell);
            registerListeners(dinamicDatebox.getDateTextBox());
        }

        protected Treecell addCell(Component... components) {
            return addCell(null, components);
        }

        protected Treecell addCell(String cssClass, Component... components) {
            Treecell cell = new Treecell();
            if (cssClass != null) {
                cell.setSclass(cssClass);
            }
            for (Component component : components) {
                cell.appendChild(component);
                if (component instanceof InputElement) {
                    registerListeners((InputElement) component);
                }
            }
            currentTreeRow.appendChild(cell);
            return cell;
        }

        private void registerListeners(InputElement associatedInput) {
            registerFocusEvent(associatedInput);
            navigationHandler.register(associatedInput);
        }

        @Override
        public void render(final Treeitem item, Object data) {
            item.setValue(data);
            applySnapshot(item);
            final T currentElement = type.cast(data);
            currentTreeRow = getTreeRowWithoutChildrenFor(item, currentElement);
            createCells(item, currentElement);
            onDropMoveFromDraggedToTarget();
        }

        protected void checkInvalidValues(Class<?> beanType,
                String property, Integer value, final Intbox component) {
            Set<ConstraintViolation<T>> violations = validator.validateValue(type, property, value);
            if (!violations.isEmpty()) {
                throw new WrongValueException(component,
                        _(violations.iterator().next().getMessage()));
            }
        }

        private void createCells(Treeitem item, T currentElement) {
            for (Column each : columns) {
                each.doCell(this, item, currentElement);
            }
            item.setTooltiptext(createTooltipText(currentElement));
        }

        private void applySnapshot(final Treeitem item) {
            if (viewStateSnapshot != null) {
                viewStateSnapshot.restorePreviousViewState(item);
            }
        }

        private Treerow getTreeRowWithoutChildrenFor(final Treeitem item,
                T element) {
            Treerow result = createOrRetrieveFor(item);
            // Attach treecells to treerow
            if (element.isUpdatedFromTimesheets()) {
                result.setDraggable("false");
                result.setDroppable("false");
            } else {
                result.setDraggable("true");
                result.setDroppable("true");
            }
            result.getChildren().clear();
            return result;
        }

        protected String pathAsString(int[] path) {
            StringBuilder result = new StringBuilder();
            for (int i = 0; i < path.length; i++) {
                if (i != 0) {
                    result.append(".");
                }
                result.append(path[i] + 1);
            }
            return result.toString();
        }

        private Treerow createOrRetrieveFor(final Treeitem item) {
            if (item.getTreerow() == null) {
                Treerow result = new Treerow();
                result.setParent(item);
                return result;
            } else {
                return item.getTreerow();
            }
        }

        private void onDropMoveFromDraggedToTarget() {
            currentTreeRow.addEventListener("onDrop", new EventListener() {
                @Override
                public void onEvent(org.zkoss.zk.ui.event.Event event) {
                    DropEvent dropEvent = (DropEvent) event;
                    move((Component) dropEvent.getTarget(),
                            (Component) dropEvent.getDragged().getParent());
                }
            });
        }

        public void addSchedulingStateCell(final T currentElement) {
            final SchedulingState schedulingState = getSchedulingStateFrom(currentElement);
            SchedulingStateToggler schedulingStateToggler = new SchedulingStateToggler(
                    schedulingState);
            schedulingStateToggler.setReadOnly(readOnly
                    || currentElement.isUpdatedFromTimesheets());
            final Treecell cell = addCell(
                    getDecorationFromState(getSchedulingStateFrom(currentElement)),
                    schedulingStateToggler);
            cell.addEventListener("onDoubleClick", new EventListener() {
                @Override
                public void onEvent(Event event) {

                    markModifiedTreeitem((Treerow) cell.getParent());
                    onDoubleClickForSchedulingStateCell(currentElement);
                }
            });
            cell.addEventListener(Events.ON_CLICK, new EventListener() {

                private Treeitem item = (Treeitem) getCurrentTreeRow().getParent();

                @Override
                public void onEvent(Event event) {
                    item.getTree().toggleItemSelection(item);
                }
            });
            schedulingState.addTypeChangeListener(
                    new ITypeChangedListener() {

                        @Override
                        public void typeChanged(Type newType) {
                            cell.setSclass(getDecorationFromState(schedulingState));
                        }
                    });
            schedulingStateToggler.afterCompose();

        }

        protected abstract SchedulingState getSchedulingStateFrom(
                T currentElement);

        private String getDecorationFromState(SchedulingState state) {
            return state.getCssClass();
        }

        protected abstract void addCodeCell(final T element);

        protected abstract void addDescriptionCell(final T element);

        public void addBudgetCell(final T currentElement) {
            Decimalbox decimalboxBudget = buildBudgetDecimalboxFor(currentElement);
            budgetDecimalboxByElement.put(currentElement, decimalboxBudget);
            if (readOnly) {
                decimalboxBudget.setDisabled(true);
            }
            addCell(decimalboxBudget);
        }

        private Decimalbox buildBudgetDecimalboxFor(final T element) {
            Decimalbox result = new DecimalboxDirectValue();
            if (element.isLeaf()) {
                Util.bind(result, getBudgetGetterFor(element),
                        getBudgetSetterFor(element));
                result.setConstraint(getBudgetConstraintFor(element));
            } else {
                // If it's a container budget cell is not editable
                Util.bind(result, getBudgetGetterFor(element));
            }
            result.setFormat(Util.getMoneyFormat());
            return result;
        }

        private Getter<BigDecimal> getBudgetGetterFor(final T element) {
            return new Util.Getter<BigDecimal>() {
                @Override
                public BigDecimal get() {
                    return getBudgetHandler().getBudgetFor(element);
                }
            };
        }

        private Setter<BigDecimal> getBudgetSetterFor(final T element) {
            return new Util.Setter<BigDecimal>() {
                @Override
                public void set(BigDecimal value) {
                    getBudgetHandler().setBudgetHours(element, value);
                    List<T> parentNodes = getModel().getParents(element);
                    // Remove the last element because it's an
                    // Order node, not an OrderElement
                    parentNodes.remove(parentNodes.size() - 1);
                    for (T node : parentNodes) {
                        DecimalboxDirectValue decimalbox = (DecimalboxDirectValue) budgetDecimalboxByElement
                                .get(node);
                        BigDecimal budget = getBudgetHandler().getBudgetFor(
                                node);
                        if (isInCurrentPage(decimalbox)) {
                            decimalbox.setValue(budget);
                        } else {
                            decimalbox.setValueDirectly(budget);
                        }
                    }
                }

                private boolean isInCurrentPage(DecimalboxDirectValue intbox) {
                    Treeitem treeItem = (Treeitem) intbox.getParent()
                            .getParent().getParent();
                    List<Treeitem> treeItems = new ArrayList<Treeitem>(
                            tree.getItems());
                    int position = treeItems.indexOf(treeItem);

                    if (position < 0) {
                        throw new RuntimeException("Treeitem " + treeItem
                                + " has to belong to tree.getItems() list");
                    }

                    return (position / tree.getPageSize()) == tree
                            .getActivePage();
                }
            };
        }

        public void updateColumnsFor(T element) {
            updateCodeFor(element);
            updateNameFor(element);
            updateHoursFor(element);
            updateBudgetFor(element);
            updateInitDateFor(element);
            updateEndDateFor(element);
        }

        private void updateCodeFor(T element) {
            if (!readOnly && !element.isJiraIssue()) {
                Textbox textbox = codeTextboxByElement.get(element);
                textbox.setValue(getCodeHandler().getCodeFor(element));
            }
        }

        private void updateBudgetFor(T element) {
            if (!readOnly && element.isLeaf()) {
                Decimalbox decimalbox = budgetDecimalboxByElement.get(element);
                decimalbox.invalidate();
                refreshBudgetValueForThisNodeAndParents(element);
            }
        }

        private void updateNameFor(T element) {
            if (!readOnly) {
                Textbox textbox = nameTextboxByElement.get(element);
                textbox.setValue(getNameHandler().getNameFor(element));
            }
        }

        private void updateInitDateFor(T element) {
            if (!readOnly) {
                DynamicDatebox dynamicDatebox = initDateDynamicDateboxByElement
                        .get(element);
                dynamicDatebox.updateComponents();
            }
        }

        private void updateEndDateFor(T element) {
            if (!readOnly) {
                DynamicDatebox dynamicDatebox = endDateDynamicDateboxByElement
                        .get(element);
                dynamicDatebox.updateComponents();
            }
        }

        public void refreshBudgetValueForThisNodeAndParents(T node) {
            List<T> nodeAndItsParents = getModel().getParents(node);
            nodeAndItsParents.add(node);
            refreshBudgetValueForNodes(nodeAndItsParents);
        }

        public void refreshBudgetValueForNodes(List<T> nodes) {
            for (T node : nodes) {
                Decimalbox decimalbox = budgetDecimalboxByElement.get(node);
                // For the Order node there is no associated decimalbox
                if (decimalbox != null) {
                    BigDecimal currentBudget = getBudgetHandler().getBudgetFor(node);
                    decimalbox.setValue(currentBudget);
                }
            }
        }

        private Constraint getBudgetConstraintFor(final T line) {
            return new Constraint() {
                @Override
                public void validate(Component comp, Object value)
                        throws WrongValueException {
                    if (value == null) {
                        throw new WrongValueException(comp,
                                _("cannot be empty"));
                    }
                    if (((BigDecimal) value).compareTo(BigDecimal.ZERO) < 0) {
                        throw new WrongValueException(comp,
                                _("cannot be negative"));
                    }
                }

            };
        }

        public void addHoursCell(final T currentElement) {
            Intbox intboxHours = buildHoursIntboxFor(currentElement);
            hoursIntBoxByElement.put(currentElement, intboxHours);
            if (readOnly || currentElement.isJiraIssue()) {
                intboxHours.setDisabled(true);
            }
            Treecell cellHours = addCell(intboxHours);
            setReadOnlyHoursCell(currentElement, intboxHours, cellHours);
        }

        private void setReadOnlyHoursCell(T element,
                Intbox boxHours, Treecell tc) {
            if (!readOnly && element.isLeaf()) {
                if (getHoursGroupHandler().hasMoreThanOneHoursGroup(element)) {
                    boxHours.setReadonly(true);
                    tc.setTooltiptext(_("Disabled because of it contains more than one hours group"));
                } else {
                    boxHours.setReadonly(false);
                    tc.setTooltiptext("");
                }
            }
        }

        private Intbox buildHoursIntboxFor(final T element) {
            Intbox result = new IntboxDirectValue();
            if (element.isLeaf()) {
                Util.bind(result, getHoursGetterFor(element),
                        getHoursSetterFor(element));
                result.setConstraint(getHoursConstraintFor(element));
            } else {
                // If it's a container hours cell is not editable
                Util.bind(result, getHoursGetterFor(element));
            }
            return result;
        }

        private Getter<Integer> getHoursGetterFor(final T element) {
            return new Util.Getter<Integer>() {
                @Override
                public Integer get() {
                    return getHoursGroupHandler().getWorkHoursFor(element);
                }
            };
        }

        private Setter<Integer> getHoursSetterFor(final T element) {
            return new Util.Setter<Integer>() {
                @Override
                public void set(Integer value) {
                    getHoursGroupHandler().setWorkHours(element, value);
                    List<T> parentNodes = getModel().getParents(element);
                    // Remove the last element because it's an
                    // Order node, not an OrderElement
                    parentNodes.remove(parentNodes.size() - 1);
                    for (T node : parentNodes) {
                        IntboxDirectValue intbox = (IntboxDirectValue) hoursIntBoxByElement
                                .get(node);
                        Integer hours = getHoursGroupHandler()
                                .getWorkHoursFor(node);
                        if (isInCurrentPage(intbox)) {
                            intbox.setValue(hours);
                        } else {
                            intbox.setValueDirectly(hours);
                        }
                    }
                }

                private boolean isInCurrentPage(IntboxDirectValue intbox) {
                    Treeitem treeItem = (Treeitem) intbox.getParent().getParent().getParent();
                    List<Treeitem> treeItems = new ArrayList<Treeitem>(
                            tree.getItems());
                    int position = treeItems.indexOf(treeItem);

                    if (position < 0) {
                        throw new RuntimeException("Treeitem " + treeItem
                                + " has to belong to tree.getItems() list");
                    }

                    return (position / tree.getPageSize()) == tree
                            .getActivePage();
                }
            };
        }

        private void updateHoursFor(T element) {
            if (!readOnly && element.isLeaf()) {
                Intbox boxHours = (Intbox) hoursIntBoxByElement.get(element);
                Treecell tc = (Treecell) boxHours.getParent();
                setReadOnlyHoursCell(element, boxHours, tc);
                boxHours.invalidate();
                refreshHoursValueForThisNodeAndParents(element);
            }
        }

        public void refreshHoursValueForThisNodeAndParents(T node) {
            List<T> nodeAndItsParents = getModel().getParents(node);
            nodeAndItsParents.add(node);
            refreshHoursValueForNodes(nodeAndItsParents);
        }

        public void refreshHoursValueForNodes(List<T> nodes) {
            for (T node : nodes) {
                Intbox intbox = hoursIntBoxByElement.get(node);
                // For the Order node there is no associated intbox
                if (intbox != null) {
                    Integer currentHours = getHoursGroupHandler()
                            .getWorkHoursFor(node);
                    intbox.setValue(currentHours);
                }
            }
        }

        public Treeitem getTreeitemForNode(T node) {
            Component cmp = hoursIntBoxByElement.get(node);
            while (!(cmp instanceof Treeitem)) {
                cmp = cmp.getParent();
            }
            return (Treeitem) cmp;
        }

        private Constraint getHoursConstraintFor(final T line) {
            return new Constraint() {
                @Override
                public void validate(Component comp, Object value)
                        throws WrongValueException {
                    if (!getHoursGroupHandler().isTotalHoursValid(line, ((Integer) value))) {
                        throw new WrongValueException(
                                comp,
                                _("Value is not valid in current list of Hours Group"));
                    }
                }

            };
        }

        protected abstract void addOperationsCell(final Treeitem item,
                final T currentElement);

        protected abstract void onDoubleClickForSchedulingStateCell(
                T currentElement);

        protected Button createRemoveButton(final T currentElement) {
            EventListener removeListener = new EventListener() {
                @Override
                public void onEvent(Event event) {
                    remove(currentElement);
                    reloadTreeUIAfterChanges();
                }
            };
            final Button result;
            if(readOnly) {
                result = createButton("/common/img/ico_borrar_out.png",
                        _("Delete"), "/common/img/ico_borrar_out.png", "icono",
                        removeListener);
                result.setDisabled(readOnly);
            }
            else {
                result = createButton("/common/img/ico_borrar1.png",
                        _("Delete"), "/common/img/ico_borrar.png", "icono",
                        removeListener);
            }
            return result;
        }

        protected Button createButton(String image, String tooltip,
                String hoverImage, String styleClass,
                EventListener eventListener) {
            Button result = new Button("", image);
            result.setHoverImage(hoverImage);
            result.setSclass(styleClass);
            result.setTooltiptext(tooltip);
            result.addEventListener(Events.ON_CLICK, eventListener);
            return result;
        }

        @Override
        public void doCatch(Throwable ex) {

        }

        @Override
        public void doFinally() {
            resetControlButtons();
        }

        @Override
        public void doTry() {

        }

    }

    public void setColumns(List<Column> columns) {
        this.columns = columns;
    }

    public interface IHoursGroupHandler<T> {

        boolean hasMoreThanOneHoursGroup(T element);

        boolean isTotalHoursValid(T line, Integer value);

        Integer getWorkHoursFor(T element);

        void setWorkHours(T element, Integer value);
    }

    protected abstract IHoursGroupHandler<T> getHoursGroupHandler();

    public interface IBudgetHandler<T> {

        BigDecimal getBudgetFor(T element);

        void setBudgetHours(T element, BigDecimal budget);
    }

    protected abstract IBudgetHandler<T> getBudgetHandler();

    public interface ICodeHandler<T> {

        String getCodeFor(T element);

    }

    protected abstract ICodeHandler<T> getCodeHandler();

    public interface INameHandler<T> {

        String getNameFor(T element);

    }

    protected abstract INameHandler<T> getNameHandler();

    /**
     * Disable control buttons (new, up, down, indent, unindent, delete)
     */
    public void updateControlButtons() {
        T element = getSelectedNode();
        if (element == null) {
            resetControlButtons();
            return;
        }
        Treeitem item = tree.getSelectedItem();

        btnNew.setDisabled(isNewButtonDisabled()
                || element.isUpdatedFromTimesheets());
        btnNewFromTemplate.setDisabled(isNewButtonDisabled()
                || element.isUpdatedFromTimesheets());

        boolean disabled = readOnly || isPredicateApplied();
        downButton.setDisabled(disabled || isLastItem(element));
        upButton.setDisabled(disabled || isFirstItem(element));

        disabled |= element.isUpdatedFromTimesheets();
        leftButton.setDisabled(disabled
                || isFirstLevelElement(item)
                || element.getParent().isUpdatedFromTimesheets());

        boolean previousSiblingIsUpdatedFromTimesheets = false;
        try {
            Treeitem previousItem = (Treeitem) item.getParent()
                    .getChildren().get(item.getIndex() - 1);
            T previousSibling = type.cast(previousItem.getValue());
            previousSiblingIsUpdatedFromTimesheets = previousSibling
                    .isUpdatedFromTimesheets();
        } catch (IndexOutOfBoundsException e) {
            // Do nothing
        }
        rightButton.setDisabled(disabled || isFirstItem(element)
                || previousSiblingIsUpdatedFromTimesheets);
    }

    protected abstract boolean isPredicateApplied();

    protected abstract String createTooltipText(T currentElement);

    protected Set<Treecell> cellsMarkedAsModified = new HashSet<Treecell>();

    public void markModifiedTreeitem(Treerow item) {
        Treecell tc = (Treecell) item.getFirstChild();
        // Check if marked label has been previously added
        if (!(tc.getLastChild() instanceof org.zkoss.zul.Label)) {
            org.zkoss.zul.Label modifiedMark = new org.zkoss.zul.Label("*");
            modifiedMark.setTooltiptext(_("Modified"));
            modifiedMark.setSclass("modified-mark");
            tc.appendChild(modifiedMark);
            cellsMarkedAsModified.add(tc);
        }
    }

    public void resetCellsMarkedAsModified() {
        for(Treecell cell : cellsMarkedAsModified) {
            cell.removeChild(cell.getLastChild());
        }
        cellsMarkedAsModified.clear();
    }

    protected boolean readOnly = true;

    public void setReadOnly(boolean readOnly) {
        if(this.readOnly != readOnly) {
            this.readOnly = readOnly;
            ((Button)orderElementTreeComponent.getFellowIfAny("btnNew")).setDisabled(readOnly);
            ((Button)orderElementTreeComponent.getFellowIfAny("btnNewFromTemplate")).setDisabled(readOnly);
            ((Textbox)orderElementTreeComponent.getFellowIfAny("newOrderElementName")).setDisabled(readOnly);
            ((Intbox)orderElementTreeComponent.getFellowIfAny("newOrderElementHours")).setDisabled(readOnly);
            ((Hbox) orderElementTreeComponent.getFellowIfAny("selectedRowButtons")).setVisible(!readOnly);
            Util.reloadBindings(orderElementTreeComponent);
        }
    }

    protected TreeComponent orderElementTreeComponent;

    public void setTreeComponent(TreeComponent orderElementsTree) {
        this.orderElementTreeComponent = orderElementsTree;
    }

    /**
     * This class is to give visibility to method
     * {@link Intbox#setValueDirectly} which is marked as protected in
     * {@link Intbox} class.
     *
     * <br />
     *
     * This is needed to prevent calling {@link AbstractComponent#smartUpdate}
     * when the {@link Intbox} is not in current page. <tt>smartUpdate</tt> is
     * called by {@link Intbox#setValue(Integer)}. This call causes a JavaScript
     * error when trying to update {@link Intbox} that are not in current page
     * in the tree.
     */
    private class IntboxDirectValue extends Intbox {
        @Override
        public void setValueDirectly(Object value) {
            super.setValueDirectly(value);
        }
    }

    /**
     * This class is to give visibility to method
     * {@link Decimalbox#setValueDirectly} which is marked as protected in
     * {@link Decimalbox} class.
     *
     * <br />
     *
     * This is needed to prevent calling {@link AbstractComponent#smartUpdate}
     * when the {@link Decimalbox} is not in current page. <tt>smartUpdate</tt>
     * is called by {@link Decimalbox#setValue(Integer)}. This call causes a
     * JavaScript error when trying to update {@link Decimalbox} that are not in
     * current page in the tree.
     */
    private class DecimalboxDirectValue extends Decimalbox {
        @Override
        public void setValueDirectly(Object value) {
            super.setValueDirectly(value);
        }
    }

}
TOP

Related Classes of org.libreplan.web.tree.TreeController$TreeViewStateSnapshot

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.