org.primordion.xholon.io.console.XholonConsole.java Source code

Java tutorial

Introduction

Here is the source code for org.primordion.xholon.io.console.XholonConsole.java

Source

/* Xholon Runtime Framework - executes event-driven & dynamic applications
 * Copyright (C) 2009 Ken Webb
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 */

package org.primordion.xholon.io.console;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.TextAreaElement;
//import com.google.gwt.event.dom.client.ClickEvent;
//import com.google.gwt.event.dom.client.ClickHandler;
//import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
//import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
//import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.MenuItem;
//import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.Widget;
//import com.google.gwt.user.client.Element;

import java.util.List;

//import org.primordion.xholon.app.Application;
import org.primordion.xholon.app.IApplication;
//import org.primordion.xholon.base.Control;
//import org.primordion.xholon.base.IControl;
import org.primordion.xholon.base.IMessage;
import org.primordion.xholon.base.IQueue;
import org.primordion.xholon.base.IReflection;
import org.primordion.xholon.base.ISignal;
import org.primordion.xholon.base.IXholon;
import org.primordion.xholon.base.IXholonClass;
import org.primordion.xholon.base.Queue;
import org.primordion.xholon.base.ReflectionFactory;
import org.primordion.xholon.base.XholonWithPorts;
import org.primordion.xholon.io.Specials;
import org.primordion.xholon.io.ISwingEntity;
import org.primordion.xholon.io.XholonGwtTabPanelHelper;
//import org.primordion.xholon.io.xml.IXml2Xholon;
//import org.primordion.xholon.io.xml.Xml2Xholon;
//import org.primordion.xholon.io.xml.Xml2XholonClass;
import org.primordion.xholon.service.IXholonService;
import org.primordion.xholon.service.NodeSelectionService;
import org.primordion.xholon.service.XholonHelperService;
//import org.primordion.xholon.util.MiscIo;

/**
 * Provide an Xholon console capability for interacting with a Xholon application.
 * A XholonConsole can be opened for any node in the Xholon tree.
 * @author <a href="mailto:ken@primordion.com">Ken Webb</a>
 * @see <a href="http://www.primordion.com/Xholon">Xholon Project website</a>
 * @since 0.8.1 (Created on October 15, 2009)
 */
public class XholonConsole extends XholonWithPorts implements IXholonConsole {

    interface XhConsUiBinder extends UiBinder<Widget, XholonConsole> {
    }

    private static XhConsUiBinder uiBinder = GWT.create(XhConsUiBinder.class);

    // Special menu
    @UiField
    MenuItem special;

    // History menu
    @UiField
    MenuItem history;

    // File menu
    @UiField
    MenuItem fileopen;
    @UiField
    MenuItem filesave;
    @UiField
    MenuItem filesaveas;
    @UiField
    MenuItem closeguiMI;

    // Edit menu
    @UiField
    MenuItem clearcommand;
    @UiField
    MenuItem increasefontsize;
    @UiField
    MenuItem decreasefontsize;

    // Mode menu
    @UiField
    MenuItem modedefault;
    @UiField
    MenuItem modexpath;
    @UiField
    MenuItem modejs;
    @UiField
    MenuItem modeattr;
    @UiField
    MenuItem terminal;

    // Help menu
    @UiField
    MenuItem help;
    @UiField
    MenuItem xpath;
    @UiField
    MenuItem jsscript;
    @UiField
    MenuItem behaviorScript;
    @UiField
    MenuItem behaviorScriptProto;
    @UiField
    MenuItem behaviorScriptInstance;

    @UiField
    MenuItem submit;

    // buttons
    //@UiField Button submit;
    //@UiField Button closeguiB;

    @UiField
    HTML commandPane;
    @UiField
    TextAreaElement commandPaneTAE;

    /** Path and name of the Inheritance Hierarchy XML file. */
    //private String xmlIhFilePathAndName = "./config/console/XholonConsole_InheritanceHierarchy.xml";

    /** Path and name of the Class Details XML file. */
    //private String xmlCdFilePathAndName = "./config/console/XholonConsole_ClassDetails.xml";

    /** Path and name of the Composite Structure Hierarchy XML file. */
    //private String xmlFilePathAndName = "./config/console/XholonConsole_CompositeStructureHierarchy.xml";

    /** The Application that the context node is running in. */
    private IApplication app = null;

    /** The Xholon node that the XholonConsole node is associated with. */
    IXholon context = null;

    /** The GUI pane where the user enters commands and text. */
    //private IXholon commandPane = null;

    /** The JLabel of the GUI pane where the user enters commands and text. */
    //private IXholon commandPaneLabel = null;

    /** The GUI pane where the user can view the results of a command. */
    //private IXholon resultPane = null;

    /** The GUI JFrame. */
    //private IXholon frame = null;

    /**
     * The GUI has one file open at a time.
     * If a file has already been opened during the lifetime of this XholonConsole instance,
     * then the latest such file will remain available for reuse, such as for the Save operation. */
    //private File latestOpenedFile = null;

    /** The name of the context node. */
    private String roleName = null;

    /**
     * A service that provides access to the XholonConsole.
     */
    protected XholonHelperService xholonHelperService = null;

    /**
     * A service that will keep track of which IXholon node(s) are currently selected.
     * This will allow other services or xholons to act on those nodes.*/
    protected IXholon nodeSelectionService = null;

    /**
     * An escape character that can be used as the first character of text.
     * It allows URIs such as "http:" to escape being interpreted as modes.
     * Enter a URI as ":http://www.example.com".
     */
    protected static final char MODE_ESCAPE = ':';

    /**
     * The value "default:" is only used when changing the mode to "".
     */
    protected static final String MODE_DEFAULT = "default:";

    /**
     * Possible values for mode.
     */
    protected String[] modes = { MODE_DEFAULT, "xpath:", "js:", "attr:" };

    /**
     * User friendly labels for the different modes.
     */
    protected String[] modeLabels = { "all features", "XPath navigation", "JavaScript scripting",
            "Attribute set and get" };

    /**
     * The language that the console is currently able to parse and respond to.
     * The mode can be set by selecting from a menu,
     * or through a command that consists of just the mode String (ex: "xpath:").
     * ex: ""default "xpath:" "js:" "attr:"
     * 
     */
    protected String mode = "";

    /**
     * An output device.
     */
    //protected IXholonConsole console = null;

    /**
     * The HTML widget that implements the XholonConsole.
     */
    protected Widget widget = null;

    protected static final int SENDMESSAGE_ASYNC = 1;
    protected static final int SENDMESSAGE_SYNC = 2;
    /**
     * Whether to call sendMessage() or sendSyncMessage().
     * 1 async (the default)
     * 2 sync  (used with Avatar)
     */
    protected int sendMsgAsyncOrSync = SENDMESSAGE_ASYNC;

    /**
     * Whether or not the console is acting as a terminal.
     */
    protected boolean terminalEnabled = false;

    /**
     * History circular queue.
     */
    protected IQueue historyQ = null;

    /**
     * Maximum size of the history circular queue.
     */
    protected int historyQSize = 20;

    /**
     * constructor
     */
    public XholonConsole() {
    }

    /**
     * constructor
     */
    public XholonConsole(IApplication app, IXholon context) {
        this.app = app;
        this.context = context;
    }

    /*
     * @see org.primordion.xholon.base.Xholon#getRoleName()
     */
    public String getRoleName() {
        return roleName;
    }

    /*
     * @see org.primordion.xholon.base.Xholon#setRoleName(java.lang.String)
     */
    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    /*
     * @see org.primordion.xholon.io.xquerygui.IXQueryGui#getXmlIhFilePathAndName()
     */
    //public String getXmlIhFilePathAndName() {
    //  return xmlIhFilePathAndName;
    //}

    /*
     * @see org.primordion.xholon.io.xquerygui.IXQueryGui#setXmlIhFilePathAndName(java.lang.String)
     */
    //public void setXmlIhFilePathAndName(String xmlIhFilePathAndName) {
    //  this.xmlIhFilePathAndName = xmlIhFilePathAndName;
    //}

    /*
     * @see org.primordion.xholon.io.xquerygui.IXQueryGui#getXmlCdFilePathAndName()
     */
    //public String getXmlCdFilePathAndName() {
    //  return xmlCdFilePathAndName;
    //}

    /*
     * @see org.primordion.xholon.io.xquerygui.IXQueryGui#setXmlCdFilePathAndName(java.lang.String)
     */
    //public void setXmlCdFilePathAndName(String xmlCdFilePathAndName) {
    //  this.xmlCdFilePathAndName = xmlCdFilePathAndName;
    //}

    /*
     * @see org.primordion.xholon.io.xquerygui.IXQueryGui#getXmlFilePathAndName()
     */
    //public String getXmlFilePathAndName() {
    //  return xmlFilePathAndName;
    //}

    /*
     * @see org.primordion.xholon.io.xquerygui.IXQueryGui#setXmlFilePathAndName(java.lang.String)
     */
    //public void setXmlFilePathAndName(String xmlFilePathAndName) {
    //  this.xmlFilePathAndName = xmlFilePathAndName;
    //}

    /*
     * @see org.primordion.xholon.io.xquerygui.IXQueryGui#getContext()
     */
    public IXholon getContext() {
        return context;
    }

    /*
     * @see org.primordion.xholon.io.xquerygui.IXQueryGui#setContext(org.primordion.xholon.base.IXholon)
     */
    public void setContext(IXholon context) {
        this.context = context;
    }

    /*
     * @see org.primordion.xholon.io.console.IXholonConsole#getCommandPane()
     */
    public IXholon getCommandPane() {
        return null; //commandPane;
    }

    /*
     * @see org.primordion.xholon.io.console.IXholonConsole#setCommandPane(org.primordion.xholon.base.IXholon)
     */
    public void setCommandPane(IXholon commandPane) {
        //this.commandPane = commandPane;
    }

    /*
     * @see org.primordion.xholon.io.xquerygui.IXQueryGui#getResultPane()
     */
    //public IXholon getResultPane() {
    //  return resultPane;
    //}

    /*
     * @see org.primordion.xholon.io.xquerygui.IXQueryGui#setResultPane(org.primordion.xholon.base.IXholon)
     */
    //public void setResultPane(IXholon resultPane) {
    //  this.resultPane = resultPane;
    //}

    /*
     * @see org.primordion.xholon.io.xquerygui.IXQueryGui#getLatestOpenedFile()
     */
    //public File getLatestOpenedFile() {
    //  return latestOpenedFile;
    //}

    /*
     * @see org.primordion.xholon.io.xquerygui.IXQueryGui#setLatestOpenedFile(java.io.File)
     */
    //public void setLatestOpenedFile(File latestOpenedFile) {
    //  this.latestOpenedFile = latestOpenedFile;
    //}

    public int getSendMsgAsyncOrSync() {
        return sendMsgAsyncOrSync;
    }

    public void setSendMsgAsyncOrSync(int sendMsgAsyncOrSync) {
        this.sendMsgAsyncOrSync = sendMsgAsyncOrSync;
    }

    /*
     * @see org.primordion.xholon.base.Xholon#getApp()
     */
    public IApplication getApp() {
        return app;
    }

    /*
     * @see org.primordion.xholon.base.Xholon#setApp(org.primordion.xholon.app.IApplication)
     */
    public void setApp(IApplication app) {
        this.app = app;
    }

    public String getMode() {
        return mode;
    }

    public void setMode(String mode) {
        if (mode.equals(MODE_DEFAULT)) {
            this.mode = "";
        } else {
            this.mode = mode;
        }
        //setTabHeader();
    }

    //public IXholonConsole getConsole() {
    //  return console;
    //}

    //public void setConsole(IXholonConsole console) {
    //  this.console = console;
    //}

    public Object getWidget() {
        return widget;
    }

    public boolean isTerminalEnabled() {
        return terminalEnabled;
    }

    public void setTerminalEnabled(boolean terminalEnabled) {
        this.terminalEnabled = terminalEnabled;
    }

    /*
     * @see org.primordion.xholon.base.XholonWithPorts#postConfigure()
     */
    public void postConfigure() {
        // set various Xholon attributes for XholonConsole
        IXholonClass ixc = app.getClassNode("XholonConsole");
        if (ixc != null) {
            setXhc(ixc);
        }
        setId(app.getNextId());
        setRoleName(context.getName());

        if ((context.getXhc() != null) && context.getXhc().hasAncestor("Avatar")) {
            setTerminalEnabled(true);
            sendMsgAsyncOrSync = SENDMESSAGE_SYNC;
        }

        historyQ = new Queue(historyQSize);

        // append this new node to the View
        /*
        IXholon view = app.getAppRoot().getFirstChild();
        while (view != null) {
          if ("View".equals(view.getRoleName())) {
            break;
          }
          view = view.getNextSibling();
        }
        if (view != null) {
          IXholon viewXholonConsole = view.getFirstChild();
          while (viewXholonConsole != null) {
            if ("XholonConsoles".equals(viewXholonConsole.getRoleName())) {
              break;
            }
            viewXholonConsole = viewXholonConsole.getNextSibling();
          }
          if (viewXholonConsole == null) {
            // create a container XholonConsole node
            viewXholonConsole =
              ((IControl)view).setControlNode("XholonConsoles", (Control)view, view.getXhc());
          }
          appendChild(viewXholonConsole);
        }
        */
        readGuiFromXml();

        // Special menu
        String specialXml = app.rcConfig("special", app.findGwtClientBundle());
        if (specialXml != null) {
            //node.consoleLog("special.xml");
            //node.consoleLog(specialXml);
            final Specials specials = new Specials();
            specials.xmlString2Items(specialXml, this);
            String[] moreSpecialItems = specials.getActionList();
            if (moreSpecialItems != null) {
                MenuBar specialSubMenu = new MenuBar(true);
                for (int i = 0; i < moreSpecialItems.length; i++) {
                    final String actionName = moreSpecialItems[i];
                    specialSubMenu.addItem(actionName, new Command() {
                        @Override
                        public void execute() {
                            //context.consoleLog("execute() " + actionName);
                            specials.doAction(actionName);
                        }
                    });
                }
                special.setSubMenu(specialSubMenu);
            }
        } else {
            //special.setEnabled(false);
            special.getParentMenu().removeItem(special);
        }

        // History menu
        // TODO History menu is only updated if I click on "History"
        // TODO don't add duplicates to the historyQ
        history.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                if (historyQ.getSize() > 0) {
                    MenuBar historySubMenu = new MenuBar(true);
                    List<String> hitems = (List<String>) historyQ.getItems();
                    for (int i = 0; i < hitems.size(); i++) {
                        final String hitem = hitems.get(i);
                        historySubMenu.addItem(limitLength(hitem, 80), new Command() {
                            @Override
                            public void execute() {
                                if (isTerminalEnabled()) {
                                    // replace the content of the last line with hitem
                                    String taval = getCommand();
                                    int lastNewlinePos = taval.lastIndexOf("\n");
                                    if (lastNewlinePos == -1) {
                                        setResult(hitem, true);
                                    } else {
                                        setResult(taval.substring(0, lastNewlinePos + 1) + hitem, true);
                                    }
                                } else {
                                    // replace entire console contents with hitem
                                    setResult(hitem, true);
                                }
                            }
                        });
                    }
                    history.setSubMenu(historySubMenu);
                }
            }
        });

        // File menu
        fileopen.setEnabled(false);
        filesave.setEnabled(false);
        filesaveas.setEnabled(false);
        closeguiMI.setEnabled(false);

        // Edit menu

        clearcommand.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                clearCommand();
            }
        });

        increasefontsize.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                changeFontSize(2.0);
            }
        });

        decreasefontsize.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                changeFontSize(-2.0);
            }
        });

        // Mode menu
        //modedefault.setEnabled(true);
        //modexpath.setEnabled(true);
        //modejs.setEnabled(true);
        modeattr.setEnabled(false);

        modedefault.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                setMode("default:");
            }
        });

        modexpath.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                setMode("xpath:");
            }
        });

        modejs.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                setMode("js:");
            }
        });

        modeattr.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                setMode("attr:");
            }
        });

        terminal.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                terminalEnabled = !terminalEnabled;
            }
        });

        // Help menu

        help.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                help();
            }
        });

        xpath.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                xpathExamples();
            }
        });

        jsscript.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                jsScript();
            }
        });

        behaviorScript.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                behaviorScript();
            }
        });

        behaviorScriptProto.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                behaviorScriptProto();
            }
        });

        behaviorScriptInstance.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                behaviorScriptInstance();
            }
        });

        // handle the submit MenuItem
        submit.setScheduledCommand(new Command() {
            @Override
            public void execute() {
                submit();
            }
        });

        // TextArea  handle ENTER key
        // TODO bug - TextArea.wrap(commandPaneTAE), in devmode, fails on assertion that the element is attached
        TextArea ta = null;
        try {
            ta = TextArea.wrap(commandPaneTAE);
        } catch (AssertionError ae) {
            // execute the lines of code that are in TextArea.wrap(); it won't compile
            //ta = new TextArea(commandPaneTAE);
            // Mark it attached and remember it for cleanup.
            //ta.onAttach();
            //RootPanel.detachOnWindowClose(ta);
            this.consoleLog("XholonConsole " + ae.toString());
        } finally {
            if (ta != null) {
                ta.addKeyDownHandler(new KeyDownHandler() {
                    @Override
                    public void onKeyDown(KeyDownEvent event) {
                        if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
                            if (isTerminalEnabled()) {
                                submit();
                            }
                        }
                    }
                });
            }
        }
    }

    /**
     * Submit a command for processing, and write out the result.
     */
    protected void submit() {
        if (xholonHelperService == null) {
            xholonHelperService = (XholonHelperService) app.getService(IXholonService.XHSRV_XHOLON_HELPER);
        }
        String result = doXholonConsoleCommandOrText(getCommand());
        if (result == null) {
            result = "";
        } else {
            setResult(result, false);
        }
    }

    /*
     * @see org.primordion.xholon.base.Xholon#processReceivedMessage(org.primordion.xholon.base.IMessage)
     */
    public void processReceivedMessage(IMessage msg) {
        switch (msg.getSignal()) {
        case ISwingEntity.SWIG_ACTION_EVENT: // -1001 JButton JMenuItem

            if (msg.getData() == null) {
                return;
            }

            // Submit Command or Text
            if ("submit".equals(msg.getData())) {
                submit();
            }

            // Edit
            else if ("clearresults".equals(msg.getData())) {
                clearResults();
            } else if ("clearcommand".equals(msg.getData())) {
                clearCommand();
            } else if ("increasefontsize".equals(msg.getData())) {
                changeFontSize(2.0);
            } else if ("decreasefontsize".equals(msg.getData())) {
                changeFontSize(-2.0);
            }

            // File
            else if ("fileopen".equals(msg.getData())) {
                openQueryFile();
            } else if ("filesave".equals(msg.getData())) {
                saveCommandFile();
            } else if ("filesaveas".equals(msg.getData())) {
                saveCommandFileAs();
            } else if ("closegui".equals(msg.getData())) {
                closeGui();
            }

            // Help
            else if ("help".equals(msg.getData())) {
                help();
            } else if ("xpath".equals(msg.getData())) {
                xpathExamples();
            } else if ("jsscript".equals(msg.getData())) {
                jsScript();
            } else if ("behaviorScript".equals(msg.getData())) {
                behaviorScript();
            } else if ("behaviorScriptProto".equals(msg.getData())) {
                behaviorScriptProto();
            } else if ("behaviorScriptInstance".equals(msg.getData())) {
                behaviorScriptInstance();
            } else if (((String) msg.getData()).startsWith("mode")) {
                setMode(((String) msg.getData()).substring(4));
            } else {
                //System.out.println(msg.getData());
            }
            break;
        case ISignal.SIGNAL_XHOLON_CONSOLE_RSP:
            Object dataObj = msg.getData();
            if (dataObj == null) {
                //setResult("null");
            } else {
                setResult(dataObj.toString(), false);
            }
            break;
        default:
            //System.out.println(msg.getSignal());
            break;
        }
    }

    @Override
    public void doAction(String action) {
        // clear the console, write action to the console
        setCommand(action, true);
        // submit
        String result = doXholonConsoleCommandOrText(getCommand());
        // write result to the console
        if (result == null) {
            result = "";
        } else {
            setResult(result, false);
        }
    }

    /**
     * Provide the user with some brief help information.
     */
    protected void help() {
        StringBuilder sb = new StringBuilder().append("Type one of the following into the console window:\n")
                .append("* An xpath expression, to navigate to another node,\n").append("  xpath:Hello\n")
                .append("* An XML string that will be pasted in as new content,\n").append("  <World/>\n")
                .append("* \"attributes\" to see a list for this node,\n")
                .append("* \"avatar\" to create a command-driven avatar,\n")
                //.append("* A URI preceeded by a colon\n")
                //.append("  :https://raw.github.com/gist/3374405/xholonWorkbook.xml\n")
                .append("* One of the following modes,\n");
        for (int i = 0; i < modes.length; i++) {
            sb.append("  ").append(modes[i]);
        }
        sb.append("\n");
        sb.append("* Some other string that's meaningful to the current app.\n")
                .append("Then click the Submit button.\n")
        //.append("Alternatively, highlight text already in this console window.")
        ;

        // node-specific actions
        String[] al = context.getActionList();
        if (al != null) {
            sb.append("\n\nActions specific to this node:\n");
            for (int i = 0; i < al.length; i++) {
                sb.append(al[i]).append("\n");
            }
        } else {
            sb.append("\n");
        }

        // mode
        String currentMode = mode;
        if (currentMode.length() == 0) {
            currentMode = MODE_DEFAULT;
        }
        sb.append("The current mode is ").append(currentMode).append(" which enables the use of ");
        for (int i = 0; i < modes.length; i++) {
            if (currentMode.equals(modes[i])) {
                sb.append(modeLabels[i]);
                break;
            }
        }
        sb.append(".\n");

        setResult(sb.toString(), true);
    }

    /**
     * Provide the user with a JavaScript starting point.
     */
    protected void jsScript() {
        StringBuilder sb = new StringBuilder().append("<script><![CDATA[\n")
                //.append("$wnd.console.log(\"Testing 1 2 3\");\n")
                .append("$wnd.xh.root().println(\"Testing 1 2 3\");\n").append("]]></script>\n");
        setResult(sb.toString(), true);
    }

    /**
     * Provide the user with XPath examples.
     */
    protected void xpathExamples() {
        StringBuilder sb = new StringBuilder().append("Sample XPath expressions that change the context node.\n")
                .append("Parent: xpath ..\n").append("Ancestor: xpath ancestor::Bestiary\n")
                .append("First child: xpath *\n").append("Child: xpath Cat\n")
                .append("Descendant: xpath descendant::Dog\n")
                .append("Descendant of root: xpath /descendant::Termite\n")
                .append("Descendant of ancestor: xpath ancestor::Bestiary/descendant::Termite\n")
                .append("Following sibling: xpath following-sibling::*\n")
                .append("Preceding sibling: xpath preceding-sibling::*\n")
                .append("Path: xpath /descendant::Grid/Row[50]/HabitatCell[50]\n")
                .append("Last: xpath /descendant::Grid/Row[last()]/HabitatCell[last()]\n")
                .append("Find by name (Licorice): xpath /descendant-or-self::*[@name='cat_2']\n");
        setResult(sb.toString(), true);
    }

    /**
     * Provide the user with a org.primordion.xholon.base.Behavior_gwtjs starting point.
     * JavaScript (js) is the default language for writing behaviors.
     * <p>lang = "gwtjs" </p>
     */
    protected void behaviorScript() {
        //String lang = "gwtjs";
        //String commentStr = "//";
        String xhcName = context.getXhcName();
        String me = "my" + xhcName;
        StringBuilder sbEx = new StringBuilder().append("var ").append(me).append(", testing;\n")
                .append("var beh = {\n").append("  postConfigure: function() {\n").append("    ").append(me)
                .append(" = this.cnode.parent();\n").append("    testing = Math.floor(Math.random() * 10);\n")
                .append("  },\n").append("  act: function() {\n").append("    ").append(me)
                .append(".println(this.toString());\n").append("  },\n").append("  toString: function() {\n")
                .append("    return \"testing:\" + testing;\n").append("  }\n").append("}\n")
                .append("//# sourceURL=behaviorScript.js\n");

        /*String prefix = "Xing";
        StringBuilder sb = new StringBuilder()
        .append("<")
        .append(prefix)
        .append(xhcName)
        .append("behavior implName=\"org.primordion.xholon.base.Behavior_gwtjs\"")
        .append("><![CDATA[\n")
        .append(sbEx.toString())
        .append("]]></")
        .append(prefix)
        .append(xhcName)
        .append("behavior>\n");*/
        setResult(wrapBehaviorScript(sbEx.toString(), "Xing", xhcName), true);
    }

    /**
     * Provide the user with a org.primordion.xholon.base.Behavior_gwtjs starting point,
     * that uses a JavaScript prototype.
     * JavaScript (js) is the default language for writing behaviors.
     * <p>lang = "gwtjs" </p>
     */
    protected void behaviorScriptProto() {
        String wndXh = "$wnd.xh.";
        String xhcName = context.getXhcName();
        String protoName = xhcName + "behavior";
        String cnodeParentName = "" + Character.toLowerCase(xhcName.charAt(0))
                + (xhcName.length() == 0 ? "" : xhcName.substring(1));
        StringBuilder sbEx = new StringBuilder().append(wndXh).append(protoName).append(" = function ")
                .append(protoName).append("() {}\n\n")

                // postConfigure
                .append(wndXh).append(protoName).append(".prototype.postConfigure = function() {\n")
                .append("  this.").append(cnodeParentName).append(" = this.cnode.parent();\n")
                .append("  this.testing = Math.floor(Math.random() * 10);\n").append("};\n\n")

                // act
                .append(wndXh).append(protoName).append(".prototype.act = function() {\n").append("  this.")
                .append(cnodeParentName).append(".println(this.toString());\n").append("};\n\n")

                // toString
                .append(wndXh).append(protoName).append(".prototype.toString = function() {\n").append("  ")
                .append("return \"testing: \" + this.testing;\n").append("};\n\n")
                .append("//# sourceURL=behaviorScriptProto.js\n");

        setResult(wrapBehaviorScript(sbEx.toString(), "", xhcName), true);
    }

    /**
    * Provide the user with a org.primordion.xholon.base.Behavior_gwtjs starting point,
    * that uses a JavaScript prototype instance.
    * JavaScript (js) is the default language for writing behaviors.
    * <p>lang = "gwtjs" </p>
    */
    protected void behaviorScriptInstance() {
        String wndXh = "$wnd.xh.";
        String xhcName = context.getXhcName();
        String protoName = xhcName + "behavior";
        //String cnodeParentName = "" + Character.toLowerCase(xhcName.charAt(0))
        //  + (xhcName.length() == 0 ? "" : xhcName.substring(1));
        StringBuilder sbEx = new StringBuilder().append("var beh = new ").append(wndXh).append(protoName)
                .append("();\n");
        setResult(wrapBehaviorScript(sbEx.toString(), "", xhcName), true);
    }

    /**
     * Wrap the behavior script in an XML tag.
     * @param jsContent The JavaScript content.
     * @param prefix An optional prefix for the XML tag name.
     * @param xhcName The XholonClass name.
     * @return The complete XML wrapper with the wrapped JavaScript.
     */
    protected String wrapBehaviorScript(String jsContent, String prefix, String xhcName) {
        StringBuilder sb = new StringBuilder().append("<").append(prefix).append(xhcName)
                .append("behavior implName=\"org.primordion.xholon.base.Behavior_gwtjs\"").append("><![CDATA[\n")
                .append(jsContent).append("]]></").append(prefix).append(xhcName).append("behavior>\n");
        return sb.toString();
    }

    /**
     * Increase or decrease the font size of the command text pane.
     */
    protected void changeFontSize(double incDec) {
        Element pre = commandPane.getElement().getFirstChildElement();
        String fontSizeStr = pre.getStyle().getFontSize(); // ex: "12px"
        if (fontSizeStr.endsWith("px") && fontSizeStr.length() > 2) {
            double fontSize = Double.parseDouble(fontSizeStr.substring(0, fontSizeStr.length() - 2));
            fontSize += incDec;
            pre.getStyle().setFontSize(fontSize, Unit.PX);
        }
    }

    /**
     * Do a XholonConsole command.
     * @param commandOrTextString A command or otherwise meaningful text string, or null.
     * This method trims leading and trailing white space.
     * @return A String that's meaningful to the caller, or null.
     */
    protected String doXholonConsoleCommandOrText(String commandOrTextString) {
        //context.println("doXholonConsoleCommandOrText1");
        if (commandOrTextString == null) {
            return "";
        }
        commandOrTextString = commandOrTextString.trim();
        //context.println(commandOrTextString);
        if (commandOrTextString.length() == 0) {
            return "";
        }

        if (isTerminalEnabled()) {
            // the last line is the command
            int lastNewlinePos = commandOrTextString.lastIndexOf("\n");
            if (lastNewlinePos != -1) {
                commandOrTextString = commandOrTextString.substring(lastNewlinePos);
            }
            commandOrTextString += "\n";
        }

        if (commandOrTextString.endsWith(":")) {
            // this is probably a request to change the mode
            for (int i = 0; i < modes.length; i++) {
                if (commandOrTextString.equals(modes[i])) {
                    mode = commandOrTextString;
                    if (mode.equals(MODE_DEFAULT)) {
                        mode = "";
                    }
                    setTabHeader();
                    return null;
                }
            }
        }
        int firstColonPos = commandOrTextString.indexOf(":");
        if (commandOrTextString.charAt(0) == '<') { // TODO or .startsWith("xml:")
            rememberButton3Selection(context);
            // this is an XML String, either normal XML content or an XML script
            xholonHelperService.pasteLastChild(context, commandOrTextString);
            addToHistory(commandOrTextString);
            return null;
        } else if (commandOrTextString.charAt(0) == MODE_ESCAPE) {
            /* TODO
            // ":" is being used as an escape character
            // this is probably a URI such as "http://" "https://" "db:" etc
            // type these as ":http://" etc
            rememberButton3Selection(context);
            xholonHelperService.pasteLastChild(context, commandOrTextString.substring(1));
            */
            return null;
        } else if (commandOrTextString.startsWith("xpath")) {
            // "xpath " or "xpath:"
            if (commandOrTextString.length() < 7) {
                return null;
            }
            if (" :".indexOf(commandOrTextString.charAt(5)) == -1) {
                return null;
            }
            IXholon xpathResult = getXPath().evaluate(commandOrTextString.substring(6).trim(), context);
            if (xpathResult != null) {
                context = xpathResult;
                rememberButton3Selection(context);
                setTabHeader();
                //((JLabel)commandPaneLabel.getVal_Object()).setText(getXPath()
                //    .getExpression(context, app.getRoot(), false));
                addToHistory(commandOrTextString);
            }
            return null;
        } else if (commandOrTextString.startsWith("attr:")) {
            /* TODO allow this to work
            // simple one-line javascript setter/getter
            if (commandOrTextString.length() < 6) {return null;}
            String attrText = commandOrTextString.substring(5).trim();
            if (attrText.endsWith(";")) {
              attrText = attrText.substring(0, attrText.length()-1);
            }
            StringBuilder sb = new StringBuilder()
            .append("<script implName=\"lang:javascript:inline:\"><![CDATA[\n");
            if (attrText.startsWith("get")) {
              // attr:getMass(); OR attr:getMass()
              sb.append("println(contextNodeKey.").append(attrText).append(");\n");
            }
            else if (attrText.startsWith("is")) {
              sb.append("println(contextNodeKey.").append(attrText).append(");\n");
            }
            else if (attrText.startsWith("set")){
              // attr:setMass(123.456);
              sb.append("contextNodeKey.").append(attrText).append(";\n");
            }
            else if (attrText.indexOf("=") != -1) {
              // attr:mass = 9876.1 OR attr:mass=2.3;
              sb.append("contextNodeKey.").append(attrText).append(";\n");
            }
            else {
              // attr:mass
              sb.append("println(contextNodeKey.").append(attrText).append(");\n");
            }
            sb.append("]]></script>");
            rememberButton3Selection(context);
            xholonHelperService.pasteLastChild(context, sb.toString());
            */
            return null;
        } else if ((firstColonPos > 0) && (firstColonPos < 11)) {
            // allow length from "x:" to "javascript:"
            // this is probably the name of some scripting language
            String scrText = commandOrTextString.substring(firstColonPos + 1).trim();
            StringBuilder sb = new StringBuilder().append("<script><![CDATA[\n").append(scrText)
                    .append("]]></script>");
            //System.out.println(sb);
            rememberButton3Selection(context);
            xholonHelperService.pasteLastChild(context, sb.toString());
            return null;
        } else if (commandOrTextString.equalsIgnoreCase("attributes")) {
            IReflection ir = ReflectionFactory.instance();
            Object rowData[][] = null;
            rowData = ir.getAttributes(context);
            Object columnNames[] = new Object[2];
            columnNames[0] = "Name";
            columnNames[1] = "Value";
            StringBuilder sb = new StringBuilder();
            sb.append(columnNames[0]).append(": [").append(columnNames[1] + "]\n");
            for (int i = 0; i < rowData.length; i++) {
                sb.append(rowData[i][0]).append(": [").append(rowData[i][1] + "]\n");
            }
            return sb.toString();
        } else if (commandOrTextString.equalsIgnoreCase("avatar")) {
            // CSH <Avatar/>
            context.appendChild("Avatar", null, "org.primordion.xholon.base.Avatar");
            IXholon avatar = context.getLastChild();
            if ((avatar != null) && ("Avatar".equals(avatar.getXhcName()))) {
                avatar.postConfigure();
                context = avatar;
                rememberButton3Selection(context);
                setTabHeader();
                sendMsgAsyncOrSync = SENDMESSAGE_SYNC;
                context.sendSyncMessage(ISignal.SIGNAL_XHOLON_CONSOLE_REQ, "vanish", this);
                IMessage rmsg = context.sendSyncMessage(ISignal.SIGNAL_XHOLON_CONSOLE_REQ, "help", this);
                Object dataObj = rmsg.getData();
                if (dataObj != null) {
                    setResult(dataObj.toString(), false);
                }
                setTerminalEnabled(true);
            }
            return null;
        } else if (commandOrTextString.equalsIgnoreCase("chatbot")) {
            context.appendChild("Chatbot", null, "org.primordion.xholon.base.Chatbot");
            IXholon chatbot = context.getLastChild();
            if ((chatbot != null) && ("Chatbot".equals(chatbot.getXhcName()))) {
                chatbot.postConfigure();
                context = chatbot;
                rememberButton3Selection(context);
                setTabHeader();
                sendMsgAsyncOrSync = SENDMESSAGE_SYNC;
                context.sendSyncMessage(ISignal.SIGNAL_XHOLON_CONSOLE_REQ, "vanish", this);
                IMessage rmsg = context.sendSyncMessage(ISignal.SIGNAL_XHOLON_CONSOLE_REQ, "help", this);
                Object dataObj = rmsg.getData();
                if (dataObj != null) {
                    setResult(dataObj.toString(), false);
                }
                setTerminalEnabled(true);
            }
            return null;
        } else if (mode.length() > 1) {
            return doXholonConsoleCommandOrText(mode + commandOrTextString);
        } else {
            addToHistory(commandOrTextString);
            if (sendMsgAsyncOrSync == SENDMESSAGE_ASYNC) {
                context.sendMessage(ISignal.SIGNAL_XHOLON_CONSOLE_REQ, commandOrTextString, this);
            } else {
                // this is used for Avatars
                IXholon contextParent = context.getParentNode(); // cache the parent
                IMessage rmsg = context.sendSyncMessage(ISignal.SIGNAL_XHOLON_CONSOLE_REQ, commandOrTextString,
                        this);
                Object dataObj = rmsg.getData();
                if (dataObj != null) {
                    String dataStr = dataObj.toString();
                    if (isTerminalEnabled() && (!dataStr.endsWith("\n"))) {
                        dataStr += "\n";
                    }
                    setResult(dataStr, false);
                }
                if (context.getParentNode() != contextParent) {
                    // the context node has moved and has a new parent, so update the XholonConsole header with tooltip
                    // note that the context node itself has NOT changed
                    setTabHeader();
                }
            }
            return null;
        }
    }

    /**
     * Get the contents of the command, as typed in by the user into the XholonConsole GUI.
     * @return The String typed in by the user, or only the selected part of the String.
     */
    protected String getCommand() {
        /*JTextPane jTextPane = (JTextPane)commandPane.getVal_Object();
        String contents = jTextPane.getSelectedText();
        if (contents == null) {
          contents = jTextPane.getText();
        }*/
        Element ele = commandPane.getElement().getFirstChildElement();
        //consoleLog(ele);
        // pre
        //String contents = ele.getInnerText();
        // textarea
        TextAreaElement ta = TextAreaElement.as(ele);
        String contents = ta.getValue();
        //consoleLog(contents);
        return contents;
    }

    /*
     * @see org.primordion.xholon.io.console.IXholonConsole#log(java.lang.Object)
     */
    public void log(Object obj) {
        if (obj instanceof String) {
            setResult((String) obj, false);
        }
    }

    /**
     * For now, the command and the result are in the same window,
     * so just call the existing setResult() method.
     */
    public void setCommand(String result, boolean replace) {
        setResult(result, replace);
    }

    /**
     * Set the result of the query into the XholonConsole GUI,
     * and make it visible to the user.
     * For now, the resultPane and commandPane are the same.
     * @param result
     * @param replace whether to replace current text (true), or append to end of current text (false)
     */
    public void setResult(String result, boolean replace) {
        //Window.alert(result);

        // get the element
        Element ele = commandPane.getElement().getFirstChildElement();

        // <pre>
        //ele.setInnerText(ele.getInnerText() + result);

        // these leave the escaped chars in the text
        //ele.setInnerHTML(ele.getInnerHTML() + result);
        //ele.setInnerText(ele.getInnerText() + SafeHtmlUtils.htmlEscape(result));

        // textarea
        TextAreaElement ta = TextAreaElement.as(ele);
        if (replace) {
            ta.setValue(result);
        } else {
            ta.setValue(ta.getValue() + result);
        }
        // optionally scroll to the bottom of the text area
        if (isTerminalEnabled()) {
            ta.setScrollTop(ta.getScrollHeight());
        }
    }

    /**
     * Clear the contents of the result pane.
     */
    protected void clearResults() {
        /*
        JTextPane jTextPane = (JTextPane)resultPane.getVal_Object();
        jTextPane.setText("");
        */
    }

    /**
     * Clear the contents of the command pane.
     */
    protected void clearCommand() {
        Element ele = commandPane.getElement().getFirstChildElement();
        TextAreaElement ta = TextAreaElement.as(ele);
        // pre
        //ele.setInnerText("");
        // textarea
        ta.setValue("");
    }

    /**
     * Set the name and title of the tab header.
     */
    public void setTabHeader() {
        //String modeStr = mode;
        //if (mode.length() == 0) {modeStr = MODE_DEFAULT;}

        String idStr = context.getName("R^^_i^");
        String xpathExpression = getXPath().getExpression(context, app.getRoot(), false);
        String tooltip = xpathExpression == null ? idStr : xpathExpression;
        XholonGwtTabPanelHelper.updateTabHeader(idStr, tooltip, -1);

        //((JFrame)frame.getVal_Object()).setTitle(context.getName()
        //    + " (" + app.getModelName() + ")[XholonConsole mode=" + modeStr + "]");
    }

    /**
     * Close the GUI.
     */
    protected void closeGui() {
        Window.alert("closegui selected");
        /*IXholon frame = getXPath().evaluate("descendant::JFrame", this);
        ((JFrame)frame.getVal_Object()).dispose();
        // remove the frame hierarchy from this XholonConsole node
        frame.removeChild();
        // remove the XholonConsole from the composite structure tree
        removeChild();*/
    }

    /**
     * Open a file that contains XholonConsole commands or text,
     * and load the contents of that file into the Command part of the XholonConsole.
     */
    protected void openQueryFile() {
        /*
        final ScriptFilter xhnFilter = new ScriptFilter();
        JFileChooser chooser = new JFileChooser();
        chooser.addChoosableFileFilter(xhnFilter);
        chooser.setCurrentDirectory(new File("./script"));
        int state = chooser.showOpenDialog(null);
        File file = chooser.getSelectedFile();
        if (file != null && state == JFileChooser.APPROVE_OPTION) {
          latestOpenedFile = file;
          String fileName = file.getAbsolutePath();
          //System.out.println(fileName);
          BufferedReader r = (BufferedReader)MiscIo.openInputFile(fileName);
          String xqueryStr = "";
          try {
            String buffer = r.readLine();
            while (buffer != null) {
              xqueryStr += buffer + "\n";
              buffer = r.readLine();
            }
          } catch (IOException e) {
            e.printStackTrace();
          }
          MiscIo.closeInputFile(r);
          JTextPane jTextPane = (JTextPane)commandPane.getVal_Object();
          jTextPane.setText(xqueryStr);
        }
        */
    }

    /**
     * Save the contents of the Command panel in an already specified file.
     */
    protected void saveCommandFile() {
        /*
        if (latestOpenedFile == null) {
          saveCommandFileAs();
        }
        else {
          File file = latestOpenedFile;
          String fileName = file.getAbsolutePath();
          FileWriter w = (FileWriter)MiscIo.openOutputFile(fileName);
          String xqueryStr = getCommand();
          try {
            w.write(xqueryStr);
          } catch (IOException e) {
            e.printStackTrace();
          }
          MiscIo.closeOutputFile(w);
        }
        */
    }

    /**
     * Save the contents of the Command panel in a file.
     */
    protected void saveCommandFileAs() {
        /*
        final ScriptFilter xhnFilter = new ScriptFilter();
        JFileChooser chooser = new JFileChooser();
        chooser.addChoosableFileFilter(xhnFilter);
        chooser.setCurrentDirectory(new File("./script"));
        int state = chooser.showSaveDialog(null);
        File file = chooser.getSelectedFile();
        if (file != null && state == JFileChooser.APPROVE_OPTION) {
          latestOpenedFile = file;
          String fileName = file.getAbsolutePath();
          FileWriter w = (FileWriter)MiscIo.openOutputFile(fileName);
          String xqueryStr = getCommand();
          try {
            w.write(xqueryStr);
          } catch (IOException e) {
            e.printStackTrace();
          }
          MiscIo.closeOutputFile(w);
        }
        */
    }

    /**
     * Read the XholonConsole GUI from a UIBinder XML file.
     */
    protected void readGuiFromXml() {
        widget = uiBinder.createAndBindUi(this);
        //RootPanel.get("xhappspecific").add(widget);
    }

    /**
     * FileFilter that filters on files with .bsh .groovy .js .rb .py extension.
     * @author Ken
     */
    /*class ScriptFilter extends FileFilter {
      public boolean accept(File f) {
    boolean accept = f.isDirectory();
    if (! accept) {
      String suffix = getSuffix(f);
      if (suffix != null) {
        accept = suffix.equals("bsh")
        || suffix.equals("groovy")
        || suffix.equals("js")
        || suffix.equals("rb")
        || suffix.equals("py");
      }
    }
    return accept;
      }
      public String getDescription() {
    return "Script Files(*.bsh *.groovy *.js *.rb *.py)";
      }
      private String getSuffix(File f) {
    String s = f.getPath();
    String suffix = null;
    int i = s.lastIndexOf('.');
    if (i > 0 && i < s.length() - 1) {
      suffix = s.substring(i+1).toLowerCase();
    }
    return suffix;
      }
    }*/

    /**
    * Remember the current context node as a MouseEvent.BUTTON3 selection (Right-Click).
    * @param node The context node.
    */
    protected void rememberButton3Selection(IXholon node) {
        // pass the node to the NodeSelectionService by sending it as an async message
        if (nodeSelectionService == null) {
            nodeSelectionService = app.getService(IXholonService.XHSRV_NODE_SELECTION);
        }
        if (nodeSelectionService != null) {
            nodeSelectionService.sendSyncMessage(NodeSelectionService.SIG_REMEMBER_BUTTON3_NODE_REQ, node, app);
        }
    }

    /**
     * Add an item to history.
     * @param historyItem 
     */
    protected void addToHistory(String historyItem) {
        if (historyQ.getSize() == historyQ.getMaxSize()) {
            historyQ.dequeue();
        }
        historyQ.enqueue(historyItem);
    }

    /**
     * Limit the length of a string.
     */
    protected String limitLength(String str, int maxLen) {
        return str.length() > 80 ? str.substring(0, 80) : str;
    }

}