//----------------------------------------------------------------------------
// COMPONENT NAME: LPEX Editor
//
// © Copyright IBM Corporation 1998, 2008
// 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>Syntax for running Lpex from the command line:
* <pre>
* java [<i>java options</i>] com.ibm.lpex.samples.Lpex [<i>filename</i>]
* [-encoding <i>charEncoding</i>] [-dt {<i>documentType</i> | none}] </pre>
* For example:
* <pre>
* java com.ibm.lpex.samples.Lpex \sample.props -dt properties </pre>
* A possible Windows batch program is:
* <pre>
* @start /b javaw com.ibm.lpex.samples.Lpex %1 %2 %3 %4 %5 %6 %7 %8 %9 </pre>
* (use <code>java</code> instead of <code>javaw</code> to see the stack trace for exceptions).
* To disable the JIT compiler, run it with this java 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 java options:
* <pre>
* -Duser.language=zh -Duser.region=CN </pre></p>
*
* Commands and actions defined in here:
* <ul>
* <li><b>Lpex</b>, <b>e</b> commands - open a new Lpex window
* <li><b>exit</b> command - close all Lpex views of the document
* <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>view</b> { log | profile } command - view the LPEX editor log / defaults profile
* <li><b>close</b> action (F3) - close the Lpex window
* <li><b>nextView</b> action (Alt+Shift+->) - 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 (Alt+Shift+<-) - 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, 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),
* Boolean.valueOf(<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; // our shell
private boolean _browse; // opened as read only
private LpexWindow _lpexWindow;
private LpexView _lpexView;
LpexViewNode _lpexViewNode;
private Shell _papaShell;
private Listener _papaShellListener;
/**
* Entry point.
*/
public static void main(String[] args)
{
new Lpex(args, null /*bounds*/, false /*browse*/, null /*papaShell*/);
}
/**
* 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>, enforce read-only mode
* @param papaShell Shell of the external program that started us, or <code>null</code>
*/
public Lpex(String[] parms, Rectangle bounds, boolean browse, Shell papaShell)
{
// extract file name and parameters
LpexParameters p = new LpexParameters(parms);
String filename = p.filename();
// 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,
_lpexView = new LpexView(filename, p.encoding(), false); // do "updateProfile" later
_lpexViewNode = new LpexViewNode(this);
// determine document parser to use
if (p.documentType() != null)
{
if ("none".equals(p.documentType()))
{
_lpexView.doCommand("set updateProfile.noParser on");
}
else
{
_lpexView.doCommand("set updateProfile.parser " +
_lpexView.query("current.updateProfile.parserAssociation." + p.documentType()));
}
}
// opened from an external program, asked to listen to its demise
if (papaShell != null)
{
_papaShell = papaShell;
_papaShellListener = new Listener() {
public void handleEvent(Event event) {
if (event.type == SWT.Close)
{
if (_lpexView.queryInt("changes") == 0 && !_lpexView.queryOn("dirty"))
{
_lpexView.doCommand("exit"); // clean doc - close immediately
}
else if (_shell != null)
{
_shell.getDisplay().asyncExec(new Runnable() {
public void run() {
if (_lpexView != null)
{
_lpexView.doCommand("exit"); // changes pending - must prompt user
}
}});
}
}
else if (event.type == SWT.Dispose)
{
_papaShell = null;
_papaShellListener = null;
}
}};
_papaShell.addListener(SWT.Close, _papaShellListener);
_papaShell.addListener(SWT.Dispose, _papaShellListener);
}
// initialize the view
setUpView(bounds, browse, null);
}
/**
* Constructor for an external program to create a new (secondary) view on its
* 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>, enforce 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>, enforce 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 the view
setUpView(bounds, browse, lpexView);
}
/**
* Initializes the new Lpex view.
* @param bounds size and position for the window
* @param browse if <code>true</code>, enforce read-only mode
* @param fromView optional document view from which this Lpex was started
*/
private void setUpView(Rectangle bounds, boolean browse, LpexView fromView)
{
_browse = browse;
// 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() {
public void renamed(LpexView lpexView)
{ Lpex.this.renamed(lpexView); }
public void updateProfile(LpexView lpexView)
{ Lpex.this.updateProfile(lpexView); }
public void disposed(LpexView lpexView)
{ _lpexViewNode.remove(); }
});
// run the "updateProfile" command
_lpexView.doDefaultCommand("updateProfile");
// for a regular document with an underlying file, if file is currently
// read only listen to the user changing its attribute to read/write
if (_lpexView.queryOn("readonly"))
{
if (!_browse && hasUnderlyingFile())
{
_lpexView.window().textWindow().addFocusListener(new FocusListener() {
public void focusGained(FocusEvent e) {
if (_lpexView != null && !isFileReadonly())
{
_lpexView.doCommand("set readonly off");
_lpexView.doCommand("screenShow view");
_lpexView.window().textWindow().removeFocusListener(this);
}
}
public void focusLost(FocusEvent e) {}
});
}
}
// start up in originating view's read-only state
else
{
if (fromView != null && fromView.queryOn("readonly"))
{
_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"));
String element = fromView.query("element");
if (element != null) // there are visible elements
{
_lpexView.doCommand("locate element " + 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 window: read & dispatch events from the OS queue until it's disposed
Display display = _shell.getDisplay();
while (!_shell.isDisposed())
{
// no events for us in the queue, sleep to give other applications a chance to run
if (!display.readAndDispatch())
{
display.sleep();
}
}
_shell = null;
}
// Returns document name / locale-sensitive "Untitled Document n".
private String documentName()
{
String name = _lpexView.query("name");
return (name != null)? name : LpexResources.message(LpexMessageConstants.MSG_UNTITLED_DOCUMENT,
_lpexView.query("documentId"));
}
// Returns whether this view's document has an underlying file.
private boolean hasUnderlyingFile()
{
boolean hasFile = false;
String name = _lpexView.query("name");
if (name != null && name.length() > 0)
{
try
{ hasFile = (new File(name)).exists(); }
catch (SecurityException e) // read access denied to file
{ hasFile = true; }
catch (Exception e) {}
}
return hasFile;
}
// Returns the read-only state of the underlying file.
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 read/write file
{ readonly = true; }
catch (Exception e) {}
}
return readonly;
}
/*======================*/
/* LPEX notifications */
/*======================*/
// Called after a "set name" command was run.
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();
}
// Called whenever the "updateProfile" command has been processed.
// Defines editor commands, actions & their associated keys.
private void updateProfile(LpexView lpexView)
{
// asked to keep this view as read only
if (_browse && !lpexView.queryOn("readonly"))
{
lpexView.doCommand("set readonly on");
}
// "Lpex" command - open an Lpex window
lpexView.defineCommand("Lpex", new LpexCommand() {
public boolean doCommand(LpexView view, String parameters)
{ return newWindow(parameters, false, false); } });
// "e" as synonym for the "Lpex" command
lpexView.defineCommand("e", new LpexCommand() {
public boolean doCommand(LpexView view, String parameters)
{ return view.doCommand("Lpex " + parameters); } });
// "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)
{ if (view != null) 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 with 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._browse); }
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("a-s-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("a-s-left", "prevView");
// "view log | profile" command - view the LPEX editor log / defaults profile
lpexView.defineCommand("view", new LpexCommand() {
public boolean doCommand(LpexView view, String parameters)
{ if ("log".equals(parameters))
{ return newWindow('\"' + LpexView.globalQuery("editorLog") + '\"', true, true); }
if ("profile".equals(parameters))
{ LpexView.doGlobalCommand("profile flush");
return newWindow('\"' + LpexView.globalQuery("defaultProfile") + '\"', true, true); }
return (view != null)?
view.doCommand("set messageText Syntax: view { log | profile }") : true;
}});
// "test" command - placeholder for testing new commands
lpexView.defineCommand("test", new LpexCommand() {
public boolean doCommand(LpexView view, String parameters)
{ return (view != null)? view.doCommand("set messageText test: " + parameters) : 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.queryInt("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
String filename = (new LpexParameters(parms)).filename();
if (filename != null)
{
LpexView lpexView = _lpexView.lpexView(filename);
if (lpexView != null && lpexView.window() != null)
{
if (refresh)
{
lpexView.doAction(lpexView.actionId("reload"));
}
if (browse)
{
lpexView.doCommand("set readonly on"); // re-set "readonly" after 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() {
new Lpex(parms, newWindowBounds(), browse, null);
}});
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() {
try
{
new Lpex(lpex._lpexViewNode, newWindowBounds(), browse);
}
catch(LpexView.ViewInstantiationException e) {}
}});
return true;
}
// Returns acceptable bounds for a new window.
private Rectangle newWindowBounds()
{
if (_shell.getMaximized())
{
return new Rectangle(5, 5,
_lpexView.queryInt("userParameter.SwtLpex.width"),
_lpexView.queryInt("userParameter.SwtLpex.height"));
}
// new window a bit to the right, down by less than title height unless too low
Rectangle newBounds = _shell.getBounds();
Rectangle shellTrim = _shell.computeTrim(0, 0, 0, 0);
newBounds.x -= shellTrim.x - 3;
newBounds.y -= shellTrim.y + 2;
Rectangle displayBounds = _shell.getDisplay().getBounds();
if (newBounds.y + newBounds.height > displayBounds.height)
{
newBounds.y = 5;
}
return newBounds;
}
/*=============================*/
/* Shell event notifications */
/*=============================*/
// Shell is being disposed.
private void disposeShell(Event event)
{
// remove our listeners to the external program's Shell
if (_papaShellListener != null)
{
_papaShell.removeListener(SWT.Close, _papaShellListener);
_papaShell.removeListener(SWT.Dispose, _papaShellListener);
_papaShellListener = null;
}
// 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();
if (!_shell.getMaximized())
{
_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.queryInt("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
* secondary {@link com.ibm.lpex.samples.Lpex Lpex} views that it creates,
* delegated to its own master view of the document.
*/
public interface Delegate
{
/**
* Redefine actions and commands of the given Lpex view.
*/
public void delegate(LpexView lpexView);
}
// keep a list of all Lpex view chains
private static ArrayList<LpexViewNode> _viewChains = new ArrayList<LpexViewNode>();
/**
* 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 = _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);
}
}
// Returns this 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;
}
}
}
/**
* This class extracts the Lpex parameters.
*/
final class LpexParameters
{
private String _filename; // file name
private String _encoding = ""; // defaults to autodetect file's encoding
private String _documentType; // overriding document type (e.g., file-name extension)
LpexParameters(String[] parms)
{
for (int i = 0; i < parms.length; i++)
{
// parameter?
if (i < parms.length-1 && (parms[i].startsWith("-") || parms[i].startsWith("/")))
{
String parm = parms[i].substring(1);
if ("enc".equals(parm) || "encoding".equals(parm))
{
_encoding = parms[++i];
continue;
}
else if ("dt".equals(parm) || "extension".equals(parm))
{
_documentType = parms[++i];
continue;
}
}
// file name?
if (i == 0)
{
_filename = LpexStringTokenizer.trimQuotes(parms[i]);
}
// unrecognized parameter
else
{
break;
}
}
}
String filename() { return _filename; }
String encoding() { return _encoding; }
String documentType() { return _documentType; }
}