001    /*
002     * Created on Feb 1, 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.ComponentStateValidator.validateIsShowing;
018    import static org.fest.swing.driver.JInternalFrameAction.*;
019    import static org.fest.swing.driver.JInternalFrameIconQuery.isIconified;
020    import static org.fest.swing.driver.JInternalFrameSetIconTask.setIcon;
021    import static org.fest.swing.driver.JInternalFrameSetMaximumTask.setMaximum;
022    import static org.fest.swing.driver.WindowLikeContainerLocations.*;
023    import static org.fest.swing.edt.GuiActionRunner.execute;
024    import static org.fest.swing.exception.ActionFailedException.actionFailure;
025    import static org.fest.swing.format.Formatting.format;
026    import static org.fest.util.Strings.concat;
027    
028    import java.awt.*;
029    import java.beans.PropertyVetoException;
030    
031    import javax.swing.JInternalFrame;
032    import javax.swing.JInternalFrame.JDesktopIcon;
033    
034    import org.fest.swing.annotation.RunsInCurrentThread;
035    import org.fest.swing.annotation.RunsInEDT;
036    import org.fest.swing.core.Robot;
037    import org.fest.swing.edt.GuiQuery;
038    import org.fest.swing.edt.GuiTask;
039    import org.fest.swing.exception.ActionFailedException;
040    import org.fest.swing.exception.UnexpectedException;
041    import org.fest.swing.util.Pair;
042    import org.fest.swing.util.Triple;
043    import org.fest.util.VisibleForTesting;
044    
045    /**
046     * Understands functional testing of <code>{@link JInternalFrame}</code>s:
047     * <ul>
048     * <li>user input simulation</li>
049     * <li>state verification</li>
050     * <li>property value query</li>
051     * </ul>
052     * This class is intended for internal use only. Please use the classes in the package
053     * <code>{@link org.fest.swing.fixture}</code> in your tests.
054     *
055     * @author Alex Ruiz
056     * @author Yvonne Wang
057     */
058    public class JInternalFrameDriver extends JComponentDriver {
059    
060      /**
061       * Creates a new </code>{@link JInternalFrameDriver}</code>.
062       * @param robot the robot to use to simulate user input.
063       */
064      public JInternalFrameDriver(Robot robot) {
065        super(robot);
066      }
067    
068      /**
069       * Brings the given <code>{@link JInternalFrame}</code> to the front.
070       * @param internalFrame the target <code>JInternalFrame</code>.
071       */
072      @RunsInEDT
073      public void moveToFront(JInternalFrame internalFrame) {
074        toFront(internalFrame);
075      }
076    
077      @RunsInEDT
078      private static void toFront(final JInternalFrame internalFrame) {
079        execute(new GuiTask() {
080          protected void executeInEDT() {
081            // it seems that moving to front always works, regardless if the internal frame is invisible and/or disabled.
082            internalFrame.toFront();
083          }
084        });
085      }
086    
087      /**
088       * Brings the given <code>{@link JInternalFrame}</code> to the back.
089       * @param internalFrame the target <code>JInternalFrame</code>.
090       */
091      @RunsInEDT
092      public void moveToBack(JInternalFrame internalFrame) {
093        toBack(internalFrame);
094      }
095    
096      @RunsInEDT
097      private static void toBack(final JInternalFrame internalFrame) {
098        execute(new GuiTask() {
099          protected void executeInEDT() {
100            // it seems that moving to back always works, regardless if the internal frame is invisible and/or disabled.
101            internalFrame.moveToBack();
102          }
103        });
104      }
105    
106      /**
107       * Maximizes the given <code>{@link JInternalFrame}</code>, deconifying it first if it is iconified.
108       * @param internalFrame the target <code>JInternalFrame</code>.
109       * @throws IllegalStateException if the <code>JInternalFrame</code> is not maximizable.
110       * @throws IllegalStateException if the <code>JInternalFrame</code> is not showing on the screen.
111       * @throws ActionFailedException if the <code>JInternalFrame</code> vetoes the action.
112       */
113      @RunsInEDT
114      public void maximize(JInternalFrame internalFrame) {
115        Pair<Container, Point> maximizeLocation = validateAndFindMaximizeLocation(internalFrame);
116        maximizeOrNormalize(internalFrame, MAXIMIZE, maximizeLocation);
117      }
118    
119      @RunsInEDT
120      private static Pair<Container, Point> validateAndFindMaximizeLocation(final JInternalFrame internalFrame) {
121        return execute(new GuiQuery<Pair<Container, Point>>() {
122          protected Pair<Container, Point> executeInEDT() {
123            validateCanMaximize(internalFrame);
124            return findMaximizeLocation(internalFrame);
125          }
126        });
127      }
128    
129      @RunsInCurrentThread
130      private static void validateCanMaximize(JInternalFrame internalFrame) {
131        validateIsShowingOrIconified(internalFrame);
132        if (!internalFrame.isMaximizable())
133          throw new IllegalStateException(concat("The JInternalFrame <", format(internalFrame), "> is not maximizable"));
134      }
135    
136      /**
137       * Normalizes the given <code>{@link JInternalFrame}</code>, deconifying it first if it is iconified.
138       * @param internalFrame the target <code>JInternalFrame</code>.
139       * @throws IllegalStateException if the <code>JInternalFrame</code> is not showing on the screen.
140       * @throws ActionFailedException if the <code>JInternalFrame</code> vetoes the action.
141       */
142      @RunsInEDT
143      public void normalize(JInternalFrame internalFrame) {
144        Pair<Container, Point> normalizeLocation = validateAndFindNormalizeLocation(internalFrame);
145        maximizeOrNormalize(internalFrame, NORMALIZE, normalizeLocation);
146      }
147    
148      @RunsInEDT
149      private static Pair<Container, Point> validateAndFindNormalizeLocation(final JInternalFrame internalFrame) {
150        return execute(new GuiQuery<Pair<Container, Point>>() {
151          protected Pair<Container, Point> executeInEDT() {
152            validateIsShowingOrIconified(internalFrame);
153            return findMaximizeLocation(internalFrame);
154          }
155        });
156      }
157    
158      @RunsInCurrentThread
159      private static void validateIsShowingOrIconified(JInternalFrame internalFrame) {
160        if (!internalFrame.isIcon()) validateIsShowing(internalFrame);
161      }
162    
163      @RunsInCurrentThread
164      private static Pair<Container, Point> findMaximizeLocation(JInternalFrame internalFrame) {
165        Container clickTarget = internalFrame.isIcon() ? internalFrame.getDesktopIcon() : internalFrame;
166        Point location = maximizeLocationOf(clickTarget);
167        return new Pair<Container, Point>(clickTarget, location);
168      }
169    
170      @RunsInEDT
171      private void maximizeOrNormalize(JInternalFrame internalFrame, JInternalFrameAction action,
172          Pair<Container, Point> toMoveMouseTo) {
173        moveMouseIgnoringAnyError(toMoveMouseTo.i, toMoveMouseTo.ii);
174        setMaximumProperty(internalFrame, action);
175      }
176    
177      @RunsInEDT
178      private void setMaximumProperty(JInternalFrame internalFrame, JInternalFrameAction action) {
179        try {
180          setMaximum(internalFrame, action);
181          robot.waitForIdle();
182        } catch (UnexpectedException unexpected) {
183          failIfVetoed(internalFrame, action, unexpected);
184        }
185      }
186    
187      /**
188       * Iconifies the given <code>{@link JInternalFrame}</code>.
189       * @param internalFrame the target <code>JInternalFrame</code>.
190       * @throws IllegalStateException if the <code>JInternalFrame</code> is not showing on the screen.
191       * @throws IllegalStateException if the <code>JInternalFrame</code> is not iconifiable.
192       * @throws ActionFailedException if the <code>JInternalFrame</code> vetoes the action.
193       */
194      @RunsInEDT
195      public void iconify(JInternalFrame internalFrame) {
196        Pair<Boolean, Point> iconifyInfo = validateAndfindIconifyInfo(internalFrame);
197        if (iconifyInfo.i) return; // internal frame is already iconified
198        moveMouseIgnoringAnyError(internalFrame, iconifyInfo.ii);
199        setIconProperty(internalFrame, ICONIFY);
200      }
201    
202      @RunsInEDT
203      private static Pair<Boolean, Point> validateAndfindIconifyInfo(final JInternalFrame internalFrame) {
204        return execute(new GuiQuery<Pair<Boolean, Point>>() {
205          protected Pair<Boolean, Point> executeInEDT() throws Throwable {
206            validateIsShowingOrIconified(internalFrame);
207            if (!internalFrame.isIconifiable())
208              throw new IllegalStateException(concat("The JInternalFrame <", format(internalFrame), "> is not iconifiable"));
209            return iconifyInfo(internalFrame);
210          }
211        });
212      }
213    
214      @RunsInCurrentThread
215      private static Pair<Boolean, Point> iconifyInfo(JInternalFrame internalFrame) {
216        boolean iconified = isIconified(internalFrame);
217        if (iconified) return new Pair<Boolean, Point>(true, null);
218        return new Pair<Boolean, Point>(iconified, findIconifyLocation(internalFrame));
219      }
220    
221      /**
222       * De-iconifies the given <code>{@link JInternalFrame}</code>.
223       * @param internalFrame the target <code>JInternalFrame</code>.
224       * @throws IllegalStateException if the <code>JInternalFrame</code> is not showing on the screen.
225       * @throws ActionFailedException if the <code>JInternalFrame</code> vetoes the action.
226       */
227      @RunsInEDT
228      public void deiconify(JInternalFrame internalFrame) {
229        Triple<Boolean, Container, Point> deiconifyInfo = validateAndfindDeiconifyInfo(internalFrame);
230        if (deiconifyInfo.i) return; // internal frame is already de-iconified
231        moveMouseIgnoringAnyError(deiconifyInfo.ii, deiconifyInfo.iii);
232        setIconProperty(internalFrame, DEICONIFY);
233      }
234    
235      @RunsInEDT
236      private static Triple<Boolean, Container, Point> validateAndfindDeiconifyInfo(final JInternalFrame internalFrame) {
237        return execute(new GuiQuery<Triple<Boolean, Container, Point>>() {
238          protected Triple<Boolean, Container, Point> executeInEDT() throws Throwable {
239            validateIsShowingOrIconified(internalFrame);
240            return deiconifyInfo(internalFrame);
241          }
242        });
243      }
244    
245      @RunsInCurrentThread
246      private static Triple<Boolean, Container, Point> deiconifyInfo(JInternalFrame internalFrame) {
247        boolean deiconified = !isIconified(internalFrame);
248        if (deiconified) return new Triple<Boolean, Container, Point>(true, null, null);
249        JDesktopIcon desktopIcon = internalFrame.getDesktopIcon();
250        return new Triple<Boolean, Container, Point>(deiconified, desktopIcon, iconifyLocationOf(desktopIcon));
251      }
252    
253      @RunsInCurrentThread
254      private static Point findIconifyLocation(JInternalFrame internalFrame) {
255        return iconifyLocationOf(internalFrame.getDesktopIcon());
256      }
257    
258      @RunsInEDT
259      private void setIconProperty(JInternalFrame internalFrame, JInternalFrameAction action) {
260        try {
261          setIcon(internalFrame, action);
262          robot.waitForIdle();
263        } catch (UnexpectedException unexpected) {
264          failIfVetoed(internalFrame, action, unexpected);
265        }
266      }
267    
268      @VisibleForTesting
269      void failIfVetoed(JInternalFrame internalFrame, JInternalFrameAction action, UnexpectedException unexpected) {
270        PropertyVetoException vetoError = vetoFrom(unexpected);
271        if (vetoError == null) return;
272        throw actionFailure(concat(action.name, " of ", format(internalFrame), " was vetoed: <", vetoError.getMessage(), ">"));
273      }
274    
275      private PropertyVetoException vetoFrom(UnexpectedException unexpected) {
276        Throwable cause = unexpected.getCause();
277        if (!(cause instanceof PropertyVetoException)) return null;
278        return (PropertyVetoException)cause;
279      }
280    
281      /**
282       * Resizes the <code>{@link JInternalFrame}</code> horizontally.
283       * @param internalFrame the target <code>JInternalFrame</code>.
284       * @param width the width that the <code>JInternalFrame</code> should have after being resized.
285       * @throws IllegalStateException if the <code>JInternalFrame</code> is not showing on the screen.
286       * @throws IllegalStateException if the <code>JInternalFrame</code> is not resizable by the user.
287       */
288      @RunsInEDT
289      public void resizeWidthTo(JInternalFrame internalFrame, int width) {
290        resizeWidth(internalFrame, width);
291      }
292    
293      /**
294       * Resizes the <code>{@link JInternalFrame}</code> vertically.
295       * @param w the target <code>JInternalFrame</code>.
296       * @param height the height that the <code>JInternalFrame</code> should have after being resized.
297       * @throws IllegalStateException if the <code>JInternalFrame</code> is not showing on the screen.
298       * @throws IllegalStateException if the <code>JInternalFrame</code> is not resizable by the user.
299       */
300      @RunsInEDT
301      public void resizeHeightTo(JInternalFrame w, int height) {
302        resizeHeight(w, height);
303      }
304    
305      /**
306       * Resizes the <code>{@link JInternalFrame}</code> to the given size.
307       * @param internalFrame the target <code>JInternalFrame</code>.
308       * @param size the size to resize the <code>JInternalFrame</code> to.
309       * @throws IllegalStateException if the <code>JInternalFrame</code> is not showing on the screen.
310       * @throws IllegalStateException if the <code>JInternalFrame</code> is not resizable by the user.
311       */
312      @RunsInEDT
313      public void resizeTo(JInternalFrame internalFrame, Dimension size) {
314        resize(internalFrame, size.width, size.height);
315      }
316    
317      /**
318       * Moves the <code>{@link JInternalFrame}</code> to the given location.
319       * @param internalFrame the target <code>JInternalFrame</code>.
320       * @param where the location to move the <code>JInternalFrame</code> to.
321       * @throws IllegalStateException if the <code>JInternalFrame</code> is not showing on the screen.
322       */
323      @RunsInEDT
324      public void moveTo(JInternalFrame internalFrame, Point where) {
325        move(internalFrame, where.x, where.y);
326      }
327    
328      /**
329       * Closes the given <code>{@link JInternalFrame}</code>.
330       * @param internalFrame the target <code>JInternalFrame</code>.
331       * @throws IllegalStateException if the <code>JInternalFrame</code> is not showing on the screen.
332       * @throws IllegalStateException if the <code>JInternalFrame</code> is not closable.
333       */
334      @RunsInEDT
335      public void close(JInternalFrame internalFrame) {
336        Pair<Boolean, Point> closeInfo = validateAndFindCloseInfo(internalFrame);
337        if (closeInfo.i) return; // internal frame is already closed
338        moveMouseIgnoringAnyError(internalFrame, closeInfo.ii);
339        JInternalFrameCloseTask.close(internalFrame);
340        robot.waitForIdle();
341      }
342    
343      @RunsInEDT
344      private static Pair<Boolean, Point> validateAndFindCloseInfo(final JInternalFrame internalFrame) {
345        return execute(new GuiQuery<Pair<Boolean, Point>>() {
346          protected Pair<Boolean, Point> executeInEDT() {
347            validateCanClose(internalFrame);
348            return closeInfo(internalFrame);
349          }
350        });
351      }
352    
353      @RunsInCurrentThread
354      private static void validateCanClose(JInternalFrame internalFrame) {
355        validateIsShowing(internalFrame);
356        if (!internalFrame.isClosable())
357          throw new IllegalStateException(concat("The JInternalFrame <", format(internalFrame), "> is not closable"));
358      }
359    
360      @RunsInCurrentThread
361      private static Pair<Boolean, Point> closeInfo(JInternalFrame internalFrame) {
362        if (internalFrame.isClosed()) return new Pair<Boolean, Point>(true, null);
363        return new Pair<Boolean, Point>(false, closeLocationOf(internalFrame));
364      }
365    
366    }