001    /*
002     * Created on Jan 27, 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 org.fest.swing.driver.ComponentMovableQuery.isUserMovable;
018    import static org.fest.swing.driver.ComponentMoveTask.moveComponent;
019    import static org.fest.swing.driver.ComponentSetSizeTask.setComponentSize;
020    import static org.fest.swing.driver.ComponentStateValidator.componentNotShowingOnScreenFailure;
021    import static org.fest.swing.driver.ComponentStateValidator.validateIsEnabledAndShowing;
022    import static org.fest.swing.driver.ContainerStateValidator.validateCanResize;
023    import static org.fest.swing.edt.GuiActionRunner.execute;
024    import static org.fest.swing.format.Formatting.format;
025    import static org.fest.util.Strings.concat;
026    
027    import java.awt.*;
028    
029    import org.fest.swing.annotation.RunsInCurrentThread;
030    import org.fest.swing.annotation.RunsInEDT;
031    import org.fest.swing.core.Robot;
032    import org.fest.swing.edt.GuiQuery;
033    import org.fest.swing.util.Pair;
034    import org.fest.swing.util.Triple;
035    
036    /**
037     * Understands functional testing of <code>{@link Container}</code>s:
038     * <ul>
039     * <li>user input simulation</li>
040     * <li>state verification</li>
041     * <li>property value query</li>
042     * </ul>
043     * This class is intended for internal use only. Please use the classes in the package
044     * <code>{@link org.fest.swing.fixture}</code> in your tests.
045     *
046     * @author Alex Ruiz
047     * @author Yvonne Wang
048     */
049    public abstract class ContainerDriver extends ComponentDriver {
050    
051      /**
052       * Creates a new </code>{@link ContainerDriver}</code>.
053       * @param robot the robot to use to simulate user input.
054       */
055      public ContainerDriver(Robot robot) {
056        super(robot);
057      }
058    
059      /**
060       * Resizes the <code>{@link Container}</code> horizontally.
061       * @param c the target <code>Container</code>.
062       * @param width the width that the <code>Container</code> should have after being resized.
063       * @throws IllegalStateException if the <code>Container</code> is not enabled.
064       * @throws IllegalStateException if the <code>Container</code> is not resizable by the user.
065       * @throws IllegalStateException if the <code>Container</code> is not showing on the screen.
066       */
067      @RunsInEDT
068      protected final void resizeWidth(Container c, int width) {
069        Pair<Dimension, Insets> resizeInfo = resizeInfo(c);
070        Dimension size = resizeInfo.i;
071        resizeBy(c, resizeInfo, width - size.width, 0);
072      }
073    
074      /**
075       * Resizes the <code>{@link Container}</code> vertically.
076       * @param c the target <code>Container</code>.
077       * @param height the height that the <code>Container</code> should have after being resized.
078       * @throws IllegalStateException if the <code>Container</code> is not enabled.
079       * @throws IllegalStateException if the <code>Container</code> is not resizable by the user.
080       * @throws IllegalStateException if the <code>Container</code> is not showing on the screen.
081       */
082      @RunsInEDT
083      protected final void resizeHeight(Container c, int height) {
084        Pair<Dimension, Insets> resizeInfo = resizeInfo(c);
085        Dimension size = resizeInfo.i;
086        resizeBy(c, resizeInfo, 0, height - size.height);
087      }
088    
089      /**
090       * Resizes the <code>{@link Container}</code> to the given size.
091       * @param c the target <code>Container</code>.
092       * @param width the width to resize the <code>Container</code> to.
093       * @param height the height to resize the <code>Container</code> to.
094       * @throws IllegalStateException if the <code>Container</code> is not enabled.
095       * @throws IllegalStateException if the <code>Container</code> is not resizable by the user.
096       * @throws IllegalStateException if the <code>Container</code> is not showing on the screen.
097       */
098      @RunsInEDT
099      protected final void resize(Container c, int width, int height) {
100        Pair<Dimension, Insets> resizeInfo = resizeInfo(c);
101        Dimension size = resizeInfo.i;
102        resizeBy(c, resizeInfo, width - size.width, height - size.height);
103      }
104    
105      @RunsInEDT
106      private static Pair<Dimension, Insets> resizeInfo(final Container c) {
107        return execute(new GuiQuery<Pair<Dimension, Insets>>() {
108          protected Pair<Dimension, Insets> executeInEDT() {
109            validateCanResize(c);
110            return new Pair<Dimension, Insets>(c.getSize(), c.getInsets());
111          }
112        });
113      }
114    
115      @RunsInEDT
116      private void resizeBy(Container c, Pair<Dimension, Insets> resizeInfo, int x, int y) {
117        simulateResizeStarted(c, resizeInfo, x, y);
118        Dimension size = resizeInfo.i;
119        setComponentSize(c, size.width + x, size.height + y);
120        robot.waitForIdle();
121      }
122    
123      @RunsInEDT
124      private void simulateResizeStarted(Container c, Pair<Dimension, Insets> resizeInfo, int x, int y) {
125        Point p = resizeLocation(resizeInfo);
126        moveMouseIgnoringAnyError(c, p);
127        moveMouseIgnoringAnyError(c, p.x + x, p.y + y);
128      }
129    
130      private static Point resizeLocation(final Pair<Dimension, Insets> resizeInfo) {
131        return resizeLocation(resizeInfo.i, resizeInfo.ii);
132      }
133    
134      private static Point resizeLocation(Dimension size, Insets insets) {
135        return resizeLocation(size.width, size.height, insets.right, insets.bottom);
136      }
137    
138      private static Point resizeLocation(int width, int height, int right, int bottom) {
139        return new Point(width - right / 2, height - bottom / 2);
140      }
141    
142      /**
143       * Move the given <code>{@link Container}</code> to the requested location.
144       * @param c the target <code>Container</code>.
145       * @param x the horizontal coordinate.
146       * @param y the vertical coordinate.
147       * @throws IllegalStateException if the <code>Container</code> is not enabled.
148       * @throws IllegalStateException if the <code>Container</code> is not movable by the user.
149       * @throws IllegalStateException if the <code>Container</code> is not showing on the screen.
150       */
151      @RunsInEDT
152      public void move(Container c, int x, int y) {
153        Triple<Dimension, Insets, Point> moveInfo = moveInfo(c);
154        Point locationOnScreen = moveInfo.iii;
155        moveBy(c, moveInfo, x - locationOnScreen.x, y - locationOnScreen.y);
156      }
157    
158      @RunsInEDT
159      private static Triple<Dimension, Insets, Point> moveInfo(final Container c) {
160        return execute(new GuiQuery<Triple<Dimension, Insets, Point>>() {
161          protected Triple<Dimension, Insets, Point> executeInEDT() {
162            validateCanMove(c);
163            Point locationOnScreen = null;
164            try {
165              locationOnScreen = c.getLocationOnScreen();
166            } catch (IllegalComponentStateException e) {
167              // we should not get to this point, validateIsShowing should have already catched that the container is not
168              // visible.
169            }
170            if (locationOnScreen == null) throw componentNotShowingOnScreenFailure(c);
171            return new Triple<Dimension, Insets, Point>(c.getSize(), c.getInsets(), locationOnScreen) ;
172          }
173        });
174      }
175    
176      @RunsInCurrentThread
177      private static void validateCanMove(Container c) {
178        validateIsEnabledAndShowing(c);
179        if (!isUserMovable(c))
180          throw new IllegalStateException(concat("Expecting component ", format(c), " to be movable by the user"));
181      }
182    
183      @RunsInEDT
184      private void moveBy(Container c, Triple<Dimension, Insets, Point> moveInfo, int x, int y) {
185        simulateMoveStarted(c, moveInfo, x, y);
186        Point locationOnScreen = moveInfo.iii;
187        Point location = new Point(locationOnScreen.x + x, locationOnScreen.y + y);
188        moveComponent(c, location);
189        robot.waitForIdle();
190      }
191    
192      @RunsInEDT
193      private void simulateMoveStarted(Container c, Triple<Dimension, Insets, Point> moveInfo, int x, int y) {
194        Point p = moveLocation(moveInfo.i, moveInfo.ii);
195        moveMouseIgnoringAnyError(c, p);
196        moveMouseIgnoringAnyError(c, p.x + x, p.y + y);
197      }
198    
199      // Returns where the mouse usually grabs to move a container (or window.) Center of the top of the frame is usually a
200      // good choice.
201      private Point moveLocation(Dimension size, Insets insets) {
202        return new Point(size.width / 2, insets.top / 2);
203      }
204    }