view gwt-client/src/main/java/org/dive4elements/river/client/client/ui/DatacageWidget.java @ 9416:05405292a7ca

Navigationtheme panel now shows themes of dWt and WQ charts grayed out, if the current station is outside the valid range of the theme.
author gernotbelger
date Thu, 16 Aug 2018 16:28:03 +0200
parents 84397da33d17
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