Package org.fest.swing.driver

Source Code of org.fest.swing.driver.JTreeDriver

/*
* Created on Jan 12, 2008
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
* Copyright @2008-2010 the original author or authors.
*/
package org.fest.swing.driver;

import static org.fest.assertions.Assertions.assertThat;
import static org.fest.swing.core.MouseButton.LEFT_BUTTON;
import static org.fest.swing.core.MouseButton.RIGHT_BUTTON;
import static org.fest.swing.driver.CommonValidations.validateCellReader;
import static org.fest.swing.driver.ComponentStateValidator.validateIsEnabledAndShowing;
import static org.fest.swing.driver.JTreeChildrenShowUpCondition.untilChildrenShowUp;
import static org.fest.swing.driver.JTreeClearSelectionTask.clearSelectionOf;
import static org.fest.swing.driver.JTreeEditableQuery.isEditable;
import static org.fest.swing.driver.JTreeExpandPathTask.expandTreePath;
import static org.fest.swing.driver.JTreeMatchingPathQuery.*;
import static org.fest.swing.driver.JTreeNodeTextQuery.nodeText;
import static org.fest.swing.driver.JTreeToggleExpandStateTask.toggleExpandState;
import static org.fest.swing.driver.JTreeVerifySelectionTask.verifyNoSelection;
import static org.fest.swing.driver.JTreeVerifySelectionTask.verifySelection;
import static org.fest.swing.edt.GuiActionRunner.execute;
import static org.fest.swing.exception.ActionFailedException.actionFailure;
import static org.fest.swing.timing.Pause.pause;
import static org.fest.swing.util.Arrays.isEmptyIntArray;
import static org.fest.util.Arrays.isEmpty;
import static org.fest.util.Strings.concat;

import java.awt.Point;
import java.awt.Rectangle;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.plaf.TreeUI;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.TreePath;

import org.fest.assertions.Description;
import org.fest.swing.annotation.RunsInCurrentThread;
import org.fest.swing.annotation.RunsInEDT;
import org.fest.swing.cell.JTreeCellReader;
import org.fest.swing.core.*;
import org.fest.swing.edt.*;
import org.fest.swing.exception.*;
import org.fest.swing.util.Pair;
import org.fest.swing.util.Triple;
import org.fest.util.VisibleForTesting;

/**
* Understands functional testing of <code>{@link JTree}</code>s:
* <ul>
* <li>user input simulation</li>
* <li>state verification</li>
* <li>property value query</li>
* </ul>
* This class is intended for internal use only. Please use the classes in the package
* <code>{@link org.fest.swing.fixture}</code> in your tests.
*
* @author Alex Ruiz
*/
public class JTreeDriver extends JComponentDriver {

  private static final String EDITABLE_PROPERTY = "editable";
  private static final String SELECTION_PROPERTY = "selection";

  private final JTreeLocation location;
  private final JTreePathFinder pathFinder;

  /**
   * Creates a new </code>{@link JTreeDriver}</code>.
   * @param robot the robot to use to simulate user input.
   */
  public JTreeDriver(Robot robot) {
    super(robot);
    location = new JTreeLocation();
    pathFinder = new JTreePathFinder();
  }

  /**
   * Clicks the given row.
   * @param tree the target <code>JTree</code>.
   * @param row the given row.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
   * visible rows in the <code>JTree</code>.
   * @throws LocationUnavailableException if a tree path for the given row cannot be found.
   * @since 1.2
   */
  @RunsInEDT
  public void clickRow(JTree tree, int row) {
    Point p = scrollToRow(tree, row);
    robot.click(tree, p);
  }

  /**
   * Clicks the given row.
   * @param tree the target <code>JTree</code>.
   * @param row the given row.
   * @param button the mouse button to use.
   * @throws NullPointerException if the given button is <code>null</code>.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
   * visible rows in the <code>JTree</code>.
   * @throws LocationUnavailableException if a tree path for the given row cannot be found.
   * @since 1.2
   */
  @RunsInEDT
  public void clickRow(JTree tree, int row, MouseButton button) {
    validateIsNotNull(button);
    clickRow(tree, row, button, 1);
  }

  /**
   * Clicks the given row.
   * @param tree the target <code>JTree</code>.
   * @param row the given row.
   * @param mouseClickInfo specifies the mouse button to use and how many times to click.
   * @throws NullPointerException if the given <code>MouseClickInfo</code> is <code>null</code>.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
   * visible rows in the <code>JTree</code>.
   * @throws LocationUnavailableException if a tree path for the given row cannot be found.
   * @since 1.2
   */
  @RunsInEDT
  public void clickRow(JTree tree, int row, MouseClickInfo mouseClickInfo) {
    validateIsNotNull(mouseClickInfo);
    clickRow(tree, row, mouseClickInfo.button(), mouseClickInfo.times());
  }

  @RunsInEDT
  private void clickRow(JTree tree, int row, MouseButton button, int times) {
    Point p = scrollToRow(tree, row);
    robot.click(tree, p, button, times);
  }

  /**
   * Double-clicks the given row.
   * @param tree the target <code>JTree</code>.
   * @param row the given row.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
   * visible rows in the <code>JTree</code>.
   * @throws LocationUnavailableException if a tree path for the given row cannot be found.
   * @since 1.2
   */
  @RunsInEDT
  public void doubleClickRow(JTree tree, int row) {
    Point p = scrollToRow(tree, row);
    doubleClick(tree, p);
  }

  /**
   * Right-clicks the given row.
   * @param tree the target <code>JTree</code>.
   * @param row the given row.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
   * visible rows in the <code>JTree</code>.
   * @throws LocationUnavailableException if a tree path for the given row cannot be found.
   * @since 1.2
   */
  @RunsInEDT
  public void rightClickRow(JTree tree, int row) {
    Point p = scrollToRow(tree, row);
    rightClick(tree, p);
  }

  @RunsInEDT
  private Point scrollToRow(JTree tree, int row) {
    Point p = scrollToRow(tree, row, location).ii;
    robot.waitForIdle();
    return p;
  }

  /**
   * Clicks the given path, expanding parent nodes if necessary.
   * @param tree the target <code>JTree</code>.
   * @param path the path to path.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws LocationUnavailableException if the given path cannot be found.
   */
  @RunsInEDT
  public void clickPath(JTree tree, String path) {
    Point p = scrollToPath(tree, path);
    robot.click(tree, p);
  }

  /**
   * Clicks the given path, expanding parent nodes if necessary.
   * @param tree the target <code>JTree</code>.
   * @param path the path to path.
   * @param button the mouse button to use.
   * @throws NullPointerException if the given button is <code>null</code>.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws LocationUnavailableException if the given path cannot be found.
   * @since 1.2
   */
  @RunsInEDT
  public void clickPath(JTree tree, String path, MouseButton button) {
    validateIsNotNull(button);
    clickPath(tree, path, button, 1);
  }

  private void validateIsNotNull(MouseButton button) {
    if (button == null) throw new NullPointerException("The given MouseButton should not be null");
  }

  /**
   * Clicks the given path, expanding parent nodes if necessary.
   * @param tree the target <code>JTree</code>.
   * @param path the path to path.
   * @param mouseClickInfo specifies the mouse button to use and how many times to click.
   * @throws NullPointerException if the given <code>MouseClickInfo</code> is <code>null</code>.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws LocationUnavailableException if the given path cannot be found.
   * @since 1.2
   */
  @RunsInEDT
  public void clickPath(JTree tree, String path, MouseClickInfo mouseClickInfo) {
    validateIsNotNull(mouseClickInfo);
    clickPath(tree, path, mouseClickInfo.button(), mouseClickInfo.times());
  }

  private void validateIsNotNull(MouseClickInfo mouseClickInfo) {
    if (mouseClickInfo == null) throw new NullPointerException("The given MouseClickInfo should not be null");
  }

  private void clickPath(JTree tree, String path, MouseButton button, int times) {
    Point p = scrollToPath(tree, path);
    robot.click(tree, p, button, times);
  }

  /**
   * Double-clicks the given path.
   * @param tree the target <code>JTree</code>.
   * @param path the path to double-click.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws LocationUnavailableException if the given path cannot be found.
   * @since 1.2
   */
  @RunsInEDT
  public void doubleClickPath(JTree tree, String path) {
    Point p = scrollToPath(tree, path);
    doubleClick(tree, p);
  }

  private Point scrollToPath(JTree tree, String path) {
    Point p = scrollToMatchingPath(tree, path).iii;
    robot.waitForIdle();
    return p;
  }

  private void doubleClick(JTree tree, Point p) {
    robot.click(tree, p, LEFT_BUTTON, 2);
  }

  /**
   * Right-clicks the given path, expanding parent nodes if necessary.
   * @param tree the target <code>JTree</code>.
   * @param path the path to path.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws LocationUnavailableException if the given path cannot be found.
   * @since 1.2
   */
  @RunsInEDT
  public void rightClickPath(JTree tree, String path) {
    Point p = scrollToPath(tree, path);
    rightClick(tree, p);
  }

  private void rightClick(JTree tree, Point p) {
    robot.click(tree, p, RIGHT_BUTTON, 1);
  }

  /**
   * Expands the given row, is possible. If the row is already expanded, this method will not do anything.
   * <p>
   * NOTE: a reasonable assumption is that the toggle control is just to the left of the row bounds and is roughly a
   * square the dimensions of the row height. Clicking in the center of that square should work.
   * </p>
   * @param tree the target <code>JTree</code>.
   * @param row the given row.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
   * visible rows in the <code>JTree</code>.
   * @throws LocationUnavailableException if a tree path for the given row cannot be found.
   * @throws ActionFailedException if this method fails to expand the row.
   * @since 1.2
   */
  @RunsInEDT
  public void expandRow(JTree tree, int row) {
    Triple<Boolean, Point, Integer> info = scrollToRowAndGetToggleInfo(tree, row, location);
    robot.waitForIdle();
    if (info.i) return; // already expanded
    toggleCell(tree, info.ii, info.iii);
  }

  /**
   * Collapses the given row, is possible. If the row is already collapsed, this method will not do anything.
   * <p>
   * NOTE: a reasonable assumption is that the toggle control is just to the left of the row bounds and is roughly a
   * square the dimensions of the row height. Clicking in the center of that square should work.
   * </p>
   * @param tree the target <code>JTree</code>.
   * @param row the given row.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
   * visible rows in the <code>JTree</code>.
   * @throws LocationUnavailableException if a tree path for the given row cannot be found.
   * @throws ActionFailedException if this method fails to collapse the row.
   * @since 1.2
   */
  @RunsInEDT
  public void collapseRow(JTree tree, int row) {
    Triple<Boolean, Point, Integer> info = scrollToRowAndGetToggleInfo(tree, row, location);
    robot.waitForIdle();
    if (!info.i) return; // already collapsed
    toggleCell(tree, info.ii, info.iii);
  }

  /**
   * Change the open/closed state of the given row, if possible.
   * <p>
   * NOTE: a reasonable assumption is that the toggle control is just to the left of the row bounds and is roughly a
   * square the dimensions of the row height. Clicking in the center of that square should work.
   * </p>
   * @param tree the target <code>JTree</code>.
   * @param row the given row.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
   * visible rows in the <code>JTree</code>.
   * @throws LocationUnavailableException if a tree path for the given row cannot be found.
   * @throws ActionFailedException if this method fails to toggle the row.
   */
  @RunsInEDT
  public void toggleRow(JTree tree, int row) {
    Triple<Boolean, Point, Integer> info = scrollToRowAndGetToggleInfo(tree, row, location);
    robot.waitForIdle();
    toggleCell(tree, info.ii, info.iii);
  }

  /*
   * Returns:
   * 1. if the row is expanded
   * 2. the location of the row
   * 3. the number of mouse clicks to toggle a row
   */
  @RunsInEDT
  private static Triple<Boolean, Point, Integer> scrollToRowAndGetToggleInfo(final JTree tree, final int row,
      final JTreeLocation location) {
    return execute(new GuiQuery<Triple<Boolean, Point, Integer>>() {
      protected Triple<Boolean, Point, Integer> executeInEDT() {
        validateIsEnabledAndShowing(tree);
        Point p = scrollToVisible(tree, row, location);
        return new Triple<Boolean, Point, Integer>(tree.isExpanded(row), p, tree.getToggleClickCount());
      }
    });
  }

  /**
   * Expands the given path, is possible. If the path is already expanded, this method will not do anything.
   * <p>
   * NOTE: a reasonable assumption is that the toggle control is just to the left of the row bounds and is roughly a
   * square the dimensions of the row height. Clicking in the center of that square should work.
   * </p>
   * @param tree the target <code>JTree</code>.
   * @param path the path to expand.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws LocationUnavailableException if the given path cannot be found.
   * @throws ActionFailedException if this method fails to expand the path.
   * @since 1.2
   */
  @RunsInEDT
  public void expandPath(JTree tree, String path) {
    Triple<Boolean, Point, Integer> info = scrollToMatchingPathAndGetToggleInfo(tree, path, pathFinder, location);
    if (info.i) return; // already expanded
    toggleCell(tree, info.ii, info.iii);
  }

  /**
   * Collapses the given path, is possible. If the path is already expanded, this method will not do anything.
   * <p>
   * NOTE: a reasonable assumption is that the toggle control is just to the left of the row bounds and is roughly a
   * square the dimensions of the row height. Clicking in the center of that square should work.
   * </p>
   * @param tree the target <code>JTree</code>.
   * @param path the path to collapse.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws LocationUnavailableException if the given path cannot be found.
   * @throws ActionFailedException if this method fails to collapse the path.
   * @since 1.2
   */
  @RunsInEDT
  public void collapsePath(JTree tree, String path) {
    Triple<Boolean, Point, Integer> info = scrollToMatchingPathAndGetToggleInfo(tree, path, pathFinder, location);
    if (!info.i) return; // already collapsed
    toggleCell(tree, info.ii, info.iii);
  }

  /*
   * Returns:
   * 1. if the node is expanded
   * 2. the location of the node
   * 3. the number of mouse clicks to toggle a node
   */
  @RunsInEDT
  private static Triple<Boolean, Point, Integer> scrollToMatchingPathAndGetToggleInfo(final JTree tree,
      final String path, final JTreePathFinder pathFinder, final JTreeLocation location) {
    return execute(new GuiQuery<Triple<Boolean, Point, Integer>>() {
      protected Triple<Boolean, Point, Integer> executeInEDT() {
        validateIsEnabledAndShowing(tree);
        TreePath matchingPath = matchingPathFor(tree, path, pathFinder);
        Point p = scrollToTreePath(tree, matchingPath, location);
        return new Triple<Boolean, Point, Integer>(tree.isExpanded(matchingPath), p, tree.getToggleClickCount());
      }
    });
  }

  @RunsInEDT
  private void toggleCell(JTree tree, Point p, int toggleClickCount) {
    if (toggleClickCount == 0) {
      toggleRowThroughTreeUI(tree, p);
      robot.waitForIdle();
      return;
    }
    robot.click(tree, p, LEFT_BUTTON, toggleClickCount);
  }

  @RunsInEDT
  private static void toggleRowThroughTreeUI(final JTree tree, final Point p) {
    execute(new GuiTask() {
      protected void executeInEDT() {
        TreeUI treeUI = tree.getUI();
        if (!(treeUI instanceof BasicTreeUI)) throw actionFailure(concat("Can't toggle row for ", treeUI));
        toggleExpandState(tree, p);
      }
    });
  }

  /**
   * Selects the given rows.
   * @param tree the target <code>JTree</code>.
   * @param rows the rows to select.
   * @throws NullPointerException if the array of rows is <code>null</code>.
   * @throws IllegalArgumentException if the array of rows is empty.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws IndexOutOfBoundsException if any of the given rows is less than zero or equal than or greater than the
   * number of visible rows in the <code>JTree</code>.
   * @throws LocationUnavailableException if a tree path for any of the given rows cannot be found.
   */
  @RunsInEDT
  public void selectRows(final JTree tree, final int[] rows) {
    validateRows(rows);
    clearSelection(tree);
    new MultipleSelectionTemplate(robot) {
      int elementCount() {
        return rows.length;
      }
      void selectElement(int index) {
        selectRow(tree, rows[index]);
      }
    }.multiSelect();
  }

  private void validateRows(final int[] rows) {
    if (rows == null) throw new NullPointerException("The array of rows should not be null");
    if (isEmptyIntArray(rows)) throw new IllegalArgumentException("The array of rows should not be empty");
  }

  @RunsInEDT
  private void clearSelection(final JTree tree) {
    clearSelectionOf(tree);
    robot.waitForIdle();
  }

  /**
   * Selects the given row.
   * @param tree the target <code>JTree</code>.
   * @param row the row to select.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
   * visible rows in the <code>JTree</code>.
   */
  @RunsInEDT
  public void selectRow(JTree tree, int row) {
    scrollAndSelectRow(tree, row);
  }

  /**
   * Selects the given paths, expanding parent nodes if necessary.
   * @param tree the target <code>JTree</code>.
   * @param paths the paths to select.
   * @throws NullPointerException if the array of rows is <code>null</code>.
   * @throws IllegalArgumentException if the array of rows is empty.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws LocationUnavailableException if any the given path cannot be found.
   */
  @RunsInEDT
  public void selectPaths(final JTree tree, final String[] paths) {
    validatePaths(paths);
    clearSelection(tree);
    new MultipleSelectionTemplate(robot) {
      int elementCount() {
        return paths.length;
      }
      void selectElement(int index) {
        selectPath(tree, paths[index]);
      }
    }.multiSelect();
  }

  private void validatePaths(final String[] paths) {
    if (paths == null) throw new NullPointerException("The array of paths should not be null");
    if (isEmpty(paths)) throw new IllegalArgumentException("The array of paths should not be empty");
  }

  /**
   * Selects the given path, expanding parent nodes if necessary. Unlike <code>{@link #clickPath(JTree, String)}</code>,
   * this method will not click the path if it is already selected
   * @param tree the target <code>JTree</code>.
   * @param path the path to select.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws LocationUnavailableException if the given path cannot be found.
   */
  @RunsInEDT
  public void selectPath(JTree tree, String path) {
    selectMatchingPath(tree, path);
  }

  /**
   * Shows a pop-up menu at the position of the node in the given row.
   * @param tree the target <code>JTree</code>.
   * @param row the given row.
   * @return a driver that manages the displayed pop-up menu.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws ComponentLookupException if a pop-up menu cannot be found.
   * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
   * visible rows in the <code>JTree</code>.
   * @throws LocationUnavailableException if a tree path for the given row cannot be found.
   */
  @RunsInEDT
  public JPopupMenu showPopupMenu(JTree tree, int row) {
    Pair<Boolean, Point> info = scrollToRow(tree, row, location);
    Point p = info.ii;
    return robot.showPopupMenu(tree, p);
  }

  /**
   * Shows a pop-up menu at the position of the last node in the given path. The last node in the given path will be
   * made visible (by expanding the parent node(s)) if it is not visible.
   * @param tree the target <code>JTree</code>.
   * @param path the given path.
   * @return a driver that manages the displayed pop-up menu.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws ComponentLookupException if a pop-up menu cannot be found.
   * @throws LocationUnavailableException if the given path cannot be found.
   * @see #separator(String)
   */
  @RunsInEDT
  public JPopupMenu showPopupMenu(JTree tree, String path) {
    Triple<TreePath, Boolean, Point> info = scrollToMatchingPath(tree, path);
    robot.waitForIdle();
    Point where = info.iii;
    return robot.showPopupMenu(tree, where);
  }

  /**
   * Starts a drag operation at the location of the given row.
   * @param tree the target <code>JTree</code>.
   * @param row the given row.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
   * visible rows in the <code>JTree</code>.
   * @throws LocationUnavailableException if a tree path for the given row cannot be found.
   */
  @RunsInEDT
  public void drag(JTree tree, int row) {
    Point p = scrollAndSelectRow(tree, row);
    drag(tree, p);
  }

  @RunsInEDT
  private Point scrollAndSelectRow(JTree tree, int row) {
    Pair<Boolean, Point> info = scrollToRow(tree, row, location);
    Point p = info.ii;
    if (!info.i) robot.click(tree, p); // path not selected, click to select
    return p;
  }

  /**
   * Ends a drag operation at the location of the given row.
   * @param tree the target <code>JTree</code>.
   * @param row the given row.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
   * visible rows in the <code>JTree</code>.
   * @throws LocationUnavailableException if a tree path for the given row cannot be found.
   * @throws ActionFailedException if there is no drag action in effect.
   */
  @RunsInEDT
  public void drop(JTree tree, int row) {
    drop(tree, scrollToRow(tree, row, location).ii);
  }

  /*
   * Returns:
   * 1. if the node is expanded
   * 2. the location of the node
   */
  @RunsInEDT
  private static Pair<Boolean, Point> scrollToRow(final JTree tree, final int row, final JTreeLocation location) {
    return execute(new GuiQuery<Pair<Boolean, Point>>() {
      protected Pair<Boolean, Point> executeInEDT() {
        validateIsEnabledAndShowing(tree);
        Point p = scrollToVisible(tree, row, location);
        boolean selected = tree.getSelectionCount() == 1 && tree.isRowSelected(row);
        return new Pair<Boolean, Point>(selected, p);
      }
    });
  }

  @RunsInCurrentThread
  private static Point scrollToVisible(JTree tree, int row, JTreeLocation location) {
    Pair<Rectangle, Point> boundsAndCoordinates = location.rowBoundsAndCoordinates(tree, row);
    tree.scrollRectToVisible(boundsAndCoordinates.i);
    return boundsAndCoordinates.ii;
  }

  /**
   * Starts a drag operation at the location of the given <code>{@link TreePath}</code>.
   * @param tree the target <code>JTree</code>.
   * @param path the given path.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws LocationUnavailableException if the given path cannot be found.
   * @see #separator(String)
   */
  @RunsInEDT
  public void drag(JTree tree, String path) {
    Point p = selectMatchingPath(tree, path);
    drag(tree, p);
  }

  @RunsInEDT
  private Point selectMatchingPath(JTree tree, String path) {
    Triple<TreePath, Boolean, Point> info = scrollToMatchingPath(tree, path);
    robot.waitForIdle();
    Point where = info.iii;
    if (!info.ii) robot.click(tree, where); // path not selected, click to select
    return where;
  }

  /**
   * Ends a drag operation at the location of the given <code>{@link TreePath}</code>.
   * @param tree the target <code>JTree</code>.
   * @param path the given path.
   * @throws IllegalStateException if the <code>JTree</code> is disabled.
   * @throws IllegalStateException if the <code>JTree</code> is not showing on the screen.
   * @throws LocationUnavailableException if the given path cannot be found.
   * @throws ActionFailedException if there is no drag action in effect.
   * @see #separator(String)
   */
  @RunsInEDT
  public void drop(JTree tree, String path) {
    Point p = scrollToMatchingPath(tree, path).iii;
    drop(tree, p);
  }

  /*
   * returns:
   * 1. the found matching path
   * 2. whether the path is already selected
   * 3. the location where the path is in the JTree
   */
  @RunsInEDT
  private Triple<TreePath, Boolean, Point> scrollToMatchingPath(JTree tree, String path) {
    TreePath matchingPath = verifyJTreeIsReadyAndFindMatchingPath(tree, path, pathFinder);
    makeVisible(tree, matchingPath, false);
    Pair<Boolean, Point> info = scrollToPathToSelect(tree, matchingPath, location);
    return new Triple<TreePath, Boolean, Point>(matchingPath, info.i, info.ii);
  }

  /*
   * returns:
   * 1. whether the path is already selected
   * 2. the location where the path is in the JTree
   */
  @RunsInEDT
  private static Pair<Boolean, Point> scrollToPathToSelect(final JTree tree, final TreePath path, final JTreeLocation location) {
    return execute(new GuiQuery<Pair<Boolean, Point>>() {
      protected Pair<Boolean, Point> executeInEDT() {
        boolean isSelected = tree.getSelectionCount() == 1 && tree.isPathSelected(path);
        return new Pair<Boolean, Point>(isSelected, scrollToTreePath(tree, path, location));
      }
    });
  }

  @RunsInCurrentThread
  private static Point scrollToTreePath(JTree tree, TreePath path, JTreeLocation location) {
    Pair<Rectangle, Point> boundsAndCoordinates = location.pathBoundsAndCoordinates(tree, path);
    tree.scrollRectToVisible(boundsAndCoordinates.i);
    return boundsAndCoordinates.ii;
  }

  @RunsInEDT
  private boolean makeParentVisible(JTree tree, TreePath path) {
    boolean changed = makeVisible(tree, path.getParentPath(), true);
    if (changed) robot.waitForIdle();
    return changed;
  }

  /**
   * Matches, makes visible, and expands the path one component at a time, from uppermost ancestor on down, since
   * children may be lazily loaded/created.
   * @param tree the target <code>JTree</code>.
   * @param path the tree path to make visible.
   * @param expandWhenFound indicates if nodes should be expanded or not when found.
   * @return if it was necessary to make visible and/or expand a node in the path.
   */
  @RunsInEDT
  private boolean makeVisible(JTree tree, TreePath path, boolean expandWhenFound) {
    boolean changed = false;
    if (path.getPathCount() > 1) changed = makeParentVisible(tree, path);
    if (!expandWhenFound) return changed;
    expandTreePath(tree, path);
    waitForChildrenToShowUp(tree, path);
    return true;
  }

  @RunsInEDT
  private void waitForChildrenToShowUp(JTree tree, TreePath path) {
    int timeout = robot.settings().timeoutToBeVisible();
    try {
      pause(untilChildrenShowUp(tree, path), timeout);
    } catch (WaitTimedOutError e) {
      throw new LocationUnavailableException(e.getMessage());
    }
  }

  /**
   * Asserts that the given <code>{@link JTree}</code>'s selected rows are equal to the given one.
   * @param tree the target <code>JTree</code>.
   * @param rows the indices of the rows, expected to be selected.
   * @throws NullPointerException if the array of row indices is <code>null</code>.
   * @throws AssertionError if the given <code>JTree</code> selection is not equal to the given rows.
   */
  @RunsInEDT
  public void requireSelection(JTree tree, int[] rows) {
    if (rows == null) throw new NullPointerException("The array of row indices should not be null");
    verifySelection(tree, rows, selectionProperty(tree));
  }

  /**
   * Asserts that the given <code>{@link JTree}</code>'s selected paths are equal to the given one.
   * @param tree the target <code>JTree</code>.
   * @param paths the given paths, expected to be selected.
   * @throws NullPointerException if the array of paths is <code>null</code>.
   * @throws LocationUnavailableException if any of the given paths cannot be found.
   * @throws AssertionError if the given <code>JTree</code> selection is not equal to the given paths.
   * @see #separator(String)
   */
  @RunsInEDT
  public void requireSelection(JTree tree, String[] paths) {
    if (paths == null) throw new NullPointerException("The array of paths should not be null");
    verifySelection(tree, paths, pathFinder, selectionProperty(tree));
  }

  /**
   * Asserts that the given <code>{@link JTree}</code> does not have any selection.
   * @param tree the given <code>JTree</code>.
   * @throws AssertionError if the <code>JTree</code> has a selection.
   */
  @RunsInEDT
  public void requireNoSelection(JTree tree) {
    verifyNoSelection(tree, selectionProperty(tree));
  }

  @RunsInEDT
  private Description selectionProperty(JTree tree) {
    return propertyName(tree, SELECTION_PROPERTY);
  }

  /**
   * Asserts that the given <code>{@link JTree}</code> is editable.
   * @param tree the given <code>JTree</code>.
   * @throws AssertionError if the <code>JTree</code> is not editable.
   */
  @RunsInEDT
  public void requireEditable(JTree tree) {
    assertEditable(tree, true);
  }

  /**
   * Asserts that the given <code>{@link JTree}</code> is not editable.
   * @param tree the given <code>JTree</code>.
   * @throws AssertionError if the <code>JTree</code> is editable.
   */
  @RunsInEDT
  public void requireNotEditable(JTree tree) {
    assertEditable(tree, false);
  }

  @RunsInEDT
  private void assertEditable(JTree tree, boolean editable) {
    assertThat(isEditable(tree)).as(editableProperty(tree)).isEqualTo(editable);
  }

  @RunsInEDT
  private static Description editableProperty(JTree tree) {
    return propertyName(tree, EDITABLE_PROPERTY);
  }

  /**
   * Returns the separator to use when converting <code>{@link TreePath}</code>s to <code>String</code>s.
   * @return the separator to use when converting <code>{@link TreePath}</code>s to <code>String</code>s.
   */
  public String separator() {
    return pathFinder.separator();
  }

  /**
   * Updates the separator to use when converting <code>{@link TreePath}</code>s to <code>String</code>s.
   * @param newSeparator the new separator.
   * @throws NullPointerException if the given separator is <code>null</code>.
   */
  public void separator(String newSeparator) {
    if (newSeparator == null) throw new NullPointerException("The path separator should not be null");
    pathFinder.separator(newSeparator);
  }

  /**
   * Updates the implementation of <code>{@link JTreeCellReader}</code> to use when comparing internal values of a
   * <code>{@link JTree}</code> and the values expected in a test.
   * @param newCellReader the new <code>JTreeCellValueReader</code> to use.
   * @throws NullPointerException if <code>newCellReader</code> is <code>null</code>.
   */
  public void cellReader(JTreeCellReader newCellReader) {
    validateCellReader(newCellReader);
    pathFinder.cellReader(newCellReader);
  }

  /**
   * Verifies that the given row index is valid.
   * @param tree the given <code>JTree</code>.
   * @param row the given index.
   * @throws IndexOutOfBoundsException if the given index is less than zero or equal than or greater than the number of
   * visible rows in the <code>JTree</code>.
   * @since 1.2
   */
  @RunsInEDT
  public void validateRow(JTree tree, int row) {
    location.validIndex(tree, row);
  }

  /**
   * Verifies that the given node path exists.
   * @param tree the given <code>JTree</code>.
   * @param path the given path.
   * @throws LocationUnavailableException if the given path cannot be found.
   * @since 1.2
   */
  @RunsInEDT
  public void validatePath(JTree tree, String path) {
    matchingPathFor(tree, path, pathFinder);
  }

  /**
   * Returns the <code>String</code> representation of the node at the given path.
   * @param tree the given <code>JTree</code>.
   * @param path the given path.
   * @return the <code>String</code> representation of the node at the given path.
   * @throws LocationUnavailableException if the given path cannot be found.
   * @since 1.2
   */
  @RunsInEDT
  public String nodeValue(JTree tree, String path) {
    return nodeText(tree, path, pathFinder);
  }

  /**
   * Returns the <code>String</code> representation of the node at the given row index.
   * @param tree the given <code>JTree</code>.
   * @param row the given row.
   * @return the <code>String</code> representation of the node at the given row index.
   * @throws IndexOutOfBoundsException if the given row is less than zero or equal than or greater than the number of
   * visible rows in the <code>JTree</code>.
   * @throws LocationUnavailableException if a tree path for the given row cannot be found.
   * @since 1.2
   */
  public String nodeValue(JTree tree, int row) {
    return nodeText(tree, row, location, pathFinder);
  }

  @VisibleForTesting
  JTreeCellReader cellReader() { return pathFinder.cellReader(); }
}
TOP

Related Classes of org.fest.swing.driver.JTreeDriver

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.