/******************************************************************************* * Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com) * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v3 * which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt ******************************************************************************/ package com.opendoorlogistics.studio.scripts.list; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.File; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.TreeMap; import javax.swing.Icon; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.event.TreeSelectionListener; import javax.swing.plaf.basic.BasicTreeUI; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import com.opendoorlogistics.core.scripts.ScriptsProvider; import com.opendoorlogistics.core.scripts.ScriptsProvider.HasScriptsProvider; import com.opendoorlogistics.core.scripts.elements.Option; import com.opendoorlogistics.core.scripts.elements.Script; import com.opendoorlogistics.core.scripts.elements.ScriptEditorType; import com.opendoorlogistics.core.scripts.io.ScriptIO; import com.opendoorlogistics.core.utils.strings.Strings; import com.opendoorlogistics.studio.scripts.editor.ScriptIcons; import com.opendoorlogistics.studio.scripts.execution.ScriptUIManager; class ScriptsTree implements HasScriptsProvider{ // final JTable table = new JTable(); final private JScrollPane listScrollPane; private ScriptNode[] items; final private JTree tree; final private TreeNode rootNode = new RootNode(); private TreeMap<ScriptEditorType, ArrayList<File>> byType; final private ScriptUIManager scriptUIManager; private class RootNode implements TreeNode{ @Override public boolean isLeaf() { return false; } @Override public TreeNode getParent() { return null; } @Override public int getIndex(TreeNode node) { if(isSingleNodeMode()){ for(int i =0 ; i < items[0].getChildCount() ; i++){ if(items[0].getChildAt(i) == node){ return i; } } } else{ for (int i = 0; items != null && i < items.length; i++) { if (items[i] == node) { return i; } } } return -1; } @Override public int getChildCount() { if(isSingleNodeMode()){ return items[0].getChildCount(); }else{ return items != null ? items.length : 0; } } @Override public TreeNode getChildAt(int childIndex) { if(isSingleNodeMode()){ return items[0].getChildAt(childIndex); }else{ return items[childIndex]; } } @Override public boolean getAllowsChildren() { return true; } @Override public Enumeration children() { return new Enumeration<TreeNode>() { int index = -1; @Override public TreeNode nextElement() { return getChildAt(++index); } @Override public boolean hasMoreElements() { return (index + 1) < getChildCount(); } }; } private boolean isSingleNodeMode(){ if(!scriptUIManager.getAppPermissions().isScriptDirectoryLocked()){ return false; } if(items==null || items.length!=1){ return false; } ScriptNode node = items[0]; if(node.isRunnable() || node.getChildCount()==0){ return false; } return true; } } ScriptsTree(final ScriptUIManager scriptUIManager,final JPopupMenu popup) { this.scriptUIManager = scriptUIManager; tree = new JTree(rootNode) { @Override public String getToolTipText(MouseEvent e) { TreePath path = tree.getPathForLocation(e.getX(), e.getY()); if (path != null && path.getLastPathComponent() != null) { ScriptNode node = (ScriptNode) path.getLastPathComponent(); return node.getTooltip(ScriptNode.isRunnable(node,scriptUIManager)); // if (node.isAvailable() == false) { // return "The format of this script is incorrect and it cannot be loaded."; // } else if (ScriptNode.isRunnable(node,scriptUIManager)) { // return "Press the button to run " + node.getFile().getName() + " or double click on its name to edit the script."; // } else { // return "Double click on the script's name to edit it."; // } } return null; } }; tree.setToolTipText(""); // set this otherwise getTooltip not called. tree.setRootVisible(false); tree.setCellRenderer(new ButtonCellRenderer(scriptUIManager)); tree.setCellEditor(new ButtonCellEditor(scriptUIManager)); tree.setEditable(true); tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); tree.setRowHeight(0); tree.addMouseListener(new MouseListener() { @Override public void mouseReleased(MouseEvent e) { ensureSelected(e); launchPopup(e); } private void ensureSelected(MouseEvent e) { // ensure correct one is selected TreePath path = tree.getPathForLocation(e.getX(), e.getY()); if (path != null) { tree.getSelectionModel().setSelectionPath(path); } } private void launchPopup(MouseEvent e) { if (e.isPopupTrigger()) { TreePath path = tree.getPathForLocation(e.getX(), e.getY()); if (path != null && path.getLastPathComponent() != null) { popup.show(e.getComponent(), e.getX(), e.getY()); } } } @Override public void mousePressed(MouseEvent e) { ensureSelected(e); launchPopup(e); } @Override public void mouseExited(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mouseClicked(MouseEvent e) { // System.out.println("mouse clicked " + e.getSource()); } }); BasicTreeUI ui = new BasicTreeUI(); tree.setUI(ui); ui.setLeftChildIndent(12); setFiles(new File[] {}); listScrollPane = new JScrollPane(tree); } void addTreeSelectionListener(TreeSelectionListener listener){ tree.getSelectionModel().addTreeSelectionListener(listener); } void updateAppearance() { if (tree != null) { tree.repaint(); } } JScrollPane getScrollPane() { return listScrollPane; } ScriptNode getSelectedValue() { if (tree != null && tree.getLastSelectedPathComponent() != null) { return ((ScriptNode) tree.getLastSelectedPathComponent()); } return null; } void setEnabled(boolean enabled) { tree.setEnabled(enabled); } void setSelected(ScriptNode node) { TreePath path = getPath(node); if (path != null) { tree.setSelectionPath(path); } } TreePath getPath(final ScriptNode findThis) { TreeNode root = (TreeNode) tree.getModel().getRoot(); class Parser { TreePath parse(TreeNode current, ArrayList<TreeNode> path) { // copy path path = new ArrayList<>(path); // add current path.add(current); // check for match if (ScriptNode.class.isInstance(current)) { ScriptNode node = (ScriptNode) current; if (node.getFile().equals(findThis.getFile()) && Strings.equalsStd(node.getOption().getOptionId(), findThis.getOption().getOptionId())) { return new TreePath(path.toArray()); } } // parse children for (int i = 0; i < current.getChildCount(); i++) { TreePath ret = parse((ScriptNode) current.getChildAt(i), path); if (ret != null) { return ret; } } return null; } } return new Parser().parse(root, new ArrayList<TreeNode>()); } JTree getTreeControl(){ return tree; } private ScriptNode buildTree(final File file, final Script script,Icon icon){ // always build the root node ScriptNode ret= new ScriptNode(rootNode, file, script, script, icon); class Builder{ void build(ScriptNode parent){ if(parent.getOption()!=null){ for(Option option:parent.getOption().getOptions()){ ScriptNode childNode = new ScriptNode(parent, file, script, option,ScriptIcons.getIcon(scriptUIManager.getApi(),option)); build(childNode); parent.addChild(childNode); } } } } new Builder().build(ret); return ret; } void setFiles(File[] files) { // save expanded state of the current trees HashMap<File, SaveExpandedState> state = SaveExpandedState.save(this); // System.out.println("1:" + state); this.byType = new TreeMap<>(); for (ScriptEditorType type : ScriptEditorType.values()) { byType.put(type, new ArrayList<File>()); } this.items = new ScriptNode[files.length]; ScriptIO scriptIO = ScriptIO.instance(); for (int i = 0; i < items.length; i++) { final File file = files[i]; Script script = null; ScriptEditorType type = null; try { script = scriptIO.fromFile(file); type = script.getScriptEditorUIType(); } catch (Throwable e) { // to do.. show as unavailable? } // save by type if(type!=null){ byType.get(type).add(file); } Icon icon = null; if (script != null) { icon = ScriptIcons.getIcon(scriptUIManager.getApi(),script); } items[i] = buildTree(file, script, icon); } // set the new model and expand all rows to a certain depth tree.setModel(new DefaultTreeModel(rootNode)); for (int i = 0; i < tree.getRowCount(); i++) { TreePath path = tree.getPathForRow(i); if(path == rootNode){ tree.expandRow(i); } else{ if(path!=null&&path.getLastPathComponent()!=null){ ScriptNode node = (ScriptNode)path.getLastPathComponent(); SaveExpandedState fileState = state.get(node.getFile()); if(fileState!=null){ Boolean expanded =node!=null&&node.getOption()!=null ?fileState.getByOptionId(node.getOption().getOptionId()):null; if(expanded!=null){ if(expanded){ tree.expandRow(i); }else{ tree.collapseRow(i); } } }else if(node.getDepth()<2){ tree.expandRow(i); } } } // tree.expandRow(i); } //System.out.println("2:" + state); } ScriptNode[] getScriptNodes() { return items; } public List<File> getScriptsByType(ScriptEditorType type) { return byType.get(type); } public List<ScriptNode> getLoadedScripts(){ ArrayList<ScriptNode> nodes = new ArrayList<>(); if(items!=null){ for(ScriptNode item:items){ if(item.getScript()!=null){ nodes.add(item); } } } return nodes; } @Override public ScriptsProvider getScriptsProvider() { final ArrayList<ScriptNode> nodes = new ArrayList<>(); final ArrayList<Script> scripts = new ArrayList<>(); if(items!=null){ for(ScriptNode item:items){ if(item.getScript()!=null){ nodes.add(item); scripts.add(item.getScript()); } } } return new ScriptsProvider() { @Override public Iterator<Script> iterator() { return scripts.iterator(); } @Override public int size() { return scripts.size(); } @Override public File getFile(int i) { return nodes.get(i).getFile(); } @Override public Script get(int i) { return scripts.get(i); } }; } }