teichmann@5861: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde teichmann@5861: * Software engineering by Intevation GmbH teichmann@5861: * teichmann@5993: * This file is Free Software under the GNU AGPL (>=v3) teichmann@5861: * and comes with ABSOLUTELY NO WARRANTY! Check out the teichmann@5993: * documentation coming with Dive4Elements River for details. teichmann@5861: */ teichmann@5861: teichmann@5835: package org.dive4elements.river.client.client.ui; sascha@848: teichmann@5871: import java.util.ArrayList; gernotbelger@9220: import java.util.Collection; teichmann@5871: import java.util.List; teichmann@5871: import java.util.MissingResourceException; teichmann@5871: import java.util.Stack; teichmann@5871: teichmann@5835: import org.dive4elements.river.client.client.Config; teichmann@5835: import org.dive4elements.river.client.client.FLYSConstants; teichmann@5835: import org.dive4elements.river.client.client.event.DatacageDoubleClickHandler; teichmann@5835: import org.dive4elements.river.client.client.event.DatacageHandler; teichmann@5835: import org.dive4elements.river.client.client.services.MetaDataService; teichmann@5835: import org.dive4elements.river.client.client.services.MetaDataServiceAsync; teichmann@5835: import org.dive4elements.river.client.shared.model.AttrList; teichmann@5835: import org.dive4elements.river.client.shared.model.DataCageNode; teichmann@5835: import org.dive4elements.river.client.shared.model.DataCageTree; teichmann@5835: import org.dive4elements.river.client.shared.model.ToLoad; teichmann@5835: import org.dive4elements.river.client.shared.model.User; sascha@848: gernotbelger@9220: import com.google.gwt.core.client.GWT; gernotbelger@9220: import com.google.gwt.user.client.rpc.AsyncCallback; gernotbelger@9220: import com.smartgwt.client.types.SelectionStyle; gernotbelger@9220: import com.smartgwt.client.types.TreeModelType; gernotbelger@9220: import com.smartgwt.client.util.SC; gernotbelger@9220: import com.smartgwt.client.widgets.Button; gernotbelger@9220: import com.smartgwt.client.widgets.events.ClickEvent; gernotbelger@9220: import com.smartgwt.client.widgets.events.ClickHandler; gernotbelger@9220: import com.smartgwt.client.widgets.grid.HoverCustomizer; gernotbelger@9220: import com.smartgwt.client.widgets.grid.ListGridRecord; gernotbelger@9220: import com.smartgwt.client.widgets.grid.events.RecordDoubleClickEvent; gernotbelger@9220: import com.smartgwt.client.widgets.grid.events.RecordDoubleClickHandler; gernotbelger@9220: import com.smartgwt.client.widgets.layout.VLayout; gernotbelger@9220: import com.smartgwt.client.widgets.tree.Tree; gernotbelger@9220: import com.smartgwt.client.widgets.tree.TreeGrid; gernotbelger@9220: import com.smartgwt.client.widgets.tree.TreeNode; gernotbelger@9220: felix@897: // TODO: refactor, extract ~DataCageGrid felix@1283: /** felix@1283: * Display tree of, for example, previous calculations and allows felix@1283: * selection in order to access/clone these. felix@1283: */ gernotbelger@9227: public final class DatacageWidget extends VLayout { gernotbelger@9227: gernotbelger@9227: private static final FLYSConstants MSG = GWT.create(FLYSConstants.class); gernotbelger@9227: sascha@1325: public static final int MAX_OPEN = 30; sascha@1325: gernotbelger@9220: public interface DatacageFilter { gernotbelger@9220: boolean accept(DataCageNode node); sascha@848: } sascha@848: gernotbelger@9220: public static final DatacageFilter ACCEPT_ALL_FILTER = new DatacageFilter() { gernotbelger@9220: @Override gernotbelger@9220: public boolean accept(final DataCageNode node) { gernotbelger@9220: return true; gernotbelger@9220: } gernotbelger@9220: }; felix@868: gernotbelger@9227: public static final DatacageFilter ACCEPT_NONE_FILTER = new DatacageFilter() { gernotbelger@9227: @Override gernotbelger@9227: public boolean accept(final DataCageNode node) { gernotbelger@9227: return false; gernotbelger@9227: } gernotbelger@9227: }; gernotbelger@9227: gernotbelger@9220: private final MetaDataServiceAsync metaDataService = GWT.create(MetaDataService.class); ingo@916: gernotbelger@9220: private final List handlers = new ArrayList(); sascha@848: gernotbelger@9220: private final List doubleHandlers = new ArrayList(); gernotbelger@9220: gernotbelger@9220: private final TreeGrid treeGrid; gernotbelger@9220: gernotbelger@9220: private Tree tree; gernotbelger@9220: gernotbelger@9220: private ToLoad toLoad; gernotbelger@9220: gernotbelger@9220: /** Layout to show spinning wheel of joy. */ gernotbelger@9220: private VLayout lockScreen; gernotbelger@9220: gernotbelger@9220: private DataCageTree dcTree; gernotbelger@9220: gernotbelger@9220: private DatacageFilter filter = ACCEPT_ALL_FILTER; gernotbelger@9220: gernotbelger@9220: private DatacageWidgetData data; gernotbelger@9220: gernotbelger@9220: public DatacageWidget(final DatacageWidgetData data) { gernotbelger@9220: gernotbelger@9220: this.data = data; gernotbelger@9220: gernotbelger@9220: this.toLoad = new ToLoad(); sascha@848: sascha@848: setWidth100(); sascha@848: gernotbelger@9220: this.treeGrid = new TreeGrid(); sascha@848: gernotbelger@9220: final String columnLabel = this.data.getColumnLabel(); gernotbelger@9220: if (columnLabel != null) gernotbelger@9220: this.treeGrid.setTreeFieldTitle(columnLabel); sascha@848: gernotbelger@9220: this.treeGrid.setLoadDataOnDemand(false); gernotbelger@9220: this.treeGrid.setWidth100(); gernotbelger@9220: this.treeGrid.setHeight100(); gernotbelger@9220: this.treeGrid.setShowRoot(false); gernotbelger@9220: this.treeGrid.setNodeIcon("[SKIN]/../blank.gif"); gernotbelger@9220: this.treeGrid.setShowConnectors(true); gernotbelger@9227: this.treeGrid.setLoadingMessage(MSG.databasket_loading()); gernotbelger@9227: this.treeGrid.setLoadingDataMessage(MSG.databasket_loading()); gernotbelger@9227: this.treeGrid.setEmptyMessage(MSG.databasket_empty()); gernotbelger@9220: gernotbelger@9220: this.treeGrid.setHoverMoveWithMouse(true); gernotbelger@9220: this.treeGrid.setCanHover(true); gernotbelger@9220: this.treeGrid.setShowHover(true); gernotbelger@9220: this.treeGrid.setHoverOpacity(75); gernotbelger@9220: this.treeGrid.setHoverWidth(120); gernotbelger@9220: gernotbelger@9220: this.treeGrid.setHoverCustomizer(new HoverCustomizer() { felix@3475: @Override gernotbelger@9220: public String hoverHTML(final Object value, final ListGridRecord record, final int rowNum, final int colNum) { gernotbelger@9220: if (record instanceof TreeNode) { gernotbelger@9220: final TreeNode hoveredTreeNode = (TreeNode) record; felix@3475: String info = hoveredTreeNode.getAttribute("info"); felix@3475: if (info == null) { felix@3475: info = hoveredTreeNode.getName(); felix@3475: } felix@3475: return info; felix@3475: } gernotbelger@9220: gernotbelger@9220: return "";// should not happen felix@3475: } felix@3475: }); felix@3475: gernotbelger@9220: this.treeGrid.addRecordDoubleClickHandler(new RecordDoubleClickHandler() { sascha@848: @Override gernotbelger@9220: public void onRecordDoubleClick(final RecordDoubleClickEvent event) { sascha@848: doubleClickedOnTree(event); sascha@848: } sascha@848: }); sascha@848: gernotbelger@9220: addMember(this.treeGrid); sascha@848: gernotbelger@9220: if (data.isShowButton()) felix@897: addMember(createPlusButton()); sascha@848: sascha@848: triggerTreeBuilding(); sascha@848: } sascha@848: gernotbelger@9220: public void setFilter(final DatacageFilter filter) { gernotbelger@9220: assert (filter != null); gernotbelger@9220: gernotbelger@9220: this.filter = filter; gernotbelger@9220: gernotbelger@9220: if (this.dcTree != null) gernotbelger@9220: updateTree(this.dcTree); gernotbelger@9220: } gernotbelger@9220: gernotbelger@9227: public TreeGrid getTreeGrid() { gernotbelger@9227: return this.treeGrid; gernotbelger@9227: } gernotbelger@9227: felix@6042: /** Disable input, show spinning wheel of joy. */ gernotbelger@9220: private void lockUI() { gernotbelger@9220: this.lockScreen = ScreenLock.lockUI(this, this.lockScreen); felix@5488: } felix@5488: felix@5488: /** Enable input, remove grey, remove spinning wheel of joy. */ gernotbelger@9220: protected final void unlockUI() { gernotbelger@9220: ScreenLock.unlockUI(this, this.lockScreen); felix@5488: } felix@897: felix@897: /** gernotbelger@9220: * Sets whether more than one item can be selected. gernotbelger@9220: * gernotbelger@9220: * @param multi gernotbelger@9220: * if true, allow mutliple selections. felix@897: */ gernotbelger@9220: public void setIsMutliSelectable(final boolean multi) { ingo@1268: if (multi) { gernotbelger@9220: this.treeGrid.setSelectionType(SelectionStyle.MULTIPLE); gernotbelger@9220: } else { gernotbelger@9220: this.treeGrid.setSelectionType(SelectionStyle.SINGLE); ingo@1268: } ingo@1268: } ingo@1268: felix@897: /** gernotbelger@9220: * @param handler gernotbelger@9220: * Handler to be added (notified on add-action). felix@897: */ gernotbelger@9220: public void addDatacageHandler(final DatacageHandler handler) { gernotbelger@9220: if (!this.handlers.contains(handler)) { gernotbelger@9220: this.handlers.add(handler); ingo@1267: } ingo@1267: } ingo@1267: gernotbelger@9220: /** gernotbelger@9220: * @param h gernotbelger@9220: * Handler to be added (notified on Double click on node). gernotbelger@9220: */ gernotbelger@9220: public void addDatacageDoubleClickHandler(final DatacageDoubleClickHandler h) { gernotbelger@9220: if (!this.doubleHandlers.contains(h)) { gernotbelger@9220: this.doubleHandlers.add(h); gernotbelger@9220: } gernotbelger@9220: } ingo@1267: ingo@1267: /** gernotbelger@9220: * @param handler gernotbelger@9220: * Handler to remove from list. felix@897: */ gernotbelger@9220: public void removeDatacageHandler(final DatacageHandler handler) { gernotbelger@9220: this.handlers.remove(handler); sascha@848: } sascha@848: felix@897: public ToLoad getSelection() { felix@897: // Reset content of toLoads. gernotbelger@9227: gernotbelger@9227: // FIXME: bad... instead we should react to selection events and update toLoad in this way. gernotbelger@9227: gernotbelger@9220: this.toLoad = new ToLoad(); felix@897: gernotbelger@9227: if (this.treeGrid == null) gernotbelger@9220: return this.toLoad; sascha@848: gernotbelger@9220: final ListGridRecord[] selection = this.treeGrid.getSelectedRecords(); sascha@848: sascha@848: if (selection != null) { gernotbelger@9220: for (final ListGridRecord record : selection) { sascha@848: if (record instanceof TreeNode) { gernotbelger@9220: collectToLoads((TreeNode) record); sascha@848: } sascha@848: } sascha@848: } sascha@848: gernotbelger@9220: return this.toLoad; felix@897: } felix@897: rrenkert@7971: public List getPlainSelection() { gernotbelger@9220: final ListGridRecord[] selection = this.treeGrid.getSelectedRecords(); gernotbelger@9220: final List nodes = new ArrayList(); rrenkert@7971: if (selection != null) { gernotbelger@9220: for (final ListGridRecord record : selection) { rrenkert@7971: if (record instanceof TreeNode) { gernotbelger@9220: nodes.add((TreeNode) record); rrenkert@7971: } rrenkert@7971: } rrenkert@7971: } rrenkert@7971: return nodes; rrenkert@7971: } rrenkert@7971: felix@897: /** felix@1283: * Returns the titles of selected items (if any). felix@1283: */ felix@1283: public String[] getSelectionTitles() { gernotbelger@9220: if (this.treeGrid == null) { felix@1283: return new String[] {}; felix@1283: } felix@1283: gernotbelger@9220: final ListGridRecord[] selection = this.treeGrid.getSelectedRecords(); felix@1283: felix@1283: if (selection == null) { felix@1283: return new String[] {}; felix@1283: } felix@1283: gernotbelger@9220: final List titleList = new ArrayList(); gernotbelger@9220: for (final ListGridRecord record : selection) { felix@1283: if (record instanceof TreeNode) { gernotbelger@9220: titleList.add(((TreeNode) record).getAttribute("name")); felix@1283: } felix@1283: } felix@1283: felix@1283: return titleList.toArray(new String[titleList.size()]); felix@1283: } felix@1283: felix@1283: /** felix@897: * Callback for add-button. felix@897: * Fires to load for every selected element and handler. felix@897: */ gernotbelger@9220: protected final void plusClicked() { felix@897: if (!getSelection().isEmpty()) { sascha@848: fireToLoad(); sascha@848: } sascha@848: } sascha@848: gernotbelger@9220: private Button createPlusButton() { gernotbelger@9227: final Button plusBtn = new Button(MSG.datacageAdd()); felix@897: plusBtn.addClickHandler(new ClickHandler() { felix@897: @Override gernotbelger@9220: public void onClick(final ClickEvent event) { felix@897: plusClicked(); felix@897: } felix@897: }); felix@897: return plusBtn; felix@897: } felix@897: gernotbelger@9220: private void fireToLoad() { gernotbelger@9220: for (final DatacageHandler handler : this.handlers) { gernotbelger@9220: handler.toLoad(this.toLoad); sascha@848: } sascha@848: } sascha@848: felix@1333: /** Notify DatacageDoubleClickHandlers that a doubleclick happened. */ gernotbelger@9220: private void fireOnDoubleClick() { gernotbelger@9220: for (final DatacageDoubleClickHandler handler : this.doubleHandlers) { gernotbelger@9220: handler.onDoubleClick(this.toLoad); ingo@1267: } ingo@1267: } ingo@1267: gernotbelger@9220: protected final void doubleClickedOnTree(final RecordDoubleClickEvent event) { gernotbelger@9220: final TreeNode node = (TreeNode) event.getRecord(); sascha@848: collectToLoads(node); ingo@1267: fireOnDoubleClick(); sascha@848: } sascha@848: felix@1349: /** felix@1349: * Adds to toLoad, from info in node. felix@1349: * Afterwards, add all children of node to stack to parse (next time felix@1349: * collectToLoads is called). felix@1349: */ gernotbelger@9227: private void collectToLoads(final TreeNode root1) { gernotbelger@9220: final Stack stack = new Stack(); sascha@848: gernotbelger@9227: stack.push(root1); sascha@848: sascha@848: while (!stack.isEmpty()) { gernotbelger@9227: final TreeNode node = stack.pop(); gernotbelger@9220: final String factory = node.getAttribute("factory"); sascha@848: if (factory != null) { // we need at least a factory gernotbelger@9220: final String artifact = node.getAttribute("artifact-id"); gernotbelger@9220: final String out = node.getAttribute("out"); gernotbelger@9220: final String name = node.getAttribute("facet"); gernotbelger@9220: final String ids = node.getAttribute("ids"); gernotbelger@9220: final String displayname = node.getAttribute("name"); gernotbelger@9220: final String targetOut = node.getAttribute("target_out"); felix@1349: String debugAttributeValues = ""; gernotbelger@9220: for (final String attr : node.getAttributes()) { gernotbelger@9220: debugAttributeValues += ("[" + attr + ": " + node.getAttributeAsString(attr) + "] "); felix@1349: } gernotbelger@9220: GWT.log("DatacageWidget.collectToLoad, attributes are " + debugAttributeValues); sascha@848: gernotbelger@9220: this.toLoad.add(artifact, factory, out, name, ids, displayname, targetOut); sascha@848: } gernotbelger@9220: final TreeNode[] children = this.tree.getChildren(node); sascha@848: if (children != null) { gernotbelger@9220: for (final TreeNode child : children) { sascha@848: stack.push(child); sascha@848: } sascha@848: } sascha@848: } sascha@848: } sascha@848: felix@3475: /** Get meta-data and populate tree with it. */ gernotbelger@9220: private void triggerTreeBuilding() { felix@5488: lockUI(); felix@5488: gernotbelger@9220: final String locale = Config.getInstance().getLocale(); sascha@848: gernotbelger@9220: final String artifactId = this.data.getArtifact().getUuid(); gernotbelger@9220: final User user = this.data.getUser(); gernotbelger@9220: final String userId = user != null ? user.identifier() : null; gernotbelger@9220: final String outs = this.data.getOuts(); gernotbelger@9220: final String parameters = this.data.getParameters(); raimund@2478: gernotbelger@9220: this.metaDataService.getMetaData(locale, artifactId, userId, outs, parameters, new AsyncCallback() { gernotbelger@9220: @Override gernotbelger@9220: public void onFailure(final Throwable caught) { gernotbelger@9220: GWT.log("Could not load meta data."); gernotbelger@9220: SC.warn(caught.getMessage()); gernotbelger@9220: unlockUI(); gernotbelger@9220: } raimund@2478: gernotbelger@9220: @Override gernotbelger@9220: public void onSuccess(final DataCageTree dcData) { gernotbelger@9220: GWT.log("Successfully loaded meta data."); gernotbelger@9220: gernotbelger@9220: setDatacageData(dcData); gernotbelger@9220: unlockUI(); gernotbelger@9220: } gernotbelger@9220: }); gernotbelger@9220: } gernotbelger@9220: gernotbelger@9220: protected final void setDatacageData(final DataCageTree dcTree) { gernotbelger@9220: gernotbelger@9220: this.dcTree = dcTree; gernotbelger@9220: gernotbelger@9220: if (this.tree != null) gernotbelger@9220: this.tree.destroy(); gernotbelger@9220: gernotbelger@9220: updateTree(this.dcTree); gernotbelger@9220: } gernotbelger@9220: gernotbelger@9227: private void updateTree(final DataCageTree dcData) { gernotbelger@9220: gernotbelger@9220: this.tree = new Tree(); gernotbelger@9220: this.tree.setModelType(TreeModelType.CHILDREN); gernotbelger@9220: this.tree.setNameProperty("name"); gernotbelger@9220: this.tree.setIdField("id"); gernotbelger@9220: this.tree.setChildrenProperty("children-nodes"); gernotbelger@9220: this.tree.setShowRoot(false); gernotbelger@9220: gernotbelger@9220: final IdGenerator idGenerator = new IdGenerator(); gernotbelger@9227: final DataCageNode dcRoot = dcData.getRoot(); gernotbelger@9220: final TreeNode root = buildRecursiveChildren(dcRoot, idGenerator); gernotbelger@9220: if (root != null) { gernotbelger@9220: this.tree.setRoot(root); gernotbelger@9220: gernotbelger@9220: // FIXME: why is this necessary? an it only happens for the first level... gernotbelger@9220: final TreeNode[] nodes = this.tree.getChildren(root); gernotbelger@9220: for (final TreeNode node : nodes) { gernotbelger@9220: if (node.getAttribute("factory") == null && !this.tree.hasChildren(node)) gernotbelger@9220: node.setIsFolder(true); gernotbelger@9220: } gernotbelger@9220: } gernotbelger@9220: gernotbelger@9220: if (idGenerator.current() < MAX_OPEN) gernotbelger@9220: this.tree.openAll(); gernotbelger@9220: gernotbelger@9220: this.treeGrid.setData(this.tree); sascha@848: } sascha@848: sascha@1325: private static final class IdGenerator { sascha@848: protected int current; sascha@848: sascha@848: public IdGenerator() { sascha@848: } sascha@848: sascha@1325: public int next() { gernotbelger@9220: return this.current++; sascha@848: } sascha@848: sascha@1325: public int current() { gernotbelger@9220: return this.current; sascha@848: } sascha@848: } // class IdGenerator sascha@848: gernotbelger@9227: private String i18n(final String s) { gernotbelger@9227: if (!(s.startsWith("${") && s.endsWith("}"))) sascha@1325: return s; sascha@1325: gernotbelger@9227: final String sub = s.substring(2, s.length() - 1); sascha@1325: sascha@1325: try { gernotbelger@9227: return MSG.getString(sub); sascha@1325: } gernotbelger@9220: catch (final MissingResourceException mre) { gernotbelger@9227: GWT.log("cannot find i18n for + '" + sub + "'", mre); gernotbelger@9227: return sub; sascha@1325: } sascha@1325: } sascha@1325: gernotbelger@9220: private TreeNode buildRecursiveChildren(final DataCageNode node, final IdGenerator idGenerator) { gernotbelger@9220: gernotbelger@9220: if (!this.filter.accept(node)) gernotbelger@9220: return null; gernotbelger@9220: gernotbelger@9220: final List children = node.getChildren(); gernotbelger@9220: gernotbelger@9220: final Collection tns = new ArrayList(); gernotbelger@9220: gernotbelger@9220: if (children != null && !children.isEmpty()) { gernotbelger@9220: gernotbelger@9220: for (final DataCageNode child : children) { gernotbelger@9220: gernotbelger@9220: final TreeNode childNode = buildRecursiveChildren(child, idGenerator); gernotbelger@9220: if (childNode != null) gernotbelger@9220: tns.add(childNode); gernotbelger@9220: } gernotbelger@9220: gernotbelger@9220: /* if we should have children, but all got filtered, hide this node as well */ gernotbelger@9220: if (tns.isEmpty()) gernotbelger@9220: return null; gernotbelger@9220: } gernotbelger@9220: gernotbelger@9220: final TreeNode tn = new TreeNode(); sascha@1325: tn.setAttribute("id", idGenerator.next()); sascha@1325: gernotbelger@9220: if (!tns.isEmpty()) gernotbelger@9220: tn.setAttribute("children-nodes", tns.toArray(new TreeNode[tns.size()])); sascha@1325: sascha@1325: tn.setAttribute("name", i18n(node.getDescription())); ingo@892: tn.setAttribute("facet", node.getName()); sascha@1325: gernotbelger@9220: final AttrList attrs = node.getAttributes(); sascha@848: if (attrs != null) { sascha@848: for (int i = 0, N = attrs.size(); i < N; ++i) { gernotbelger@9220: final String key = attrs.getKey(i); gernotbelger@9220: final String value = attrs.getValue(i); sascha@848: tn.setAttribute(key, value); sascha@848: } sascha@848: } sascha@848: sascha@1325: return tn; sascha@848: } gernotbelger@9220: }