package abbot.editor;
import java.io.IOException;
import java.util.*;
import javax.swing.table.AbstractTableModel;
import org.jdom.Element;
import abbot.Log;
import abbot.i18n.Strings;
import abbot.script.*;
/** Formats a Script for display in a table. Keeps track of
* "open" nodes to create a tree-like display
* NOTE: this is a brute-force implementation with no attempts at
* optimization. But it's a very simple tree+table implementation.
*/
class ScriptModel extends AbstractTableModel {
private HashSet openSequences = new HashSet();
private HashMap parents = new HashMap();
private Script script = null;
private ArrayList rows = new ArrayList();
/** Encapsulate information we need to manipulate a row. Note that Entry
* objects exist only for those steps which are "visible", i.e. children
* of closed sequences have no Entry.
*/
private class Entry implements XMLifiable {
public Step step;
public Sequence parent;
public int nestingDepth;
public Entry(Step step, Sequence parent, int nestingDepth) {
this.step = step;
this.parent = parent;
this.nestingDepth = nestingDepth;
}
/** What to display. */
public String toString() {
String str = step.toString();
if ((step instanceof Script)
&& !step.getDescription().startsWith("Script")) {
str += " - " + step.getDescription();
}
return str;
}
/** What to edit. */
public String toEditableString() {
return step.toEditableString();
}
public Element toXML() {
return step.toXML();
}
}
public ScriptModel() {
this(null);
}
public ScriptModel(Script script) {
setScript(script);
}
private void layout(boolean scanParents) {
if (scanParents)
mapParents(script);
rows.clear();
if (script != null) {
addSubRows(script, 0);
}
//Log.debug("Layout finished with " + rows.size() + " rows");
fireTableDataChanged();
}
/** Remove the given step from the script. */
public synchronized void removeStep(Step step) {
getParent(step).removeStep(step);
openSequences.remove(step);
layout(true);
}
/** Remove all the given steps. If any are not found, an exception is
thrown before any changes are made.
*/
public synchronized void removeSteps(List steps) {
Iterator iter = steps.iterator();
while (iter.hasNext()) {
Step step = (Step)iter.next();
getParent(step);
}
iter = steps.iterator();
while (iter.hasNext()) {
Step step = (Step)iter.next();
getParent(step).removeStep(step);
openSequences.remove(step);
}
layout(true);
}
/** Insert the given step at the given index in its parent. */
public synchronized void insertStep(Sequence parent, Step step, int index) {
parent.addStep(index, step);
layout(true);
}
/** Insert the steps into the given sequence at the given index. */
public synchronized void insertSteps(Sequence parent, List steps, int index) {
Iterator iter = steps.iterator();
while (iter.hasNext()) {
parent.addStep(index++, (Step)iter.next());
}
layout(true);
}
private Entry getEntry(int row) {
if (row > rows.size() -1 || row < 0)
throw new IllegalArgumentException("Row " + row
+ " out of bounds ("
+ rows.size() + " available)");
return (Entry)rows.get(row);
}
/** Returns -1 if the step is not found or not visible. */
public synchronized int getRowOf(Step step) {
if (step != script) {
//Log.debug("Checking " + rows.size() + " rows");
for (int i=0;i < rows.size();i++) {
Entry entry = getEntry(i);
if (entry.step.equals(step))
return i;
else
Log.debug("Not in " + entry.step);
}
Log.debug("Step " + step + " not found in (maybe not visible)");
}
return -1;
}
/** Return whether the given row is "open". */
public synchronized boolean isOpen(int row) {
return openSequences.contains(getEntry(row).step);
}
public synchronized boolean isOpen(Step step) {
return openSequences.contains(step);
}
/** Toggle the open state of the node, if it's a sequence. */
public synchronized void toggle(int row) {
Step step = getEntry(row).step;
if (step instanceof Sequence) {
if (openSequences.contains(step))
openSequences.remove(step);
else
openSequences.add(step);
layout(false);
}
}
/** Set the script to display. Don't allow any model accesses until
this method has completed.
*/
public synchronized void setScript(Script script) {
//Log.debug("Setting table model script to " + script);
this.script = script;
openSequences.clear();
if (script != null) {
openSequences.add(script);
}
layout(true);
//Log.debug("Model has " + rows.size() + " rows");
}
public synchronized int getRowCount() {
if (script == null)
return 0;
return rows.size();
}
public int getColumnCount() { return 1; }
public synchronized Step getStepAt(int row) {
return getEntry(row).step;
}
private void validate(int row, int col) {
if (row < 0 || row > getRowCount()-1)
throw new IllegalArgumentException("Invalid row " + row);
if (col != 0)
throw new IllegalArgumentException("Invalid column " + col);
}
/** Returns the step at the given row. */
public synchronized Object getValueAt(int row, int col) {
validate(row, col);
return getStepAt(row);
}
/** Assumes value is XML for a script step.
*/
// FIXME: I don't think this is used any longer now that editors are
// available for all script steps.
public synchronized void setValueAt(Object value, int row, int col) {
validate(row, col);
if (col == 0) {
//Log.debug("Setting value at " + row + " to " + value);
Entry entry = getEntry(row);
if (entry.step instanceof Script) {
// FIXME maybe use a file chooser instead?
//Log.debug("Set script value to " + value);
Script old = (Script)entry.step;
Script step = new Script((String)value, script.getHierarchy());
step.setRelativeTo(old.getRelativeTo());
Sequence parent = entry.parent != null
? entry.parent : script;
parent.setStep(parent.indexOf(old), step);
layout(true);
}
else if (entry.step instanceof Sequence) {
String desc = (String)value;
if (!"".equals(desc)
&& !entry.step.getDescription().equals(desc)) {
entry.step.setDescription(desc);
}
}
else {
try {
Step step = Step.createStep(script, (String)value);
Sequence parent = entry.parent != null
? entry.parent : script;
parent.setStep(parent.indexOf(entry.step), step);
layout(true);
}
catch(IllegalArgumentException e) {
Log.warn(e);
}
catch(InvalidScriptException e) {
// Edit rejected
Log.warn(e);
}
catch(IOException e) {
Log.warn(e);
}
}
}
}
public String getColumnName(int col) { return ""; }
public Class getColumnClass(int col) {
if (col == 0)
return Entry.class;
return Object.class;
}
public Script getScript() { return script; }
public synchronized int getNestingDepthAt(int row) {
return row < 0 || row >= getRowCount()
? 0 : getEntry(row).nestingDepth;
}
public synchronized Script getScriptOf(int row) {
validate(row, 0);
Entry entry = getEntry(row);
Sequence parent = entry.parent;
while (!(parent instanceof Script))
parent = getParent(parent);
return (Script)parent;
}
/** Return the parent sequence of the given step. */
public synchronized Sequence getParent(Step step) {
Sequence seq = (Sequence)parents.get(step);
if (seq == null) {
throw new IllegalArgumentException("Step " + step
+ " not found in "
+ getScript());
}
return seq;
}
/** Keep track of the parent for any given step to aid in editing. */
private void mapParents(Sequence seq) {
if (seq == null)
return;
else if (seq == getScript()) {
parents.clear();
}
Iterator iter = seq.steps().iterator();
while (iter.hasNext()) {
Step step = (Step)iter.next();
parents.put(step, seq);
if (step instanceof Sequence) {
mapParents((Sequence)step);
}
}
}
/** Add row entries corresponding to the contents of the given sequence
* if it's toggled open.
*/
private void addSubRows(Sequence seq, int level) {
if (openSequences.contains(seq)) {
//Log.debug("Adding " + seq.steps().size() + " rows");
Iterator iter = seq.steps().iterator();
while (iter.hasNext()) {
Step step = (Step)iter.next();
//Log.debug("Adding " + step);
rows.add(new Entry(step, seq, level));
if (step instanceof Sequence) {
addSubRows((Sequence)step, level + 1);
}
}
}
}
/** Move the given steps and all between them to the new location.
If the steps are being moved later in the same sequence, the index
represents the target index <i>before</i> the move.
*/
public synchronized void moveSteps(Sequence parent, List steps, int index) {
Step indexStep = index < parent.size() ? parent.getStep(index) : null;
// Remove all, then insert all; otherwise moving steps down in a
// sequence would fail
Iterator iter = steps.iterator();
while (iter.hasNext()) {
Step step = (Step)iter.next();
getParent(step).removeStep(step);
}
iter = steps.iterator();
index = indexStep != null ? parent.indexOf(indexStep) : parent.size();
while (iter.hasNext()) {
Step step = (Step)iter.next();
parent.addStep(index++, step);
}
layout(true);
}
}