//---------------------------------------------------------------------------- // COMPONENT NAME: LPEX Editor // // All Rights Reserved. // // DESCRIPTION: // Lpex - sample stand-alone SWT LPEX widget editor //---------------------------------------------------------------------------- package com.ibm.lpex.samples; import java.io.File; import java.util.ArrayList; import com.ibm.lpex.core.LpexAction; import com.ibm.lpex.core.LpexCommand; import com.ibm.lpex.core.LpexMessageConstants; import com.ibm.lpex.core.LpexResources; import com.ibm.lpex.core.LpexStringTokenizer; import com.ibm.lpex.core.LpexView; import com.ibm.lpex.core.LpexViewAdapter; import com.ibm.lpex.core.LpexWindow; import org.eclipse.swt.SWT; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.Shell; /** * Sample stand-alone editor built on the LPEX edit widget. * * <p>Here is the Lpex <a href="doc-files/Lpex.java.html">source code</a>.</p> * * <p>To run this sample from the command line: * <pre> * java [<i>java options</i>] com.ibm.lpex.samples.Lpex [<i>filename</i>] * [-encoding <i>encoding</i>] [-extension {<i>extension</i> | none}] </pre> * A possible Windows batch program is: * <pre> * &#64;start /b java com.ibm.lpex.samples.Lpex %1 %2 %3 %4 %5 %6 %7 %8 %9 </pre> * To disable the JIT compiler, run it with this option: * <pre> * -Djava.compiler= </pre> * You can run Lpex in a particular locale. For example, in order to run it in * Simplified Chinese (zh_CN), use these two options: * <pre> * -Duser.language=zh -Duser.region=CN </pre></p> * * Commands and actions defined in here: * <ul> * <li><b>exit</b> command - close all Lpex views of the document * <li><b>Lpex</b> command - open a new Lpex window * <li><b>openNewView</b> command - open an additional view on the document * <li><b>quit</b> command - unconditionally quit the Lpex window * <li><b>test</b> command - placeholder for testing new commands * <li><b>viewProfile</b> command - view the LPEX defaults profile * <li><b>viewLog</b> command - view the LPEX editor log * <li><b>close</b> action (F3) - close the Lpex window * <li><b>nextView</b> action (Ctrl+Alt+-&gt;) - go to the next view of this document * <li><b>openNewView</b> action (Ctrl+O) - open an additional view on the document * <li><b>prevView</b> action (Ctrl+Alt+&lt;-) - go to the previous view of this document * <li><b>test</b> action - placeholder for testing new actions. * </ul> * * <p>Example user-defined editor action to open an additional document view with Lpex:</p> * <table bgcolor="#f0f0f0"><tr><td><pre>&nbsp; * lpexView.defineAction(<font color="#800080">"openNewView"</font>, <font color="#0000ff"><b>new</b></font> LpexAction() { * <font color="#0000ff"><b>public void</b></font> doAction(LpexView view) * { * Display display = getDisplay(); * display.asyncExec(<font color="#0000ff"><b>new</b></font> Runnable() { * <font color="#0000ff"><b>public void</b></font> run() { * <font color="#0000ff"><b>try</b></font> * { * Class cl = Class.forName(<font color="#800080">"com.ibm.lpex.samples.Lpex"</font>); * Constructor lpexConstructor = cl.getConstructor(<font color="#0000ff"><b>new</b></font> Class[] * { LpexView.class, com.ibm.lpex.samples.Lpex.Delegate.class, * Rectangle.class, java.lang.Boolean.TYPE }); * lpexConstructor.newInstance(<font color="#0000ff"><b>new</b></font> Object[] * { <i>getView</i>(), <i>getDelegate</i>(), <font color="#0000ff"><b>new</b></font> Rectangle(10, 10, 648, 711),&nbsp; * <font color="#0000ff"><b>new</b></font> java.lang.Boolean(<font color="#0000ff"><b>false</b></font>) }); * } * <font color="#0000ff"><b>catch</b></font> (Exception e) {} * }}); * } * <font color="#0000ff"><b>public boolean</b></font> available(LpexView view) * { <font color="#0000ff"><b>return true</b></font>; } * }); </pre></td></tr></table> * * @see com.ibm.lpex.samples All the samples */ public final class Lpex { private Shell _shell; private LpexWindow _lpexWindow; private LpexView _lpexView; LpexViewNode _lpexViewNode; /** * Entry point. */ public static void main(String[] args) { new Lpex(args, null /*bounds*/, false /*browse*/); } /** * Constructor for opening a file. * @param parms file name and parameters * @param bounds size and position for the window * @param browse if <code>true</code>, open the view in read-only mode */ public Lpex(String[] parms, Rectangle bounds, boolean browse) { String filename = null; String encoding = ""; // default to autodetect file's encoding String extension = null; // extract file name and parameters int i = 0; if (i < parms.length && !parms[i].startsWith("-") && !parms[i].startsWith("/")) { filename = LpexStringTokenizer.trimQuotes(parms[i++]); } while (i < parms.length-1 && (parms[i].startsWith("-") || parms[i].startsWith("/"))) { String parm = parms[i++].substring(1); if ("encoding".equals(parm) || "enc".equals(parm)) { encoding = parms[i++]; } else if ("extension".equals(parm) || "dt".equals(parm)) { extension = parms[i++]; } else { break; } } // ensure canonical file name if (filename != null) { if (filename.trim().length() == 0) { filename = null; } else { try { filename = (new File(filename)).getCanonicalPath(); } catch(Exception e) {} } } // create an LpexView for the given file name and character encoding, // do an explicit "updateProfile" later _lpexView = new LpexView(filename, encoding, false); _lpexViewNode = new LpexViewNode(this); // determine document parser to use if (extension != null) { if ("none".equals(extension)) { _lpexView.doCommand("set updateProfile.noParser on"); } else { _lpexView.doCommand("set updateProfile.parser " + _lpexView.query("current.updateProfile.parserAssociation." + extension)); } } // initialize the view setUpView(bounds, browse, null); } /** * Constructor for an external program to create a new view on an existing document. * @param lpexView master LpexView of the document * @param delegate optional Delegate object for the external program to handle * actions and commands from this Lpex view * @param bounds size and position for the window * @param browse if <code>true</code>, open the view in read-only mode */ public Lpex(LpexView lpexView, Delegate delegate, Rectangle bounds, boolean browse) throws LpexView.ViewInstantiationException { this(LpexViewNode.getLpexViewNode(lpexView, delegate), bounds, browse); } /** * Constructor for a new view on an existing document. * @param lpexViewNode an existing view on the document * @param bounds size and position for the window * @param browse if <code>true</code>, open the file in read-only mode */ private Lpex(LpexViewNode lpexViewNode, Rectangle bounds, boolean browse) throws LpexView.ViewInstantiationException { // originating view LpexView lpexView = lpexViewNode.lpexView(); // create a new LpexView for the given view's document, // do an explicit "updateProfile" later _lpexView = new LpexView(lpexView, false); _lpexViewNode = new LpexViewNode(this, lpexViewNode); // initialize view setUpView(bounds, browse, lpexView); } /** * Initialize the new Lpex view. * @param bounds size and position for the window * @param browse if <code>true</code>, open the view in read-only mode * @param fromView optional document view from which this Lpex was started */ private void setUpView(Rectangle bounds, boolean browse, LpexView fromView) { // create a new Shell _shell = new Shell(); // use the same parser as the originating view if (fromView != null) { if (fromView.queryOn("updateProfile.noParser")) { _lpexView.doCommand("set updateProfile.noParser on"); } else { _lpexView.doCommand("set updateProfile.parser " + fromView.query("updateProfile.parser")); } } // create & set a window for our LpexView _lpexWindow = new LpexWindow(_shell); _lpexView.setWindow(_lpexWindow); // add an LpexViewListener to handle all the LPEX listening needed here _lpexView.addLpexViewListener(new LpexViewAdapter() { // called after a "set name" command was run public void renamed(LpexView lpexView) { Lpex.this.renamed(lpexView); } // called after an "updateProfile" command was run public void updateProfile(LpexView lpexView) { Lpex.this.updateProfile(lpexView); } // called when the LpexView is disposed public void disposed(LpexView lpexView) { _lpexViewNode.remove(); } }); // run the "updateProfile" command _lpexView.doDefaultCommand("updateProfile"); // if underlying file is read only, listen to user changing its attribute if (_lpexView.queryOn("readonly") && (!browse || fromView != null)) { _lpexView.window().textWindow().addFocusListener(new FocusListener() { public void focusGained(FocusEvent e) { if (!isFileReadonly()) { _lpexView.doCommand("set readonly off"); _lpexView.doCommand("screenShow view"); _lpexView.window().textWindow().removeFocusListener(this); } } public void focusLost(FocusEvent e) {} }); } // asked to set view as read only if (browse) { _lpexView.doCommand("set readonly on"); } // listen to a few Shell events _shell.addListener(SWT.Close, new Listener() { public void handleEvent(Event event) { closeShell(event); }}); _shell.addListener(SWT.Dispose, new Listener() { public void handleEvent(Event event) { disposeShell(event); }}); _shell.addListener(SWT.Resize, new Listener() { public void handleEvent(Event event) { resizeShell(event); }}); // start the new view in the same state as the originating one if (fromView != null) { _lpexView.doCommand("set includedClasses " + fromView.query("includedClasses")); _lpexView.doCommand("set excludedClasses " + fromView.query("excludedClasses")); _lpexView.doCommand("locate element " + fromView.query("element")); _lpexView.doCommand("set cursorRow " + fromView.query("cursorRow")); _lpexView.doCommand("set position " + fromView.query("position")); } // set Shell's position & size, open it if (bounds == null) { int width = _lpexView.queryInt("userParameter.SwtLpex.width"); int height = _lpexView.queryInt("userParameter.SwtLpex.height"); bounds = new Rectangle(10, 10, (width <= 0)? 674 : width, (height <= 0)? 683 : height); } _shell.setBounds(bounds); _shell.open(); setWindowTitle(); // display view, set input focus in the edit area //_lpexView.doDefaultCommand("screenShow"); //_lpexWindow.textWindow().setFocus(); // run the Shell run(); } private String documentName() { String name = _lpexView.query("name"); return (name != null)? name : LpexResources.message(LpexMessageConstants.MSG_UNTITLED_DOCUMENT, _lpexView.query("documentId")); } private boolean isFileReadonly() { boolean readonly = false; String name = _lpexView.query("name"); if (name != null && name.length() > 0) { try { File file = new File(name); readonly = file.exists() && !file.canWrite(); } catch (SecurityException e) // not allowed to touch file!? { readonly = true; } catch (Exception e) {} } return readonly; } private void run() { Display display = _shell.getDisplay(); while (!_shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } _shell = null; } /*======================*/ /* LPEX notifications */ /*======================*/ private void renamed(LpexView lpexView) { String name = lpexView.query("name"); if (name != null) { try { String canonicalPath = (new File(name)).getCanonicalPath(); if (canonicalPath != null && !canonicalPath.equals(name)) { lpexView.doDefaultCommand("set name " + canonicalPath); return; } } catch(Exception e) {} } setWindowTitle(); } /** * Defines editor commands, actions & their associated keys. * Called whenever the <b>updateProfile</b> command has been processed. */ private void updateProfile(LpexView lpexView) { // "Lpex" command - open an Lpex window lpexView.defineCommand("Lpex", new LpexCommand() { public boolean doCommand(LpexView view, String parameters) { return newWindow(parameters, false, false); } }); // "quit" command - unconditionally quit this Lpex window // (also needed by base profile vi's ":q" commands) lpexView.defineCommand("quit", new LpexCommand() { public boolean doCommand(LpexView view, String parameters) { quit(); return true; } }); // "exit" command - safely close all Lpex views of the document lpexView.defineCommand("exit", new LpexCommand() { public boolean doCommand(LpexView view, String parameters) { _lpexViewNode.disposeAllLpex(); return true; } }); // "openNewView" command - open a new Lpex window on the same document lpexView.defineCommand("openNewView", new LpexCommand() { public boolean doCommand(LpexView view, String parameters) { view.doAction(view.actionId("openNewView")); return true; } }); // "close" action (F3) - optionally save & close this Lpex window lpexView.defineAction("close", new LpexAction() { public void doAction(LpexView view) { close(); } public boolean available(LpexView view) { return true; } }); assignKey("f3", "close"); // ol' style close on F3 // "openNewView" action (Ctrl+O) - open a new Lpex window on the same document lpexView.defineAction("openNewView", new LpexAction() { public void doAction(LpexView view) { newWindow(Lpex.this, Lpex.this._lpexView.queryOn("readonly")); } public boolean available(LpexView view) { return true; } }); assignKey("c-o", "openNewView"); // "nextView" action (Ctrl+Alt+->) - go to the next view of this document lpexView.defineAction("nextView", new LpexAction() { public void doAction(LpexView view) { LpexView v = Lpex.this._lpexViewNode.nextLpexView(); if (v != null && v.window() != null) v.window().setFocus(); } public boolean available(LpexView view) { return true; } }); assignKey("c-a-right", "nextView"); // "prevView" action (Ctrl+Alt+<-) - go to the previous view of this document lpexView.defineAction("prevView", new LpexAction() { public void doAction(LpexView view) { LpexView v = Lpex.this._lpexViewNode.prevLpexView(); if (v != null && v.window() != null) v.window().setFocus(); } public boolean available(LpexView view) { return true; } }); assignKey("c-a-left", "prevView"); // "viewLog" command - view the LPEX editor log lpexView.defineCommand("viewLog", new LpexCommand() { public boolean doCommand(LpexView view, String parameters) { return newWindow("\"" + view.query("editorLog") + "\"", true, true); } }); // "viewProfile" command - view the LPEX defaults profile lpexView.defineCommand("viewProfile", new LpexCommand() { public boolean doCommand(LpexView view, String parameters) { return newWindow("\"" + view.query("defaultProfile") + "\"", true, true); } }); // "test" command - placeholder for testing new commands lpexView.defineCommand("test", new LpexCommand() { public boolean doCommand(LpexView view, String parameters) { view.doCommand("set messageText test command: "+parameters); return true; } }); // "test" action - placeholder for testing new actions lpexView.defineAction("test", new LpexAction() { public void doAction(LpexView view) { view.doCommand("set messageText test action"); } public boolean available(LpexView view) { return true; } }); // if there is a master view in the chain, delegate to it potential // application-sensitive actions and commands, such as "save" Delegate delegate = _lpexViewNode.findDelegate(); if (delegate != null) { delegate.delegate(lpexView); } } // Assigns an action to the given key (e.g., "c-o") if free in any context. private void assignKey(String key, String actionName) { if (!_lpexView.keyAssigned(key + ".t")) // text area _lpexView.doCommand("set keyAction." + key + ".t " + actionName); if (!_lpexView.keyAssigned(key + ".p")) // prefix area _lpexView.doCommand("set keyAction." + key + ".p " + actionName); if (!_lpexView.keyAssigned(key + ".c")) // command line _lpexView.doCommand("set keyAction." + key + ".c " + actionName); } // Unconditionally quits this Lpex window. private void quit() { _lpexView.dispose(); _shell.dispose(); } // Safely closes this Lpex window. private void close() { _shell.close(); } // Sets the title of an Lpex window. void setWindowTitle() { String title = "SWT Lpex - " + documentName(); if (_lpexView.documentViews() != 1) { title += ": " + _lpexView.query("viewId"); } _shell.setText(title); } // Creates an Lpex window for a new file. If the file is already opened in // the editor, just goes to a view handling it, optionally refreshing it first. private boolean newWindow(String parameters, final boolean browse, boolean refresh) { final String[] parms = LpexStringTokenizer.split(parameters); // extract file name, see if it is already opened in the editor if (parms.length > 0 && !parms[0].startsWith("-") && !parms[0].startsWith("/")) { LpexView lpexView = _lpexView.lpexView(LpexStringTokenizer.trimQuotes(parms[0])); if (lpexView != null && lpexView.window() != null) { if (refresh) { lpexView.doAction(lpexView.actionId("reload")); } lpexView.window().setFocus(); lpexView.doAction(lpexView.actionId("textWindow")); return true; } } // if new, open a new document for the file _shell.getDisplay().asyncExec(new Runnable() { // à la SwingUtilities#invokeLater() public void run() { Rectangle newBounds = _shell.getBounds(); newBounds.x += 20; newBounds.y += 20; new Lpex(parms, newBounds, browse); } }); return true; } // Creates an additional Lpex window for an already-open document. private boolean newWindow(final Lpex lpex, final boolean browse) { _shell.getDisplay().asyncExec(new Runnable() { public void run() { Rectangle newBounds = _shell.getBounds(); newBounds.x += 20; newBounds.y += 20; try { new Lpex(lpex._lpexViewNode, newBounds, browse); } catch(LpexView.ViewInstantiationException e) {} } }); return true; } /*=============================*/ /* Shell event notifications */ /*=============================*/ // Shell being disposed. private void disposeShell(Event event) { // dispose of our view & its resources (document parser, etc.) if (_lpexView != null) { _lpexView.dispose(); } } // Shell size was set / Shell was resized. private void resizeShell(Event event) { Rectangle rect = _shell.getClientArea(); _lpexWindow.setBounds(0, 0, rect.width, rect.height); rect = _shell.getBounds(); _lpexView.doCommand("set userParameter.SwtLpex.width " + rect.width); _lpexView.doCommand("set userParameter.SwtLpex.height " + rect.height); } // Shell close request. private void closeShell(Event event) { event.doit = closeWindow(); } // Checks whether this Lpex window can be safely closed (saved / user-abandoned). private boolean closeWindow() { boolean close = true; if (_lpexView != null && _lpexView.documentViews() == 1 && // last view? (_lpexView.queryInt("changes") != 0 || _lpexView.queryOn("dirty"))) { MessageBox box = new MessageBox(_shell, SWT.ICON_QUESTION | SWT.YES | SWT.NO | SWT.CANCEL); box.setText(_shell.getText()); box.setMessage(LpexResources.message(LpexMessageConstants.MSG_FILE_SAVE_CHANGES, documentName())); int rc = box.open(); if (rc == SWT.YES) { _lpexView.doCommand("save"); if (_lpexView.query("status") != null) // save failed / was cancelled { close = false; } } else if (rc == SWT.CANCEL) { close = false; } } return close; } /** * This interface allows an external program to have actions and commands of * Lpex views that it created delegated to its own master view of the document. */ public interface Delegate { /** * Redefines actions and commands of the given Lpex view. */ public void delegate(LpexView lpexView); } // keep a list of all Lpex view chains private static ArrayList _viewChains = new ArrayList(); /** * LpexView node for the management of multiple views of one LPEX document. */ static final class LpexViewNode { private LpexViewNode _prev; private LpexViewNode _next; private LpexView _lpexView; private Lpex _lpex; // null = a non-Lpex (external program) master view node private Delegate _delegate; // action/command delegation to the master LpexView // Constructs a top node for an Lpex view. LpexViewNode(Lpex lpex) { _lpex = lpex; _lpexView = lpex._lpexView; _viewChains.add(this); } // Chains a new view node after another node of the same document. LpexViewNode(Lpex newLpex, LpexViewNode oldNode) { _lpex = newLpex; _lpexView = newLpex._lpexView; _prev = oldNode; _next = oldNode._next; if (oldNode._next != null) { oldNode._next._prev = this; } oldNode._next = this; } // Constructs a top node for an external program's master view. private LpexViewNode(LpexView lpexView, Delegate delegate) { _lpexView = lpexView; _delegate = delegate; _viewChains.add(this); _lpexView.addLpexViewListener(new LpexViewAdapter() { // called when the master LpexView is disposed public void disposed(LpexView view) { // clear chain, we may have delegated actions to the now-disposed view disposeAllLpex(); remove(); } }); } // Finds an existing, or constructs a new, top node for an external program's // master view. static LpexViewNode getLpexViewNode(LpexView lpexView, Delegate delegate) { for (int i = 0; i < _viewChains.size(); i++) { LpexViewNode lpexViewNode = (LpexViewNode)_viewChains.get(i); if (lpexViewNode._lpexView == lpexView) { return lpexViewNode; } } // not found, start new view chain return new LpexViewNode(lpexView, delegate); } // Safely disposes of all document's Lpex views. void disposeAllLpex() { // dispose all around the top master view, if any LpexViewNode node = this; while (node != null && node._lpex != null && node._prev != null) { node = node._prev; } while (node._next != null) { node._next._lpex.quit(); } while (node._prev != null) { node._prev._lpex.quit(); } // if not an external-program view, close top view as well if (node._lpex != null) { node._lpex.close(); } } // Removes node. void remove() { // unchain if (_prev != null) { _prev._next = _next; } if (_next != null) { _next._prev = _prev; } // cut self off from Lpex if (_lpex != null) { _lpex._lpexView = null; _lpex._lpexViewNode = null; } // if a top node, remove from list of view chains int i = _viewChains.indexOf(this); if (i >= 0) { _viewChains.remove(i); } } // Retrieves node's view. LpexView lpexView() { return _lpexView; } // Finds document chain's next view. LpexView nextLpexView() { LpexViewNode node = _next; if (node == null) { for (node = _prev; node != null && node._prev != null; node = node._prev) {} } return (node != null)? node._lpexView : null; } // Finds document chain's previous view. LpexView prevLpexView() { LpexViewNode node = _prev; if (node == null) { for (node = _next; node != null && node._next != null; node = node._next) {} } return (node != null)? node._lpexView : null; } // Finds the master view's delegate in this Lpex view's chain. Delegate findDelegate() { for (LpexViewNode node = _prev; node != null; node = node._prev) { if (node._lpex == null) { return node._delegate; } } return null; } } }