001    /*
002     * Created on Jan 12, 2008
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
005     * the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
010     * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
011     * specific language governing permissions and limitations under the License.
012     *
013     * Copyright @2008-2010 the original author or authors.
014     */
015    package org.fest.swing.driver;
016    
017    import static java.lang.String.valueOf;
018    import static org.fest.util.Arrays.format;
019    import static org.fest.util.Strings.concat;
020    
021    import java.awt.Point;
022    import java.awt.Rectangle;
023    
024    import javax.swing.JTree;
025    import javax.swing.tree.TreePath;
026    
027    import org.fest.swing.annotation.RunsInCurrentThread;
028    import org.fest.swing.exception.LocationUnavailableException;
029    import org.fest.swing.util.Pair;
030    
031    /**
032     * Understands a visible location on a <code>{@link JTree}</code>. A row index or a <code>{@link String}</code>ified
033     * <code>{@link TreePath}</code> (i.e. each <code>{@link TreePath}</code> component is a <code>String</code>) or
034     * a <code>{@link TreePath}</code> of <code>Object</code> may be used to indicate the location. Note that if a
035     * <code>{@link TreePath}</code> is used, the entire path leading up to the designated node must be viewable at the
036     * time the location is used.
037     *
038     * @author Alex Ruiz
039     */
040    public final class JTreeLocation {
041    
042      /**
043       * Returns the bounds and visible coordinates of the given row.
044       * <p>
045       * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
046       * responsible for calling this method from the EDT.
047       * </p>
048       * @param tree the target <code>JTree</code>.
049       * @param row the given row.
050       * @return the the bounds and visible coordinates of the given row.
051       * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
052       * visible rows in the <code>JTree</code>.
053       * @throws LocationUnavailableException if a tree path for the given row cannot be found.
054       * @since 1.2
055       */
056      @RunsInCurrentThread
057      public Pair<Rectangle, Point> rowBoundsAndCoordinates(JTree tree, int row) {
058        Rectangle rowBounds = tree.getRowBounds(validIndex(tree, row));
059        if (rowBounds != null) return new Pair<Rectangle, Point>(rowBounds, pointAt(rowBounds));
060        throw new LocationUnavailableException(concat("The tree row ", row, " is not visible"));
061      }
062    
063      /**
064       * Returns the path for the given row.
065       * <p>
066       * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
067       * responsible for calling this method from the EDT.
068       * </p>
069       * @param tree the target <code>JTree</code>.
070       * @param row the given row.
071       * @return the path for the given row.
072       * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
073       * visible rows in the <code>JTree</code>.
074       * @throws LocationUnavailableException if a tree path for the given row cannot be found.
075       */
076      @RunsInCurrentThread
077      public TreePath pathFor(JTree tree, int row) {
078        TreePath path = tree.getPathForRow(validIndex(tree, row));
079        if (path != null) return path;
080        throw new LocationUnavailableException(concat("Unable to find tree path for row [", valueOf(row), "]"));
081      }
082    
083      /**
084       * Validates that the given row index is valid.
085       * <p>
086       * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
087       * responsible for calling this method from the EDT.
088       * </p>
089       * @param tree the target <code>JTree</code>.
090       * @param row the row index to validate.
091       * @return the validated row index.
092       * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
093       * visible rows in the <code>JTree</code>.
094       */
095      @RunsInCurrentThread
096      public int validIndex(JTree tree, int row) {
097        int rowCount = tree.getRowCount();
098        if (row >= 0 && row < rowCount) return row;
099        throw new IndexOutOfBoundsException(concat(
100            "The given row (", valueOf(row), ") should be greater than or equal to 0 and less than ", valueOf(rowCount)));
101      }
102    
103      /**
104       * Returns the bounds and visible coordinates of the given path.
105       * <p>
106       * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
107       * responsible for calling this method from the EDT.
108       * </p>
109       * @param tree the target <code>JTree</code>.
110       * @param path the given path.
111       * @return the bounds and visible coordinates of the given path.
112       * @throws LocationUnavailableException if any part of the path is not visible.
113       * @since 1.2
114       */
115      @RunsInCurrentThread
116      public Pair<Rectangle, Point> pathBoundsAndCoordinates(JTree tree, TreePath path) {
117        Rectangle pathBounds = tree.getPathBounds(path);
118        if (pathBounds != null) return new Pair<Rectangle, Point>(pathBounds, pointAt(pathBounds));
119        throw new LocationUnavailableException(concat("The tree path ", format(path.getPath()), " is not visible"));
120      }
121    
122      private Point pointAt(Rectangle cellBounds) {
123        return new Point(cellBounds.x + cellBounds.width / 2, cellBounds.y + cellBounds.height / 2);
124      }
125    }