//----------------------------------------------------------------------------
// 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>
* @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+->) - 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+<-) - 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>
* 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),
* <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;
}
}
}