001: package abbot.tester;
002:
003: import java.awt.*;
004: import java.util.Arrays;
005:
006: import javax.swing.JTree;
007: import javax.swing.tree.TreePath;
008: import javax.swing.tree.TreeModel;
009:
010: import abbot.Log;
011: import abbot.i18n.Strings;
012: import abbot.script.parsers.TreePathParser;
013: import abbot.util.ExtendedComparator;
014:
015: /** Provides encapsulation of a visible location on a {@link JTree}.
016: * A row index or a {@link String}ified {@link TreePath} (i.e. each
017: * {@link TreePath} component is a {@link String}) or a {@link TreePath}
018: * of {@link Object} may be used to indicate the location. Note that if a
019: * {@link TreePath} is used, the entire path leading up to the designated
020: * node must be viewable at the time the location is used.
021: * @see #findMatchingPath for a description of the matching algorithm.
022: */
023: // TODO: add flag for "in icon" vs in hit region
024: public class JTreeLocation extends ComponentLocation {
025: private int row = -1;
026: /** TreePath of Strings. */
027: private TreePath treePath;
028: private boolean inExpansion;
029:
030: /** Create an uninitialized JTreeLocation. If queried for its
031: * {@link Point} will return the default location.
032: */
033: public JTreeLocation() {
034: }
035:
036: /** Create a JTreeLocation corresponding to the given row, by index. */
037: public JTreeLocation(int row) {
038: this (row, false);
039: }
040:
041: /** Create a JTreeLocation corresponding to the given row, by index. */
042: public JTreeLocation(int row, boolean inExpansion) {
043: if (row < 0) {
044: String msg = Strings.get("tester.JTree.row_not_visible",
045: new Object[] { new Integer(row) });
046: throw new LocationUnavailableException(msg);
047: }
048: this .row = row;
049: this .inExpansion = inExpansion;
050: }
051:
052: /** Create a JTreeLocation corresponding to the given TreePath. The
053: * TreePath must consist of usable String representations that can be
054: * used in later comparisons. The default
055: * <classname>@<hashcode> returned by
056: * {@link Object#toString()} is not usable; if that is all that is
057: * available, refer to the row number instead.
058: */
059: public JTreeLocation(TreePath treePath) {
060: this (treePath, false);
061: }
062:
063: /** Create a JTreeLocation corresponding to the given TreePath. The
064: * TreePath must consist of usable String representations that can be
065: * used in later comparisons. The default
066: * <classname>@<hashcode> format returned by
067: * {@link Object#toString()} is not usable; if that is all that is
068: * available, refer to the row number instead.
069: */
070: public JTreeLocation(TreePath treePath, boolean inExpansion) {
071: this .treePath = treePath;
072: this .inExpansion = inExpansion;
073: }
074:
075: public JTreeLocation(Point p) {
076: super (p);
077: }
078:
079: public void setInExpansion(boolean in) {
080: inExpansion = in;
081: }
082:
083: public boolean isInExpansion() {
084: return inExpansion;
085: }
086:
087: /** Convert the given row to an x, y coordinate.
088: @throws LocationUnavailableException if the row is not visible.
089: */
090: protected Point rowToPoint(JTree tree, int row) {
091: TreePath path = tree.getPathForRow(row);
092: if (path == null) {
093: String msg = Strings.get("tester.JTree.row_not_visible",
094: new Object[] { new Integer(row) });
095: throw new LocationUnavailableException(msg);
096: }
097: return pathToPoint(tree, path);
098: }
099:
100: /** Convert the given path to an x, y coordinate.
101: @throws LocationUnavailableException if any part of the path is
102: hidden.
103: */
104: protected Point pathToPoint(JTree tree, TreePath path) {
105: path = findMatchingPath(tree, path);
106: Rectangle rect = tree.getPathBounds(path);
107: if (rect == null) {
108: String msg = Strings.get("tester.JTree.path_not_visible",
109: new Object[] { path });
110: throw new LocationUnavailableException(msg);
111: }
112: if (inExpansion)
113: // FIXME this is only an approximation; should probably get the
114: // location from the UI somehow, but it's not available in the API
115: // FIXME this will probably be bogus if the tree rows are really
116: // tall
117: return new Point(rect.x - rect.height / 2, rect.y
118: + rect.height / 2);
119: return new Point(rect.x + rect.width / 2, rect.y + rect.height
120: / 2);
121: }
122:
123: /** Return the path represented by this JTree location.
124: @return null if the path can not be found.
125: */
126: TreePath getPath(JTree tree) {
127: if (treePath != null) {
128: try {
129: return findMatchingPath(tree, treePath);
130: } catch (LocationUnavailableException e) {
131: return treePath;
132: }
133: }
134: if (row != -1) {
135: return tree.getPathForRow(row);
136: }
137: Point where = super .getPoint(tree);
138: return tree.getPathForLocation(where.x, where.y);
139: }
140:
141: /** Return the row represented by this JTree location.
142: @return -1 if the row is not found.
143: */
144: int getRow(JTree tree) {
145: if (treePath != null)
146: return tree.getRowForPath(getPath(tree));
147: else if (row != -1)
148: return row;
149: Point where = super .getPoint(tree);
150: return tree.getRowForLocation(where.x, where.y);
151: }
152:
153: /** Return a concrete point for the abstract location. */
154: public Point getPoint(Component c) {
155: JTree tree = (JTree) c;
156: if (treePath != null) {
157: // convert the string-based path to a real path
158: return pathToPoint(tree, treePath);
159: }
160: if (row != -1) {
161: return rowToPoint(tree, row);
162: }
163: return super .getPoint(c);
164: }
165:
166: public Rectangle getBounds(Component c) {
167: JTree tree = (JTree) c;
168: int row = getRow(tree);
169: if (row == -1) {
170: Point where = getPoint(c);
171: return new Rectangle(where.x, where.y, 1, 1);
172: }
173: Rectangle rect = tree.getRowBounds(row);
174: if (rect == null) {
175: String msg = Strings.get("tester.JTree.row_not_visible",
176: new Object[] { new Integer(row) });
177: throw new LocationUnavailableException(msg);
178: }
179: if (inExpansion) {
180: rect.x -= rect.height;
181: rect.width = rect.height;
182: }
183: return rect;
184: }
185:
186: public boolean equals(Object o) {
187: if (o instanceof JTreeLocation) {
188: JTreeLocation loc = (JTreeLocation) o;
189: if (loc.inExpansion != inExpansion)
190: return false;
191: if (treePath != null) {
192: if (treePath.getPathCount() != loc.treePath
193: .getPathCount())
194: return false;
195: for (int i = 0; i < treePath.getPathCount(); i++) {
196: if (treePath.getPathComponent(i) == null) {
197: if (loc.treePath.getPathComponent(i) != null)
198: return false;
199: } else if (!treePath.getPathComponent(i).equals(
200: loc.treePath.getPathComponent(i))) {
201: return false;
202: }
203: }
204: return true;
205: }
206: if (row != -1)
207: return row == loc.row;
208: }
209: return super .equals(o);
210: }
211:
212: public String toString() {
213: String s = inExpansion ? "+" : "";
214: if (treePath != null) {
215: return s + encodeValue(treePath.toString());
216: }
217: if (row != -1)
218: return s + encodeIndex(row);
219: return super .toString();
220: }
221:
222: public ComponentLocation parse(String encoded) {
223: encoded = encoded.trim();
224: if (encoded.startsWith("+")) {
225: inExpansion = true;
226: encoded = encoded.substring(1);
227: }
228: if (isValue(encoded)) {
229: String path = parseValue(encoded);
230: treePath = (TreePath) new TreePathParser().parse(path);
231: return this ;
232: } else if (isIndex(encoded)) {
233: row = parseIndex(encoded);
234: return this ;
235: }
236: return super .parse(encoded);
237: }
238:
239: protected String badFormat(String encoded) {
240: return Strings.get("location.tree.bad_format",
241: new Object[] { encoded });
242: }
243:
244: /** Return whether the given Object matches the final element of the given
245: * TreePath. {@link Object#equals} is attempted first (a
246: * <code>null</code> pattern matches everything), followed by a comparison
247: * of the pattern as a {@link String} (via {@link Object#toString}). The
248: * pattern may be a regular expression bounded by forward slashes.
249: */
250: private static boolean matchesLastComponent(JTree tree,
251: Object pattern, TreePath path) {
252: // For matching the root node, or for other nodes you don't care about
253: // whether they match
254: if (pattern == null)
255: return true;
256: if (pattern.equals(path.getLastPathComponent()))
257: return true;
258: // Try a string comparison
259: String objString = JTreeTester.valueToString(tree, path);
260: return ExtendedComparator.stringsMatch(pattern.toString(),
261: objString);
262: }
263:
264: /** Given a {@link TreePath} (which may be composed of objects, string
265: * representations of objects, or regular expressions), return the
266: * equivalent {@link TreePath} for the given {@link JTree} constructed
267: * from objects from the tree's model.<p>
268: * For each element, {@link Object#equals} is attempted first (a
269: * <code>null</code> pattern matches everything), followed by a comparison
270: * of the pattern as a {@link String} (via {@link Object#toString}). The
271: * pattern may be a regular expression bounded by forward slashes.
272: * @throws LocationUnavailableException if no matching path is found.
273: */
274: public static TreePath findMatchingPath(JTree tree, TreePath path) {
275: Log.debug("Find path matching " + path);
276: Object[] input = path.getPath();
277: TreeModel model = tree.getModel();
278: Object root = model.getRoot();
279: // If the root is not visible and it doesn't match the first path
280: // element, start the path with the invisible root.
281: if (!tree.isRootVisible()
282: && !matchesLastComponent(tree, input[0], new TreePath(
283: root))) {
284: Object[] tmp = new Object[input.length + 1];
285: System.arraycopy(input, 0, tmp, 1, input.length);
286: tmp[0] = null; // null always matches root
287: input = tmp;
288: }
289: TreePath realPath = findMatchingPath(tree, new TreePath(root),
290: input);
291: if (realPath != null)
292: return realPath;
293: String msg = Strings.get("tester.JTree.path_not_found",
294: new Object[] { path });
295: throw new LocationUnavailableException(msg);
296: }
297:
298: /** Build up the given tree path with Objects from the TreeModel which
299: * match the given array of Objects.
300: */
301: private static TreePath findMatchingPath(JTree tree,
302: TreePath realPath, Object[] input) {
303: // The given array is a tree path of objects which may or may not be
304: // node objects. Convert them to existing tree objects if necessary.
305: // Return null if any of them are not found.
306: Log.debug("Comparing " + realPath + " with " + input[0]
307: + " from " + java.util.Arrays.asList(input));
308: TreeModel model = tree.getModel();
309: if (!matchesLastComponent(tree, input[0], realPath)) {
310: Log.debug("no root match");
311: } else {
312: Log.debug("node matched: "
313: + realPath.getLastPathComponent());
314: if (input.length == 1)
315: return realPath;
316:
317: Object[] subs = new Object[input.length - 1];
318: System.arraycopy(input, 1, subs, 0, subs.length);
319: Object obj = realPath.getLastPathComponent();
320: int count = model.getChildCount(obj);
321: Log.debug("Obj " + obj + " (" + obj.getClass() + ") has "
322: + count);
323: // Find the right child to match
324: for (int i = 0; i < count; i++) {
325: Object child = model.getChild(obj, i);
326: Log.debug("checking child " + i + " (" + child + ")");
327: TreePath newPath = findMatchingPath(tree, realPath
328: .pathByAddingChild(child), subs);
329: if (newPath != null) {
330: return newPath;
331: }
332: }
333: Log.debug("No child path matched");
334: }
335: return null;
336: }
337: }
|