001: package abbot.editor;
002:
003: import java.io.IOException;
004: import java.util.*;
005:
006: import javax.swing.table.AbstractTableModel;
007:
008: import org.jdom.Element;
009:
010: import abbot.Log;
011: import abbot.script.*;
012:
013: /** Formats a Script for display in a table. Keeps track of
014: * "open" nodes to create a tree-like display
015: * NOTE: this is a brute-force implementation with no attempts at
016: * optimization. But it's a very simple tree+table implementation.
017: */
018: class ScriptModel extends AbstractTableModel {
019: private HashSet openSequences = new HashSet();
020: private HashMap parents = new HashMap();
021: private Script script = null;
022: private ArrayList rows = new ArrayList();
023:
024: /** Encapsulate information we need to manipulate a row. Note that Entry
025: * objects exist only for those steps which are "visible", i.e. children
026: * of closed sequences have no Entry.
027: */
028: private class Entry implements XMLifiable {
029: public Step step;
030: public Sequence parent;
031: public int nestingDepth;
032:
033: public Entry(Step step, Sequence parent, int nestingDepth) {
034: this .step = step;
035: this .parent = parent;
036: this .nestingDepth = nestingDepth;
037: }
038:
039: /** What to display. */
040: public String toString() {
041: String str = step.toString();
042: if ((step instanceof Script)
043: && !step.getDescription().startsWith("Script")) {
044: str += " - " + step.getDescription();
045: }
046: return str;
047: }
048:
049: /** What to edit. */
050: public String toEditableString() {
051: return step.toEditableString();
052: }
053:
054: public Element toXML() {
055: return step.toXML();
056: }
057: }
058:
059: public ScriptModel() {
060: this (null);
061: }
062:
063: public ScriptModel(Script script) {
064: setScript(script);
065: }
066:
067: private void layout(boolean scanParents) {
068: if (scanParents)
069: mapParents(script);
070: rows.clear();
071: if (script != null) {
072: addSubRows(script, 0);
073: }
074: //Log.debug("Layout finished with " + rows.size() + " rows");
075: fireTableDataChanged();
076: }
077:
078: /** Remove the given step from the script. */
079: public synchronized void removeStep(Step step) {
080: getParent(step).removeStep(step);
081: openSequences.remove(step);
082: layout(true);
083: }
084:
085: /** Remove all the given steps. If any are not found, an exception is
086: thrown before any changes are made.
087: */
088: public synchronized void removeSteps(List steps) {
089: Iterator iter = steps.iterator();
090: while (iter.hasNext()) {
091: Step step = (Step) iter.next();
092: getParent(step);
093: }
094: iter = steps.iterator();
095: while (iter.hasNext()) {
096: Step step = (Step) iter.next();
097: getParent(step).removeStep(step);
098: openSequences.remove(step);
099: }
100: layout(true);
101: }
102:
103: /** Insert the given step at the given index in its parent. */
104: public synchronized void insertStep(Sequence parent, Step step,
105: int index) {
106: parent.addStep(index, step);
107: layout(true);
108: }
109:
110: /** Insert the steps into the given sequence at the given index. */
111: public synchronized void insertSteps(Sequence parent, List steps,
112: int index) {
113: Iterator iter = steps.iterator();
114: while (iter.hasNext()) {
115: parent.addStep(index++, (Step) iter.next());
116: }
117: layout(true);
118: }
119:
120: private Entry getEntry(int row) {
121: if (row > rows.size() - 1 || row < 0)
122: throw new IllegalArgumentException("Row " + row
123: + " out of bounds (" + rows.size() + " available)");
124: return (Entry) rows.get(row);
125: }
126:
127: /** Returns -1 if the step is not found or not visible. */
128: public synchronized int getRowOf(Step step) {
129: if (step != script) {
130: //Log.debug("Checking " + rows.size() + " rows");
131: for (int i = 0; i < rows.size(); i++) {
132: Entry entry = getEntry(i);
133: if (entry.step.equals(step)) {
134: return i;
135: }
136: Log.debug("Not in " + entry.step);
137: }
138: Log.debug("Step " + step
139: + " not found in (maybe not visible)");
140: }
141: return -1;
142: }
143:
144: /** Return whether the given row is "open". */
145: public synchronized boolean isOpen(int row) {
146: return openSequences.contains(getEntry(row).step);
147: }
148:
149: public synchronized boolean isOpen(Step step) {
150: return openSequences.contains(step);
151: }
152:
153: /** Toggle the open state of the node, if it's a sequence. */
154: public synchronized void toggle(int row) {
155: Step step = getEntry(row).step;
156: if (step instanceof Sequence) {
157: if (openSequences.contains(step))
158: openSequences.remove(step);
159: else
160: openSequences.add(step);
161: layout(false);
162: }
163: }
164:
165: /** Set the script to display. Don't allow any model accesses until
166: this method has completed.
167: */
168: public synchronized void setScript(Script script) {
169: //Log.debug("Setting table model script to " + script);
170: this .script = script;
171: openSequences.clear();
172: if (script != null) {
173: openSequences.add(script);
174: }
175: layout(true);
176: //Log.debug("Model has " + rows.size() + " rows");
177: }
178:
179: public synchronized int getRowCount() {
180: if (script == null)
181: return 0;
182: return rows.size();
183: }
184:
185: public int getColumnCount() {
186: return 1;
187: }
188:
189: public synchronized Step getStepAt(int row) {
190: return getEntry(row).step;
191: }
192:
193: private void validate(int row, int col) {
194: if (row < 0 || row > getRowCount() - 1)
195: throw new IllegalArgumentException("Invalid row " + row);
196: if (col != 0)
197: throw new IllegalArgumentException("Invalid column " + col);
198: }
199:
200: /** Returns the step at the given row. */
201: public synchronized Object getValueAt(int row, int col) {
202: validate(row, col);
203: return getStepAt(row);
204: }
205:
206: /** Assumes value is XML for a script step.
207: */
208: // FIXME: I don't think this is used any longer now that editors are
209: // available for all script steps.
210: public synchronized void setValueAt(Object value, int row, int col) {
211: validate(row, col);
212: if (col == 0) {
213: //Log.debug("Setting value at " + row + " to " + value);
214: Entry entry = getEntry(row);
215: if (entry.step instanceof Script) {
216: // FIXME maybe use a file chooser instead?
217: //Log.debug("Set script value to " + value);
218: Script old = (Script) entry.step;
219: Script step = new Script((String) value, script
220: .getHierarchy());
221: step.setRelativeTo(old.getRelativeTo());
222: Sequence parent = entry.parent != null ? entry.parent
223: : script;
224: parent.setStep(parent.indexOf(old), step);
225: layout(true);
226: } else if (entry.step instanceof Sequence) {
227: String desc = (String) value;
228: if (!"".equals(desc)
229: && !entry.step.getDescription().equals(desc)) {
230: entry.step.setDescription(desc);
231: }
232: } else {
233: try {
234: Step step = Step.createStep(script, (String) value);
235: Sequence parent = entry.parent != null ? entry.parent
236: : script;
237: parent.setStep(parent.indexOf(entry.step), step);
238: layout(true);
239: } catch (IllegalArgumentException e) {
240: Log.warn(e);
241: } catch (InvalidScriptException e) {
242: // Edit rejected
243: Log.warn(e);
244: } catch (IOException e) {
245: Log.warn(e);
246: }
247: }
248: }
249: }
250:
251: public String getColumnName(int col) {
252: return "";
253: }
254:
255: public Class getColumnClass(int col) {
256: if (col == 0)
257: return Entry.class;
258: return Object.class;
259: }
260:
261: public Script getScript() {
262: return script;
263: }
264:
265: public synchronized int getNestingDepthAt(int row) {
266: return row < 0 || row >= getRowCount() ? 0
267: : getEntry(row).nestingDepth;
268: }
269:
270: public synchronized Script getScriptOf(int row) {
271: validate(row, 0);
272: Entry entry = getEntry(row);
273: Sequence parent = entry.parent;
274: while (!(parent instanceof Script))
275: parent = getParent(parent);
276: return (Script) parent;
277: }
278:
279: /** Return the parent sequence of the given step. */
280: public synchronized Sequence getParent(Step step) {
281: Sequence seq = (Sequence) parents.get(step);
282: if (seq == null) {
283: throw new IllegalArgumentException("Step " + step
284: + " not found in " + getScript());
285: }
286: return seq;
287: }
288:
289: /** Keep track of the parent for any given step to aid in editing. */
290: private void mapParents(Sequence seq) {
291: if (seq == null)
292: return;
293: else if (seq == getScript()) {
294: parents.clear();
295: }
296: Iterator iter = seq.steps().iterator();
297: while (iter.hasNext()) {
298: Step step = (Step) iter.next();
299: parents.put(step, seq);
300: if (step instanceof Sequence) {
301: mapParents((Sequence) step);
302: }
303: }
304: }
305:
306: /** Add row entries corresponding to the contents of the given sequence
307: * if it's toggled open.
308: */
309: private void addSubRows(Sequence seq, int level) {
310: if (openSequences.contains(seq)) {
311: //Log.debug("Adding " + seq.steps().size() + " rows");
312: Iterator iter = seq.steps().iterator();
313: while (iter.hasNext()) {
314: Step step = (Step) iter.next();
315: //Log.debug("Adding " + step);
316: rows.add(new Entry(step, seq, level));
317: if (step instanceof Sequence) {
318: addSubRows((Sequence) step, level + 1);
319: }
320: }
321: }
322: }
323:
324: /** Move the given steps and all between them to the new location.
325: If the steps are being moved later in the same sequence, the index
326: represents the target index <i>before</i> the move.
327: */
328: public synchronized void moveSteps(Sequence parent, List steps,
329: int index) {
330: Step indexStep = index < parent.size() ? parent.getStep(index)
331: : null;
332: // Remove all, then insert all; otherwise moving steps down in a
333: // sequence would fail
334: Iterator iter = steps.iterator();
335: while (iter.hasNext()) {
336: Step step = (Step) iter.next();
337: getParent(step).removeStep(step);
338: }
339: iter = steps.iterator();
340: index = indexStep != null ? parent.indexOf(indexStep) : parent
341: .size();
342: while (iter.hasNext()) {
343: Step step = (Step) iter.next();
344: parent.addStep(index++, step);
345: }
346: layout(true);
347: }
348: }
|