view gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DatacageWidget.java @ 9227:84397da33d17

Allow to control specific behaviour in TwinDatacagePanel Implemented client logic of 'intelligent datacage filtering' for SINFO
author gernotbelger
date Wed, 04 Jul 2018 18:28:08 +0200
parents e3c2ae1887e8
children
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU AGPL (>=v3)
 * and comes with ABSOLUTELY NO WARRANTY! Check out the
 * documentation coming with Dive4Elements River for details.
 */

package org.dive4elements.river.client.client.ui;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Stack;

import org.dive4elements.river.client.client.Config;
import org.dive4elements.river.client.client.FLYSConstants;
import org.dive4elements.river.client.client.event.DatacageDoubleClickHandler;
import org.dive4elements.river.client.client.event.DatacageHandler;
import org.dive4elements.river.client.client.services.MetaDataService;
import org.dive4elements.river.client.client.services.MetaDataServiceAsync;
import org.dive4elements.river.client.shared.model.AttrList;
import org.dive4elements.river.client.shared.model.DataCageNode;
import org.dive4elements.river.client.shared.model.DataCageTree;
import org.dive4elements.river.client.shared.model.ToLoad;
import org.dive4elements.river.client.shared.model.User;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.smartgwt.client.types.SelectionStyle;
import com.smartgwt.client.types.TreeModelType;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.Button;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.grid.HoverCustomizer;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import com.smartgwt.client.widgets.grid.events.RecordDoubleClickEvent;
import com.smartgwt.client.widgets.grid.events.RecordDoubleClickHandler;
import com.smartgwt.client.widgets.layout.VLayout;
import com.smartgwt.client.widgets.tree.Tree;
import com.smartgwt.client.widgets.tree.TreeGrid;
import com.smartgwt.client.widgets.tree.TreeNode;

// TODO: refactor, extract ~DataCageGrid
/**
 * Display tree of, for example, previous calculations and allows
 * selection in order to access/clone these.
 */
public final class DatacageWidget extends VLayout {

    private static final FLYSConstants MSG = GWT.create(FLYSConstants.class);

    public static final int MAX_OPEN = 30;

    public interface DatacageFilter {
        boolean accept(DataCageNode node);
    }

    public static final DatacageFilter ACCEPT_ALL_FILTER = new DatacageFilter() {
        @Override
        public boolean accept(final DataCageNode node) {
            return true;
        }
    };

    public static final DatacageFilter ACCEPT_NONE_FILTER = new DatacageFilter() {
        @Override
        public boolean accept(final DataCageNode node) {
            return false;
        }
    };

    private final MetaDataServiceAsync metaDataService = GWT.create(MetaDataService.class);

    private final List<DatacageHandler> handlers = new ArrayList<DatacageHandler>();

    private final List<DatacageDoubleClickHandler> doubleHandlers = new ArrayList<DatacageDoubleClickHandler>();

    private final TreeGrid treeGrid;

    private Tree tree;

    private ToLoad toLoad;

    /** Layout to show spinning wheel of joy. */
    private VLayout lockScreen;

    private DataCageTree dcTree;

    private DatacageFilter filter = ACCEPT_ALL_FILTER;

    private DatacageWidgetData data;

    public DatacageWidget(final DatacageWidgetData data) {

        this.data = data;

        this.toLoad = new ToLoad();

        setWidth100();

        this.treeGrid = new TreeGrid();

        final String columnLabel = this.data.getColumnLabel();
        if (columnLabel != null)
            this.treeGrid.setTreeFieldTitle(columnLabel);

        this.treeGrid.setLoadDataOnDemand(false);
        this.treeGrid.setWidth100();
        this.treeGrid.setHeight100();
        this.treeGrid.setShowRoot(false);
        this.treeGrid.setNodeIcon("[SKIN]/../blank.gif");
        this.treeGrid.setShowConnectors(true);
        this.treeGrid.setLoadingMessage(MSG.databasket_loading());
        this.treeGrid.setLoadingDataMessage(MSG.databasket_loading());
        this.treeGrid.setEmptyMessage(MSG.databasket_empty());

        this.treeGrid.setHoverMoveWithMouse(true);
        this.treeGrid.setCanHover(true);
        this.treeGrid.setShowHover(true);
        this.treeGrid.setHoverOpacity(75);
        this.treeGrid.setHoverWidth(120);

        this.treeGrid.setHoverCustomizer(new HoverCustomizer() {
            @Override
            public String hoverHTML(final Object value, final ListGridRecord record, final int rowNum, final int colNum) {
                if (record instanceof TreeNode) {
                    final TreeNode hoveredTreeNode = (TreeNode) record;
                    String info = hoveredTreeNode.getAttribute("info");
                    if (info == null) {
                        info = hoveredTreeNode.getName();
                    }
                    return info;
                }

                return "";// should not happen
            }
        });

        this.treeGrid.addRecordDoubleClickHandler(new RecordDoubleClickHandler() {
            @Override
            public void onRecordDoubleClick(final RecordDoubleClickEvent event) {
                doubleClickedOnTree(event);
            }
        });

        addMember(this.treeGrid);

        if (data.isShowButton())
            addMember(createPlusButton());

        triggerTreeBuilding();
    }

    public void setFilter(final DatacageFilter filter) {
        assert (filter != null);

        this.filter = filter;

        if (this.dcTree != null)
            updateTree(this.dcTree);
    }

    public TreeGrid getTreeGrid() {
        return this.treeGrid;
    }

    /** Disable input, show spinning wheel of joy. */
    private void lockUI() {
        this.lockScreen = ScreenLock.lockUI(this, this.lockScreen);
    }

    /** Enable input, remove grey, remove spinning wheel of joy. */
    protected final void unlockUI() {
        ScreenLock.unlockUI(this, this.lockScreen);
    }

    /**
     * Sets whether more than one item can be selected.
     *
     * @param multi
     *            if true, allow mutliple selections.
     */
    public void setIsMutliSelectable(final boolean multi) {
        if (multi) {
            this.treeGrid.setSelectionType(SelectionStyle.MULTIPLE);
        } else {
            this.treeGrid.setSelectionType(SelectionStyle.SINGLE);
        }
    }

    /**
     * @param handler
     *            Handler to be added (notified on add-action).
     */
    public void addDatacageHandler(final DatacageHandler handler) {
        if (!this.handlers.contains(handler)) {
            this.handlers.add(handler);
        }
    }

    /**
     * @param h
     *            Handler to be added (notified on Double click on node).
     */
    public void addDatacageDoubleClickHandler(final DatacageDoubleClickHandler h) {
        if (!this.doubleHandlers.contains(h)) {
            this.doubleHandlers.add(h);
        }
    }

    /**
     * @param handler
     *            Handler to remove from list.
     */
    public void removeDatacageHandler(final DatacageHandler handler) {
        this.handlers.remove(handler);
    }

    public ToLoad getSelection() {
        // Reset content of toLoads.

        // FIXME: bad... instead we should react to selection events and update toLoad in this way.

        this.toLoad = new ToLoad();

        if (this.treeGrid == null)
            return this.toLoad;

        final ListGridRecord[] selection = this.treeGrid.getSelectedRecords();

        if (selection != null) {
            for (final ListGridRecord record : selection) {
                if (record instanceof TreeNode) {
                    collectToLoads((TreeNode) record);
                }
            }
        }

        return this.toLoad;
    }

    public List<TreeNode> getPlainSelection() {
        final ListGridRecord[] selection = this.treeGrid.getSelectedRecords();
        final List<TreeNode> nodes = new ArrayList<TreeNode>();
        if (selection != null) {
            for (final ListGridRecord record : selection) {
                if (record instanceof TreeNode) {
                    nodes.add((TreeNode) record);
                }
            }
        }
        return nodes;
    }

    /**
     * Returns the titles of selected items (if any).
     */
    public String[] getSelectionTitles() {
        if (this.treeGrid == null) {
            return new String[] {};
        }

        final ListGridRecord[] selection = this.treeGrid.getSelectedRecords();

        if (selection == null) {
            return new String[] {};
        }

        final List<String> titleList = new ArrayList<String>();
        for (final ListGridRecord record : selection) {
            if (record instanceof TreeNode) {
                titleList.add(((TreeNode) record).getAttribute("name"));
            }
        }

        return titleList.toArray(new String[titleList.size()]);
    }

    /**
     * Callback for add-button.
     * Fires to load for every selected element and handler.
     */
    protected final void plusClicked() {
        if (!getSelection().isEmpty()) {
            fireToLoad();
        }
    }

    private Button createPlusButton() {
        final Button plusBtn = new Button(MSG.datacageAdd());
        plusBtn.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(final ClickEvent event) {
                plusClicked();
            }
        });
        return plusBtn;
    }

    private void fireToLoad() {
        for (final DatacageHandler handler : this.handlers) {
            handler.toLoad(this.toLoad);
        }
    }

    /** Notify DatacageDoubleClickHandlers that a doubleclick happened. */
    private void fireOnDoubleClick() {
        for (final DatacageDoubleClickHandler handler : this.doubleHandlers) {
            handler.onDoubleClick(this.toLoad);
        }
    }

    protected final void doubleClickedOnTree(final RecordDoubleClickEvent event) {
        final TreeNode node = (TreeNode) event.getRecord();
        collectToLoads(node);
        fireOnDoubleClick();
    }

    /**
     * Adds to toLoad, from info in node.
     * Afterwards, add all children of node to stack to parse (next time
     * collectToLoads is called).
     */
    private void collectToLoads(final TreeNode root1) {
        final Stack<TreeNode> stack = new Stack<TreeNode>();

        stack.push(root1);

        while (!stack.isEmpty()) {
            final TreeNode node = stack.pop();
            final String factory = node.getAttribute("factory");
            if (factory != null) { // we need at least a factory
                final String artifact = node.getAttribute("artifact-id");
                final String out = node.getAttribute("out");
                final String name = node.getAttribute("facet");
                final String ids = node.getAttribute("ids");
                final String displayname = node.getAttribute("name");
                final String targetOut = node.getAttribute("target_out");
                String debugAttributeValues = "";
                for (final String attr : node.getAttributes()) {
                    debugAttributeValues += ("[" + attr + ": " + node.getAttributeAsString(attr) + "] ");
                }
                GWT.log("DatacageWidget.collectToLoad, attributes are " + debugAttributeValues);

                this.toLoad.add(artifact, factory, out, name, ids, displayname, targetOut);
            }
            final TreeNode[] children = this.tree.getChildren(node);
            if (children != null) {
                for (final TreeNode child : children) {
                    stack.push(child);
                }
            }
        }
    }

    /** Get meta-data and populate tree with it. */
    private void triggerTreeBuilding() {
        lockUI();

        final String locale = Config.getInstance().getLocale();

        final String artifactId = this.data.getArtifact().getUuid();
        final User user = this.data.getUser();
        final String userId = user != null ? user.identifier() : null;
        final String outs = this.data.getOuts();
        final String parameters = this.data.getParameters();

        this.metaDataService.getMetaData(locale, artifactId, userId, outs, parameters, new AsyncCallback<DataCageTree>() {
            @Override
            public void onFailure(final Throwable caught) {
                GWT.log("Could not load meta data.");
                SC.warn(caught.getMessage());
                unlockUI();
            }

            @Override
            public void onSuccess(final DataCageTree dcData) {
                GWT.log("Successfully loaded meta data.");

                setDatacageData(dcData);
                unlockUI();
            }
        });
    }

    protected final void setDatacageData(final DataCageTree dcTree) {

        this.dcTree = dcTree;

        if (this.tree != null)
            this.tree.destroy();

        updateTree(this.dcTree);
    }

    private void updateTree(final DataCageTree dcData) {

        this.tree = new Tree();
        this.tree.setModelType(TreeModelType.CHILDREN);
        this.tree.setNameProperty("name");
        this.tree.setIdField("id");
        this.tree.setChildrenProperty("children-nodes");
        this.tree.setShowRoot(false);

        final IdGenerator idGenerator = new IdGenerator();
        final DataCageNode dcRoot = dcData.getRoot();
        final TreeNode root = buildRecursiveChildren(dcRoot, idGenerator);
        if (root != null) {
            this.tree.setRoot(root);

            // FIXME: why is this necessary? an it only happens for the first level...
            final TreeNode[] nodes = this.tree.getChildren(root);
            for (final TreeNode node : nodes) {
                if (node.getAttribute("factory") == null && !this.tree.hasChildren(node))
                    node.setIsFolder(true);
            }
        }

        if (idGenerator.current() < MAX_OPEN)
            this.tree.openAll();

        this.treeGrid.setData(this.tree);
    }

    private static final class IdGenerator {
        protected int current;

        public IdGenerator() {
        }

        public int next() {
            return this.current++;
        }

        public int current() {
            return this.current;
        }
    } // class IdGenerator

    private String i18n(final String s) {
        if (!(s.startsWith("${") && s.endsWith("}")))
            return s;

        final String sub = s.substring(2, s.length() - 1);

        try {
            return MSG.getString(sub);
        }
        catch (final MissingResourceException mre) {
            GWT.log("cannot find i18n for + '" + sub + "'", mre);
            return sub;
        }
    }

    private TreeNode buildRecursiveChildren(final DataCageNode node, final IdGenerator idGenerator) {

        if (!this.filter.accept(node))
            return null;

        final List<DataCageNode> children = node.getChildren();

        final Collection<TreeNode> tns = new ArrayList<TreeNode>();

        if (children != null && !children.isEmpty()) {

            for (final DataCageNode child : children) {

                final TreeNode childNode = buildRecursiveChildren(child, idGenerator);
                if (childNode != null)
                    tns.add(childNode);
            }

            /* if we should have children, but all got filtered, hide this node as well */
            if (tns.isEmpty())
                return null;
        }

        final TreeNode tn = new TreeNode();
        tn.setAttribute("id", idGenerator.next());

        if (!tns.isEmpty())
            tn.setAttribute("children-nodes", tns.toArray(new TreeNode[tns.size()]));

        tn.setAttribute("name", i18n(node.getDescription()));
        tn.setAttribute("facet", node.getName());

        final AttrList attrs = node.getAttributes();
        if (attrs != null) {
            for (int i = 0, N = attrs.size(); i < N; ++i) {
                final String key = attrs.getKey(i);
                final String value = attrs.getValue(i);
                tn.setAttribute(key, value);
            }
        }

        return tn;
    }
}

http://dive4elements.wald.intevation.org