All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.assertj.swing.driver.JTreeDriver Maven / Gradle / Ivy

There is a newer version: 3.17.1
Show newest version
/**
 * 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 2012-2015 the original author or authors.
 */
package org.assertj.swing.driver;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.util.Preconditions.checkNotNull;
import static org.assertj.core.util.Preconditions.checkNotNullOrEmpty;
import static org.assertj.core.util.Strings.concat;
import static org.assertj.swing.core.MouseButton.LEFT_BUTTON;
import static org.assertj.swing.core.MouseButton.RIGHT_BUTTON;
import static org.assertj.swing.driver.ComponentPreconditions.checkEnabledAndShowing;
import static org.assertj.swing.driver.JTreeChildrenShowUpCondition.untilChildrenShowUp;
import static org.assertj.swing.driver.JTreeClearSelectionTask.clearSelectionOf;
import static org.assertj.swing.driver.JTreeEditableQuery.isEditable;
import static org.assertj.swing.driver.JTreeExpandPathTask.expandTreePath;
import static org.assertj.swing.driver.JTreeMatchingPathQuery.matchingPathFor;
import static org.assertj.swing.driver.JTreeMatchingPathQuery.verifyJTreeIsReadyAndFindMatchingPath;
import static org.assertj.swing.driver.JTreeNodeTextQuery.nodeText;
import static org.assertj.swing.driver.JTreeToggleExpandStateTask.toggleExpandState;
import static org.assertj.swing.driver.JTreeVerifySelectionTask.checkHasSelection;
import static org.assertj.swing.driver.JTreeVerifySelectionTask.checkNoSelection;
import static org.assertj.swing.edt.GuiActionRunner.execute;
import static org.assertj.swing.exception.ActionFailedException.actionFailure;
import static org.assertj.swing.timing.Pause.pause;
import static org.assertj.swing.util.Platform.controlOrCommandKey;

import java.awt.Point;
import java.awt.Rectangle;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
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.assertj.core.description.Description;
import org.assertj.core.util.VisibleForTesting;
import org.assertj.swing.annotation.RunsInCurrentThread;
import org.assertj.swing.annotation.RunsInEDT;
import org.assertj.swing.cell.JTreeCellReader;
import org.assertj.swing.core.MouseButton;
import org.assertj.swing.core.MouseClickInfo;
import org.assertj.swing.core.Robot;
import org.assertj.swing.edt.GuiQuery;
import org.assertj.swing.exception.LocationUnavailableException;
import org.assertj.swing.exception.WaitTimedOutError;
import org.assertj.swing.internal.annotation.InternalApi;
import org.assertj.swing.util.ArrayPreconditions;
import org.assertj.swing.util.Pair;
import org.assertj.swing.util.Triple;

/**
 * 

* Supports functional testing of {@code JTree}s. *

* *

* Note: This class is intended for internal use only. Please use the classes in the package * {@link org.assertj.swing.fixture} in your tests. *

* * @author Alex Ruiz */ @InternalApi 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 {@link JTreeDriver}. * * @param robot the robot to use to simulate user input. */ public JTreeDriver(@Nonnull Robot robot) { super(robot); location = new JTreeLocation(); pathFinder = new JTreePathFinder(); } /** * Clicks the given row. * * @param tree the target {@code JTree}. * @param row the given row. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} 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}. * @throws LocationUnavailableException if a tree path for the given row cannot be found. */ @RunsInEDT public void clickRow(@Nonnull JTree tree, int row) { Point p = scrollToRow(tree, row); robot.click(tree, p); } /** * Clicks the given row. * * @param tree the target {@code JTree}. * @param row the given row. * @param button the mouse button to use. * @throws NullPointerException if the given button is {@code null}. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} 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}. * @throws LocationUnavailableException if a tree path for the given row cannot be found. */ @RunsInEDT public void clickRow(@Nonnull JTree tree, int row, @Nonnull MouseButton button) { checkNotNull(button); clickRow(tree, row, button, 1); } /** * Clicks the given row. * * @param tree the target {@code JTree}. * @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} is {@code null}. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} 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}. * @throws LocationUnavailableException if a tree path for the given row cannot be found. */ @RunsInEDT public void clickRow(@Nonnull JTree tree, int row, @Nonnull MouseClickInfo mouseClickInfo) { checkNotNull(mouseClickInfo); clickRow(tree, row, mouseClickInfo.button(), mouseClickInfo.times()); } @RunsInEDT private void clickRow(@Nonnull JTree tree, int row, @Nonnull 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}. * @param row the given row. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} 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}. * @throws LocationUnavailableException if a tree path for the given row cannot be found. */ @RunsInEDT public void doubleClickRow(@Nonnull JTree tree, int row) { Point p = scrollToRow(tree, row); doubleClick(tree, p); } /** * Right-clicks the given row. * * @param tree the target {@code JTree}. * @param row the given row. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} 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}. * @throws LocationUnavailableException if a tree path for the given row cannot be found. */ @RunsInEDT public void rightClickRow(@Nonnull JTree tree, int row) { Point p = scrollToRow(tree, row); rightClick(tree, p); } @RunsInEDT private @Nonnull Point scrollToRow(@Nonnull JTree tree, int row) { Point p = scrollToRow(tree, row, location(), false).second; robot.waitForIdle(); return checkNotNull(p); } /** * Clicks the given path, expanding parent nodes if necessary. * * @param tree the target {@code JTree}. * @param path the path to path. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} is not showing on the screen. * @throws LocationUnavailableException if the given path cannot be found. */ @RunsInEDT public void clickPath(@Nonnull JTree tree, @Nonnull 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}. * @param path the path to path. * @param button the mouse button to use. * @throws NullPointerException if the given button is {@code null}. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} is not showing on the screen. * @throws LocationUnavailableException if the given path cannot be found. */ @RunsInEDT public void clickPath(@Nonnull JTree tree, @Nonnull String path, @Nonnull MouseButton button) { checkNotNull(button); clickPath(tree, path, button, 1); } /** * Clicks the given path, expanding parent nodes if necessary. * * @param tree the target {@code JTree}. * @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} is {@code null}. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} is not showing on the screen. * @throws LocationUnavailableException if the given path cannot be found. */ @RunsInEDT public void clickPath(@Nonnull JTree tree, @Nonnull String path, @Nonnull MouseClickInfo mouseClickInfo) { checkNotNull(mouseClickInfo); clickPath(tree, path, mouseClickInfo.button(), mouseClickInfo.times()); } private void clickPath(@Nonnull JTree tree, @Nonnull String path, @Nonnull 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}. * @param path the path to double-click. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} is not showing on the screen. * @throws LocationUnavailableException if the given path cannot be found. */ @RunsInEDT public void doubleClickPath(@Nonnull JTree tree, @Nonnull String path) { Point p = scrollToPath(tree, path); doubleClick(tree, p); } private @Nonnull Point scrollToPath(@Nonnull JTree tree, @Nonnull String path) { Point p = scrollToMatchingPath(tree, path, false).third; robot.waitForIdle(); return checkNotNull(p); } private void doubleClick(@Nonnull JTree tree, @Nonnull 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}. * @param path the path to path. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} is not showing on the screen. * @throws LocationUnavailableException if the given path cannot be found. */ @RunsInEDT public void rightClickPath(@Nonnull JTree tree, @Nonnull String path) { Point p = scrollToPath(tree, path); rightClick(tree, p); } private void rightClick(@Nonnull JTree tree, @Nonnull 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. *

* 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 centre of that square should work. *

* * @param tree the target {@code JTree}. * @param row the given row. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} 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}. * @throws LocationUnavailableException if a tree path for the given row cannot be found. * @throws org.assertj.swing.exception.ActionFailedException if this method fails to expand the row. */ @RunsInEDT public void expandRow(@Nonnull JTree tree, int row) { Triple info = scrollToRowAndGetToggleInfo(tree, row, location()); robot.waitForIdle(); if (info.first) { return; // already expanded } toggleCell(tree, checkNotNull(info.second), info.third); } /** * Collapses the given row, is possible. If the row is already collapsed, this method will not do anything. *

* 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 centre of that square should work. *

* * @param tree the target {@code JTree}. * @param row the given row. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} 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}. * @throws LocationUnavailableException if a tree path for the given row cannot be found. * @throws org.assertj.swing.exception.ActionFailedException if this method fails to collapse the row. */ @RunsInEDT public void collapseRow(@Nonnull JTree tree, int row) { Triple info = scrollToRowAndGetToggleInfo(tree, row, location()); robot.waitForIdle(); if (!info.first) { return; // already collapsed } toggleCell(tree, checkNotNull(info.second), info.third); } /** * Change the open/closed state of the given row, if possible. *

* 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 centre of that square should work. *

* * @param tree the target {@code JTree}. * @param row the given row. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} 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}. * @throws LocationUnavailableException if a tree path for the given row cannot be found. * @throws org.assertj.swing.exception.ActionFailedException if this method fails to toggle the row. */ @RunsInEDT public void toggleRow(@Nonnull JTree tree, int row) { Triple info = scrollToRowAndGetToggleInfo(tree, row, location()); robot.waitForIdle(); toggleCell(tree, checkNotNull(info.second), info.third); } /* * 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 @Nonnull Triple scrollToRowAndGetToggleInfo(final @Nonnull JTree tree, final int row, final @Nonnull JTreeLocation location) { Triple result = execute(new GuiQuery>() { @Override protected Triple executeInEDT() { checkEnabledAndShowing(tree); Point p = scrollToVisible(tree, row, location); return Triple.of(tree.isExpanded(row), p, tree.getToggleClickCount()); } }); return checkNotNull(result); } /** * Expands the given path, is possible. If the path is already expanded, this method will not do anything. *

* 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 centre of that square should work. *

* * @param tree the target {@code JTree}. * @param path the path to expand. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} is not showing on the screen. * @throws LocationUnavailableException if the given path cannot be found. * @throws org.assertj.swing.exception.ActionFailedException if this method fails to expand the path. */ @RunsInEDT public void expandPath(@Nonnull JTree tree, @Nonnull String path) { Triple info = scrollToMatchingPathAndGetToggleInfo(tree, path, pathFinder(), location()); if (info.first) { return; // already expanded } toggleCell(tree, checkNotNull(info.second), info.third); } /** * Collapses the given path, is possible. If the path is already expanded, this method will not do anything. *

* 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 centre of that square should work. *

* * @param tree the target {@code JTree}. * @param path the path to collapse. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} is not showing on the screen. * @throws LocationUnavailableException if the given path cannot be found. * @throws org.assertj.swing.exception.ActionFailedException if this method fails to collapse the path. */ @RunsInEDT public void collapsePath(@Nonnull JTree tree, @Nonnull String path) { Triple info = scrollToMatchingPathAndGetToggleInfo(tree, path, pathFinder(), location()); if (!info.first) { return; // already collapsed } toggleCell(tree, checkNotNull(info.second), info.third); } /* * 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 @Nonnull Triple scrollToMatchingPathAndGetToggleInfo( final @Nonnull JTree tree, final @Nonnull String path, final @Nonnull JTreePathFinder pathFinder, final @Nonnull JTreeLocation location) { Triple result = execute(new GuiQuery>() { @Override protected Triple executeInEDT() { checkEnabledAndShowing(tree); TreePath matchingPath = matchingPathFor(tree, path, pathFinder); Point p = scrollToTreePath(tree, matchingPath, location); return Triple.of(tree.isExpanded(matchingPath), p, tree.getToggleClickCount()); } }); return checkNotNull(result); } @RunsInEDT private void toggleCell(@Nonnull JTree tree, @Nonnull 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 @Nonnull JTree tree, final @Nonnull Point p) { execute(() -> { 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}. * @param rows the rows to select. * @throws NullPointerException if the array of rows is {@code null}. * @throws IllegalArgumentException if the array of rows is empty. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} 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}. * @throws LocationUnavailableException if a tree path for any of the given rows cannot be found. */ @RunsInEDT public void selectRows(final @Nonnull JTree tree, final @Nonnull int[] rows) { ArrayPreconditions.checkNotNullOrEmpty(rows); clearSelection(tree); new MultipleSelectionTemplate(robot) { @Override int elementCount() { return rows.length; } @Override void selectElement(int index) { selectRow(tree, rows[index]); } }.multiSelect(); } /** * Unselects the given rows. * * @param tree the target {@code JTree}. * @param rows the rows to unselect. * @throws NullPointerException if the array of rows is {@code null}. * @throws IllegalArgumentException if the array of rows is empty. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} 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}. * @throws LocationUnavailableException if a tree path for any of the given rows cannot be found. */ @RunsInEDT public void unselectRows(final @Nonnull JTree tree, final @Nonnull int[] rows) { ArrayPreconditions.checkNotNullOrEmpty(rows); new MultipleSelectionTemplate(robot) { @Override int elementCount() { return rows.length; } @Override void unselectElement(int index) { unselectRow(tree, rows[index]); } }.multiUnselect(); } @RunsInEDT private void clearSelection(final @Nonnull JTree tree) { clearSelectionOf(tree); robot.waitForIdle(); } /** * Selects the given row. * * @param tree the target {@code JTree}. * @param row the row to select. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} 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}. */ @RunsInEDT public void selectRow(@Nonnull JTree tree, int row) { scrollAndSelectRow(tree, row, true, false); } /** * Unselects the given row. * * @param tree the target {@code JTree}. * @param row the row to unselect. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} 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}. */ @RunsInEDT public void unselectRow(@Nonnull JTree tree, int row) { int key = controlOrCommandKey(); robot.pressKeyWhileRunning(key, () -> scrollAndSelectRow(tree, row, false, false)); } /** * Selects the given paths, expanding parent nodes if necessary. * * @param tree the target {@code JTree}. * @param paths the paths to select. * @throws NullPointerException if the array of rows is {@code null}. * @throws IllegalArgumentException if the array of rows is empty. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} is not showing on the screen. * @throws LocationUnavailableException if any the given path cannot be found. */ @RunsInEDT public void selectPaths(final @Nonnull JTree tree, final @Nonnull String[] paths) { checkNotNullOrEmpty(paths); clearSelection(tree); new MultipleSelectionTemplate(robot) { @Override int elementCount() { return paths.length; } @Override void selectElement(int index) { selectPath(tree, checkNotNull(paths[index])); } }.multiSelect(); } /** * Unselects the given paths, expanding parent nodes if necessary. * * @param tree the target {@code JTree}. * @param paths the paths to unselect. * @throws NullPointerException if the array of rows is {@code null}. * @throws IllegalArgumentException if the array of rows is empty. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} is not showing on the screen. * @throws LocationUnavailableException if any the given path cannot be found. */ @RunsInEDT public void unselectPaths(final @Nonnull JTree tree, final @Nonnull String[] paths) { checkNotNullOrEmpty(paths); new MultipleSelectionTemplate(robot) { @Override int elementCount() { return paths.length; } @Override void unselectElement(int index) { unselectPath(tree, checkNotNull(paths[index])); } }.multiUnselect(); } /** * Selects the given path, expanding parent nodes if necessary. Unlike {@link #clickPath(JTree, String)}, this method * will not click the path if it is already selected * * @param tree the target {@code JTree}. * @param path the path to select. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} is not showing on the screen. * @throws LocationUnavailableException if the given path cannot be found. */ @RunsInEDT public void selectPath(@Nonnull JTree tree, @Nonnull String path) { selectMatchingPath(tree, path, true, false); } /** * Unselects the given path, expanding parent nodes if necessary. Unlike {@link #clickPath(JTree, String)}, this * method * will not click the path if it is not selected * * @param tree the target {@code JTree}. * @param path the path to unselect. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} is not showing on the screen. * @throws LocationUnavailableException if the given path cannot be found. */ @RunsInEDT public void unselectPath(@Nonnull JTree tree, @Nonnull String path) { int key = controlOrCommandKey(); robot.pressKeyWhileRunning(key, () -> selectMatchingPath(tree, path, false, false)); } /** * Shows a pop-up menu at the position of the node in the given row. * * @param tree the target {@code JTree}. * @param row the given row. * @return a driver that manages the displayed pop-up menu. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} is not showing on the screen. * @throws org.assertj.swing.exception.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}. * @throws LocationUnavailableException if a tree path for the given row cannot be found. */ @RunsInEDT public @Nonnull JPopupMenu showPopupMenu(@Nonnull JTree tree, int row) { Pair info = scrollToRow(tree, row, location(), true); Point p = checkNotNull(info.second); 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}. * @param path the given path. * @return a driver that manages the displayed pop-up menu. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} is not showing on the screen. * @throws org.assertj.swing.exception.ComponentLookupException if a pop-up menu cannot be found. * @throws LocationUnavailableException if the given path cannot be found. * @see #replaceSeparator(String) */ @RunsInEDT public @Nonnull JPopupMenu showPopupMenu(@Nonnull JTree tree, @Nonnull String path) { Triple info = scrollToMatchingPath(tree, path, true); robot.waitForIdle(); Point where = checkNotNull(info.third); return robot.showPopupMenu(tree, where); } /** * Starts a drag operation at the location of the given row. * * @param tree the target {@code JTree}. * @param row the given row. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} 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}. * @throws LocationUnavailableException if a tree path for the given row cannot be found. */ @RunsInEDT public void drag(@Nonnull JTree tree, int row) { Point p = scrollAndSelectRow(tree, row, true, true); drag(tree, p); } @RunsInEDT private @Nonnull Point scrollAndSelectRow(@Nonnull JTree tree, int row, boolean select, boolean singleSelectRequired) { Pair info = scrollToRow(tree, row, location(), singleSelectRequired); Point p = checkNotNull(info.second); if (info.first != select) { robot.click(tree, p); } return p; } /** * Ends a drag operation at the location of the given row. * * @param tree the target {@code JTree}. * @param row the given row. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} 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}. * @throws LocationUnavailableException if a tree path for the given row cannot be found. * @throws org.assertj.swing.exception.ActionFailedException if there is no drag action in effect. */ @RunsInEDT public void drop(@Nonnull JTree tree, int row) { Pair info = scrollToRow(tree, row, location(), true); drop(tree, checkNotNull(info.second)); } /* * Returns: 1. if the node is expanded 2. the location of the node */ @RunsInEDT private static @Nonnull Pair scrollToRow(final @Nonnull JTree tree, final int row, final @Nonnull JTreeLocation location, boolean singleSelectRequired) { Pair result = execute(new GuiQuery>() { @Override protected Pair executeInEDT() { checkEnabledAndShowing(tree); Point p = scrollToVisible(tree, row, location); boolean selected = (!singleSelectRequired || tree.getSelectionCount() == 1) && tree.isRowSelected(row); return Pair.of(selected, p); } }); return checkNotNull(result); } @RunsInCurrentThread private static @Nonnull Point scrollToVisible(@Nonnull JTree tree, int row, @Nonnull JTreeLocation location) { Pair boundsAndCoordinates = location.rowBoundsAndCoordinates(tree, row); tree.scrollRectToVisible(boundsAndCoordinates.first); return checkNotNull(boundsAndCoordinates.second); } /** * Starts a drag operation at the location of the given {@code TreePath}. * * @param tree the target {@code JTree}. * @param path the given path. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} is not showing on the screen. * @throws LocationUnavailableException if the given path cannot be found. * @see #replaceSeparator(String) */ @RunsInEDT public void drag(@Nonnull JTree tree, @Nonnull String path) { Point p = selectMatchingPath(tree, path, true, true); drag(tree, p); } @RunsInEDT private @Nonnull Point selectMatchingPath(@Nonnull JTree tree, @Nonnull String path, boolean select, boolean singleSelectionRequired) { Triple info = scrollToMatchingPath(tree, path, singleSelectionRequired); robot.waitForIdle(); Point where = checkNotNull(info.third); if (info.second != select) { robot.click(tree, where); } return where; } /** * Ends a drag operation at the location of the given {@code TreePath}. * * @param tree the target {@code JTree}. * @param path the given path. * @throws IllegalStateException if the {@code JTree} is disabled. * @throws IllegalStateException if the {@code JTree} is not showing on the screen. * @throws LocationUnavailableException if the given path cannot be found. * @throws org.assertj.swing.exception.ActionFailedException if there is no drag action in effect. * @see #replaceSeparator(String) */ @RunsInEDT public void drop(@Nonnull JTree tree, @Nonnull String path) { Point p = scrollToMatchingPath(tree, path, true).third; drop(tree, checkNotNull(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 @Nonnull Triple scrollToMatchingPath(@Nonnull JTree tree, @Nonnull String path, boolean singleSelectionRequired) { TreePath matchingPath = verifyJTreeIsReadyAndFindMatchingPath(tree, path, pathFinder()); makeVisible(tree, matchingPath, false); Pair info = scrollToPathToSelect(tree, matchingPath, location(), singleSelectionRequired); return Triple.of(matchingPath, info.first, info.second); } /* * returns: 1. whether the path is already selected 2. the location where the path is in the JTree */ @RunsInEDT private static @Nonnull Pair scrollToPathToSelect(final @Nonnull JTree tree, final @Nonnull TreePath path, final @Nonnull JTreeLocation location, boolean singleSelectionRequired) { Pair result = execute(new GuiQuery>() { @Override protected Pair executeInEDT() { boolean isSelected = (!singleSelectionRequired || tree.getSelectionCount() == 1) && tree.isPathSelected(path); return Pair.of(isSelected, scrollToTreePath(tree, path, location)); } }); return checkNotNull(result); } @RunsInCurrentThread private static @Nonnull Point scrollToTreePath(@Nonnull JTree tree, @Nonnull TreePath path, @Nonnull JTreeLocation location) { Pair boundsAndCoordinates = location.pathBoundsAndCoordinates(tree, path); tree.scrollRectToVisible(boundsAndCoordinates.first); return checkNotNull(boundsAndCoordinates.second); } @RunsInEDT private boolean makeParentVisible(@Nonnull JTree tree, @Nonnull TreePath path) { boolean changed = makeVisible(tree, checkNotNull(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}. * @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(@Nonnull JTree tree, @Nonnull 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(@Nonnull JTree tree, @Nonnull 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 JTree}'s selected rows are equal to the given one. * * @param tree the target {@code JTree}. * @param rows the indices of the rows, expected to be selected. * @throws NullPointerException if the array of row indices is {@code null}. * @throws AssertionError if the given {@code JTree} selection is not equal to the given rows. */ @RunsInEDT public void requireSelection(@Nonnull JTree tree, @Nonnull int[] rows) { checkNotNull(rows); checkHasSelection(tree, rows, selectionProperty(tree)); } /** * Asserts that the given {@code JTree}'s selected paths are equal to the given one. * * @param tree the target {@code JTree}. * @param paths the given paths, expected to be selected. * @throws NullPointerException if the array of paths is {@code null}. * @throws LocationUnavailableException if any of the given paths cannot be found. * @throws AssertionError if the given {@code JTree} selection is not equal to the given paths. * @see #replaceSeparator(String) */ @RunsInEDT public void requireSelection(@Nonnull JTree tree, @Nonnull String[] paths) { checkNotNull(paths); checkHasSelection(tree, paths, pathFinder(), selectionProperty(tree)); } /** * Asserts that the given {@code JTree} does not have any selection. * * @param tree the given {@code JTree}. * @throws AssertionError if the {@code JTree} has a selection. */ @RunsInEDT public void requireNoSelection(@Nonnull JTree tree) { checkNoSelection(tree, selectionProperty(tree)); } @RunsInEDT private @Nonnull Description selectionProperty(@Nonnull JTree tree) { return propertyName(tree, SELECTION_PROPERTY); } /** * Asserts that the given {@code JTree} is editable. * * @param tree the given {@code JTree}. * @throws AssertionError if the {@code JTree} is not editable. */ @RunsInEDT public void requireEditable(@Nonnull JTree tree) { assertEditable(tree, true); } /** * Asserts that the given {@code JTree} is not editable. * * @param tree the given {@code JTree}. * @throws AssertionError if the {@code JTree} is editable. */ @RunsInEDT public void requireNotEditable(@Nonnull JTree tree) { assertEditable(tree, false); } @RunsInEDT private void assertEditable(@Nonnull JTree tree, boolean editable) { assertThat(isEditable(tree)).as(editableProperty(tree)).isEqualTo(editable); } @RunsInEDT private static @Nonnull Description editableProperty(@Nonnull JTree tree) { return propertyName(tree, EDITABLE_PROPERTY); } /** * @return the separator to use when converting {@code TreePath}s to {@code String}s. */ public @Nonnull String separator() { return pathFinder.separator(); } /** * Updates the separator to use when converting {@code TreePath}s to {@code String}s. * * @param newSeparator the new separator. * @throws NullPointerException if the given separator is {@code null}. */ public void replaceSeparator(@Nonnull String newSeparator) { pathFinder.replaceSeparator(checkNotNull(newSeparator)); } /** * Updates the implementation of {@link JTreeCellReader} to use when comparing internal values of a {@code JTree} and * the values expected in a test. * * @param newCellReader the new {@code JTreeCellValueReader} to use. * @throws NullPointerException if {@code newCellReader} is {@code null}. */ public void replaceCellReader(@Nonnull JTreeCellReader newCellReader) { pathFinder.replaceCellReader(checkNotNull(newCellReader)); } /** * Verifies that the given row index is valid. * * @param tree the given {@code JTree}. * @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}. */ @RunsInEDT public void checkRowInBounds(@Nonnull JTree tree, int row) { location.checkRowInBounds(tree, row); } /** * Verifies that the given node path exists. * * @param tree the given {@code JTree}. * @param path the given path. * @throws LocationUnavailableException if the given path cannot be found. */ @RunsInEDT public void checkPathExists(@Nonnull JTree tree, @Nonnull String path) { matchingPathFor(tree, path, pathFinder()); } /** * Returns the {@code String} representation of the node at the given path. * * @param tree the given {@code JTree}. * @param path the given path. * @return the {@code String} representation of the node at the given path. * @throws LocationUnavailableException if the given path cannot be found. */ @RunsInEDT public @Nullable String nodeValue(@Nonnull JTree tree, @Nonnull String path) { return nodeText(tree, path, pathFinder()); } /** * Returns the {@code String} representation of the node at the given row index. * * @param tree the given {@code JTree}. * @param row the given row. * @return the {@code String} 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}. * @throws LocationUnavailableException if a tree path for the given row cannot be found. */ public @Nullable String nodeValue(@Nonnull JTree tree, int row) { return nodeText(tree, row, location(), pathFinder()); } @VisibleForTesting JTreeCellReader cellReader() { return pathFinder.cellReader(); } private @Nonnull JTreeLocation location() { return location; } private @Nonnull JTreePathFinder pathFinder() { return pathFinder; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy