1/*
2 * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23package org.netbeans.jemmy.operators;
24
25import java.awt.Component;
26import java.awt.Container;
27import java.awt.TextComponent;
28import java.awt.event.TextListener;
29import java.util.Hashtable;
30
31import org.netbeans.jemmy.Action;
32import org.netbeans.jemmy.ComponentChooser;
33import org.netbeans.jemmy.Outputable;
34import org.netbeans.jemmy.TestOut;
35import org.netbeans.jemmy.Timeoutable;
36import org.netbeans.jemmy.Timeouts;
37import org.netbeans.jemmy.drivers.DriverManager;
38import org.netbeans.jemmy.drivers.TextDriver;
39
40/**
41 * This operator type covers java.awt.TextArea component.
42 *
43 *
44 * @see org.netbeans.jemmy.Timeouts
45 *
46 * @author Alexandre Iline (alexandre.iline@oracle.com)
47 *
48 */
49public class TextComponentOperator extends ComponentOperator
50        implements Timeoutable, Outputable {
51
52    /**
53     * Identifier for a "text" property.
54     *
55     * @see #getDump
56     */
57    public static final String TEXT_DPROP = "Text";
58
59    private final static long PUSH_KEY_TIMEOUT = 0;
60    private final static long BETWEEN_KEYS_TIMEOUT = 0;
61    private final static long CHANGE_CARET_POSITION_TIMEOUT = 60000;
62    private final static long TYPE_TEXT_TIMEOUT = 60000;
63
64    private Timeouts timeouts;
65    private TestOut output;
66
67    private TextDriver driver;
68
69    /**
70     * Constructor.
71     *
72     * @param b The {@code java.awt.TextComponent} managed by this
73     * instance.
74     */
75    public TextComponentOperator(TextComponent b) {
76        super(b);
77        driver = DriverManager.getTextDriver(getClass());
78    }
79
80    /**
81     * Constructs a TextComponentOperator object.
82     *
83     * @param cont a container
84     * @param chooser a component chooser specifying searching criteria.
85     * @param index an index between appropriate ones.
86     */
87    public TextComponentOperator(ContainerOperator<?> cont, ComponentChooser chooser, int index) {
88        this((TextComponent) cont.
89                waitSubComponent(new TextComponentFinder(chooser),
90                        index));
91        copyEnvironment(cont);
92    }
93
94    /**
95     * Constructs a TextComponentOperator object.
96     *
97     * @param cont a container
98     * @param chooser a component chooser specifying searching criteria.
99     */
100    public TextComponentOperator(ContainerOperator<?> cont, ComponentChooser chooser) {
101        this(cont, chooser, 0);
102    }
103
104    /**
105     * Constructor. Waits for a component in a container to show. The component
106     * is identified as the {@code index+1}'th
107     * {@code java.awt.TextComponent} that shows, lies below the container
108     * in the display containment hierarchy, and that has the desired text. Uses
109     * cont's timeout and output for waiting and to init this operator.
110     *
111     * @param cont The operator for a container containing the sought for
112     * textComponent.
113     * @param text TextComponent text.
114     * @param index Ordinal component index. The first component has
115     * {@code index} 0.
116     * @see ComponentOperator#isCaptionEqual(String, String, boolean, boolean)
117     */
118    public TextComponentOperator(ContainerOperator<?> cont, String text, int index) {
119        this((TextComponent) waitComponent(cont,
120                new TextComponentByTextFinder(text,
121                        cont.getComparator()),
122                index));
123        copyEnvironment(cont);
124    }
125
126    /**
127     * Constructor. Waits for a component in a container to show. The component
128     * is identified as the first {@code java.awt.TextComponent} that
129     * shows, lies below the container in the display containment hierarchy, and
130     * that has the desired text. Uses cont's timeout and output for waiting and
131     * to init this operator.
132     *
133     * @param cont The operator for a container containing the sought for
134     * textComponent.
135     * @param text TextComponent text.
136     * @see ComponentOperator#isCaptionEqual(String, String, boolean, boolean)
137     */
138    public TextComponentOperator(ContainerOperator<?> cont, String text) {
139        this(cont, text, 0);
140    }
141
142    /**
143     * Constructor. Waits component in container first. Uses cont's timeout and
144     * output for waiting and to init operator.
145     *
146     * @param cont The operator for a container containing the sought for
147     * textComponent.
148     * @param index Ordinal component index.
149     * @see ComponentOperator#isCaptionEqual(String, String, boolean, boolean)
150     */
151    public TextComponentOperator(ContainerOperator<?> cont, int index) {
152        this((TextComponent) waitComponent(cont,
153                new TextComponentFinder(),
154                index));
155        copyEnvironment(cont);
156    }
157
158    /**
159     * Constructor. Waits component in container first. Uses cont's timeout and
160     * output for waiting and to init operator.
161     *
162     * @param cont The operator for a container containing the sought for
163     * textComponent.
164     * @see ComponentOperator#isCaptionEqual(String, String, boolean, boolean)
165     */
166    public TextComponentOperator(ContainerOperator<?> cont) {
167        this(cont, 0);
168    }
169
170    /**
171     * Searches TextComponent in a container.
172     *
173     * @param cont Container in which to search for the component. The container
174     * lies above the component in the display containment hierarchy. The
175     * containment need not be direct.
176     * @param chooser org.netbeans.jemmy.ComponentChooser implementation,
177     * defining and applying search criteria.
178     * @param index Ordinal component index. The first {@code index} is 0.
179     * @return TextComponent instance or null if component was not found.
180     */
181    public static TextComponent findTextComponent(Container cont, ComponentChooser chooser, int index) {
182        return (TextComponent) findComponent(cont, new TextComponentFinder(chooser), index);
183    }
184
185    /**
186     * Searches for the first TextComponent in a container.
187     *
188     * @param cont Container in which to search for the component. The container
189     * lies above the component in the display containment hierarchy. The
190     * containment need not be direct.
191     * @param chooser org.netbeans.jemmy.ComponentChooser implementation,
192     * defining and applying search criteria.
193     * @return TextComponent instance or null if component was not found.
194     */
195    public static TextComponent findTextComponent(Container cont, ComponentChooser chooser) {
196        return findTextComponent(cont, chooser, 0);
197    }
198
199    /**
200     * Searches TextComponent by text.
201     *
202     * @param cont Container to search component in.
203     * @param text TextComponent text. If null, contents is not checked.
204     * @param ce Compare text exactly.
205     * @param ccs Compare text case sensitively.
206     * @param index Ordinal component index.
207     * @return TextComponent instance or null if component was not found.
208     * @see ComponentOperator#isCaptionEqual(String, String, boolean, boolean)
209     */
210    public static TextComponent findTextComponent(Container cont, String text, boolean ce, boolean ccs, int index) {
211        return findTextComponent(cont, new TextComponentByTextFinder(text, new DefaultStringComparator(ce, ccs)), index);
212    }
213
214    /**
215     * Searches TextComponent by text.
216     *
217     * @param cont Container to search component in.
218     * @param text TextComponent text. If null, contents is not checked.
219     * @param ce Compare text exactly.
220     * @param ccs Compare text case sensitively.
221     * @return TextComponent instance or null if component was not found.
222     * @see ComponentOperator#isCaptionEqual(String, String, boolean, boolean)
223     */
224    public static TextComponent findTextComponent(Container cont, String text, boolean ce, boolean ccs) {
225        return findTextComponent(cont, text, ce, ccs, 0);
226    }
227
228    /**
229     * Waits TextComponent in container.
230     *
231     * @param cont Container to search component in.
232     * @param chooser org.netbeans.jemmy.ComponentChooser implementation.
233     * @param index Ordinal component index.
234     * @return TextComponent instance.
235     */
236    public static TextComponent waitTextComponent(Container cont, ComponentChooser chooser, int index) {
237        return (TextComponent) waitComponent(cont, new TextComponentFinder(chooser), index);
238    }
239
240    /**
241     * Waits 0'th TextComponent in container.
242     *
243     * @param cont Container to search component in.
244     * @param chooser org.netbeans.jemmy.ComponentChooser implementation.
245     * @return TextComponent instance.
246     */
247    public static TextComponent waitTextComponent(Container cont, ComponentChooser chooser) {
248        return waitTextComponent(cont, chooser, 0);
249    }
250
251    /**
252     * Waits TextComponent by text.
253     *
254     * @param cont Container to search component in.
255     * @param text TextComponent text. If null, contents is not checked.
256     * @param ce Compare text exactly.
257     * @param ccs Compare text case sensitively.
258     * @param index Ordinal component index.
259     * @return TextComponent instance.
260     * @see ComponentOperator#isCaptionEqual(String, String, boolean, boolean)
261     */
262    public static TextComponent waitTextComponent(Container cont, String text, boolean ce, boolean ccs, int index) {
263        return waitTextComponent(cont, new TextComponentByTextFinder(text, new DefaultStringComparator(ce, ccs)), index);
264    }
265
266    /**
267     * Waits TextComponent by text.
268     *
269     * @param cont Container to search component in.
270     * @param text TextComponent text. If null, contents is not checked.
271     * @param ce Compare text exactly.
272     * @param ccs Compare text case sensitively.
273     * @return TextComponent instance.
274     * @see ComponentOperator#isCaptionEqual(String, String, boolean, boolean)
275     */
276    public static TextComponent waitTextComponent(Container cont, String text, boolean ce, boolean ccs) {
277        return waitTextComponent(cont, text, ce, ccs, 0);
278    }
279
280    static {
281        Timeouts.initDefault("TextComponentOperator.PushKeyTimeout", PUSH_KEY_TIMEOUT);
282        Timeouts.initDefault("TextComponentOperator.BetweenKeysTimeout", BETWEEN_KEYS_TIMEOUT);
283        Timeouts.initDefault("TextComponentOperator.ChangeCaretPositionTimeout", CHANGE_CARET_POSITION_TIMEOUT);
284        Timeouts.initDefault("TextComponentOperator.TypeTextTimeout", TYPE_TEXT_TIMEOUT);
285    }
286
287    @Override
288    public void setTimeouts(Timeouts timeouts) {
289        super.setTimeouts(timeouts);
290        this.timeouts = timeouts;
291    }
292
293    @Override
294    public Timeouts getTimeouts() {
295        return timeouts;
296    }
297
298    @Override
299    public void setOutput(TestOut out) {
300        output = out;
301        super.setOutput(output.createErrorOutput());
302    }
303
304    @Override
305    public TestOut getOutput() {
306        return output;
307    }
308
309    @Override
310    public void copyEnvironment(Operator anotherOperator) {
311        super.copyEnvironment(anotherOperator);
312        driver
313                = (TextDriver) DriverManager.
314                getDriver(DriverManager.TEXT_DRIVER_ID,
315                        getClass(),
316                        anotherOperator.getProperties());
317    }
318
319    /**
320     * Changes caret position.
321     *
322     * @param position Position to move caret to.
323     *
324     */
325    public void changeCaretPosition(final int position) {
326        makeComponentVisible();
327        produceTimeRestricted(new Action<Void, Void>() {
328            @Override
329            public Void launch(Void obj) {
330                driver.changeCaretPosition(TextComponentOperator.this, position);
331                return null;
332            }
333
334            @Override
335            public String getDescription() {
336                return "Caret moving";
337            }
338
339            @Override
340            public String toString() {
341                return "TextComponentOperator.changeCaretPosition.Action{description = " + getDescription() + '}';
342            }
343        }, "TextComponentOperator.ChangeCaretPositionTimeout");
344    }
345
346    /**
347     * Selects a part of text.
348     *
349     * @param startPosition Start caret position
350     * @param finalPosition Final caret position
351     *
352     */
353    public void selectText(final int startPosition, final int finalPosition) {
354        makeComponentVisible();
355        produceTimeRestricted(new Action<Void, Void>() {
356            @Override
357            public Void launch(Void obj) {
358                driver.selectText(TextComponentOperator.this, startPosition, finalPosition);
359                return null;
360            }
361
362            @Override
363            public String getDescription() {
364                return "Text selecting";
365            }
366
367            @Override
368            public String toString() {
369                return "TextComponentOperator.selectText.Action{description = " + getDescription() + '}';
370            }
371        }, "TextComponentOperator.TypeTextTimeout");
372    }
373
374    /**
375     * Finds start text position.
376     *
377     * @param text Text to be searched.
378     * @param index Index of text instance (first instance has index 0)
379     * @return Caret position correspondent to text start.
380     */
381    public int getPositionByText(String text, int index) {
382        String allText = getText();
383        int position = 0;
384        int ind = 0;
385        while ((position = allText.indexOf(text, position)) >= 0) {
386            if (ind == index) {
387                return position;
388            } else {
389                ind++;
390            }
391            position = position + text.length();
392        }
393        return -1;
394    }
395
396    /**
397     * Finds start text position.
398     *
399     * @param text Text to be searched.
400     * @return Caret position correspondent to text start.
401     */
402    public int getPositionByText(String text) {
403        return getPositionByText(text, 0);
404    }
405
406    /**
407     * Clears text.
408     *
409     */
410    public void clearText() {
411        output.printLine("Clearing text in text component\n    : "
412                + toStringSource());
413        output.printGolden("Clearing text in text component");
414        makeComponentVisible();
415        produceTimeRestricted(new Action<Void, Void>() {
416            @Override
417            public Void launch(Void obj) {
418                driver.clearText(TextComponentOperator.this);
419                return null;
420            }
421
422            @Override
423            public String getDescription() {
424                return "Text clearing";
425            }
426
427            @Override
428            public String toString() {
429                return "TextComponentOperator.clearText.Action{description = " + getDescription() + '}';
430            }
431        }, "TextComponentOperator.TypeTextTimeout");
432    }
433
434    /**
435     * Types text starting from known position.
436     *
437     * @param text Text to be typed.
438     * @param caretPosition Position to start type text
439     */
440    public void typeText(final String text, final int caretPosition) {
441        output.printLine("Typing text \"" + text + "\" from "
442                + Integer.toString(caretPosition) + " position "
443                + "in text component\n    : "
444                + toStringSource());
445        output.printGolden("Typing text \"" + text + "\" in text component");
446        makeComponentVisible();
447        produceTimeRestricted(new Action<Void, Void>() {
448            @Override
449            public Void launch(Void obj) {
450                driver.typeText(TextComponentOperator.this, text, caretPosition);
451                return null;
452            }
453
454            @Override
455            public String getDescription() {
456                return "Text typing";
457            }
458
459            @Override
460            public String toString() {
461                return "TextComponentOperator.typeText.Action{description = " + getDescription() + '}';
462            }
463        }, "TextComponentOperator.TypeTextTimeout");
464    }
465
466    /**
467     * Types text starting from known position.
468     *
469     * @param text Text to be typed.
470     */
471    public void typeText(String text) {
472        typeText(text, getCaretPosition());
473    }
474
475    /**
476     * Requests a focus, clears text, types new one and pushes Enter.
477     *
478     * @param text New text value. Shouldn't include final '\n'.
479     *
480     */
481    public void enterText(final String text) {
482        makeComponentVisible();
483        produceTimeRestricted(new Action<Void, Void>() {
484            @Override
485            public Void launch(Void obj) {
486                driver.enterText(TextComponentOperator.this, text);
487                return null;
488            }
489
490            @Override
491            public String getDescription() {
492                return "Text entering";
493            }
494
495            @Override
496            public String toString() {
497                return "TextComponentOperator.enterText.Action{description = " + getDescription() + '}';
498            }
499        }, "TextComponentOperator.TypeTextTimeout");
500    }
501
502    @Override
503    public Hashtable<String, Object> getDump() {
504        Hashtable<String, Object> result = super.getDump();
505        result.put(TEXT_DPROP, ((TextComponent) getSource()).getText());
506        return result;
507    }
508
509    ////////////////////////////////////////////////////////
510    //Mapping                                             //
511    /**
512     * Maps {@code TextComponent.addTextListener(TextListener)} through queue
513     */
514    public void addTextListener(final TextListener textListener) {
515        runMapping(new MapVoidAction("addTextListener") {
516            @Override
517            public void map() {
518                ((TextComponent) getSource()).addTextListener(textListener);
519            }
520        });
521    }
522
523    /**
524     * Maps {@code TextComponent.getCaretPosition()} through queue
525     */
526    public int getCaretPosition() {
527        return (runMapping(new MapIntegerAction("getCaretPosition") {
528            @Override
529            public int map() {
530                return ((TextComponent) getSource()).getCaretPosition();
531            }
532        }));
533    }
534
535    /**
536     * Maps {@code TextComponent.getSelectedText()} through queue
537     */
538    public String getSelectedText() {
539        return (runMapping(new MapAction<String>("getSelectedText") {
540            @Override
541            public String map() {
542                return ((TextComponent) getSource()).getSelectedText();
543            }
544        }));
545    }
546
547    /**
548     * Maps {@code TextComponent.getSelectionEnd()} through queue
549     */
550    public int getSelectionEnd() {
551        return (runMapping(new MapIntegerAction("getSelectionEnd") {
552            @Override
553            public int map() {
554                return ((TextComponent) getSource()).getSelectionEnd();
555            }
556        }));
557    }
558
559    /**
560     * Maps {@code TextComponent.getSelectionStart()} through queue
561     */
562    public int getSelectionStart() {
563        return (runMapping(new MapIntegerAction("getSelectionStart") {
564            @Override
565            public int map() {
566                return ((TextComponent) getSource()).getSelectionStart();
567            }
568        }));
569    }
570
571    /**
572     * Maps {@code TextComponent.getText()} through queue
573     */
574    public String getText() {
575        return (runMapping(new MapAction<String>("getText") {
576            @Override
577            public String map() {
578                return ((TextComponent) getSource()).getText();
579            }
580        }));
581    }
582
583    /**
584     * Maps {@code TextComponent.isEditable()} through queue
585     */
586    public boolean isEditable() {
587        return (runMapping(new MapBooleanAction("isEditable") {
588            @Override
589            public boolean map() {
590                return ((TextComponent) getSource()).isEditable();
591            }
592        }));
593    }
594
595    /**
596     * Maps {@code TextComponent.removeTextListener(TextListener)} through queue
597     */
598    public void removeTextListener(final TextListener textListener) {
599        runMapping(new MapVoidAction("removeTextListener") {
600            @Override
601            public void map() {
602                ((TextComponent) getSource()).removeTextListener(textListener);
603            }
604        });
605    }
606
607    /**
608     * Maps {@code TextComponent.select(int, int)} through queue
609     */
610    public void select(final int i, final int i1) {
611        runMapping(new MapVoidAction("select") {
612            @Override
613            public void map() {
614                ((TextComponent) getSource()).select(i, i1);
615            }
616        });
617    }
618
619    /**
620     * Maps {@code TextComponent.selectAll()} through queue
621     */
622    public void selectAll() {
623        runMapping(new MapVoidAction("selectAll") {
624            @Override
625            public void map() {
626                ((TextComponent) getSource()).selectAll();
627            }
628        });
629    }
630
631    /**
632     * Maps {@code TextComponent.setCaretPosition(int)} through queue
633     */
634    public void setCaretPosition(final int i) {
635        runMapping(new MapVoidAction("setCaretPosition") {
636            @Override
637            public void map() {
638                ((TextComponent) getSource()).setCaretPosition(i);
639            }
640        });
641    }
642
643    /**
644     * Maps {@code TextComponent.setEditable(boolean)} through queue
645     */
646    public void setEditable(final boolean b) {
647        runMapping(new MapVoidAction("setEditable") {
648            @Override
649            public void map() {
650                ((TextComponent) getSource()).setEditable(b);
651            }
652        });
653    }
654
655    /**
656     * Maps {@code TextComponent.setSelectionEnd(int)} through queue
657     */
658    public void setSelectionEnd(final int i) {
659        runMapping(new MapVoidAction("setSelectionEnd") {
660            @Override
661            public void map() {
662                ((TextComponent) getSource()).setSelectionEnd(i);
663            }
664        });
665    }
666
667    /**
668     * Maps {@code TextComponent.setSelectionStart(int)} through queue
669     */
670    public void setSelectionStart(final int i) {
671        runMapping(new MapVoidAction("setSelectionStart") {
672            @Override
673            public void map() {
674                ((TextComponent) getSource()).setSelectionStart(i);
675            }
676        });
677    }
678
679    /**
680     * Maps {@code TextComponent.setText(String)} through queue
681     */
682    public void setText(final String string) {
683        runMapping(new MapVoidAction("setText") {
684            @Override
685            public void map() {
686                ((TextComponent) getSource()).setText(string);
687            }
688        });
689    }
690
691    //End of mapping                                      //
692    ////////////////////////////////////////////////////////
693    /**
694     * Return a TextDriver used by this component.
695     *
696     * @return a driver got by the operator during creation.
697     */
698    protected TextDriver getTextDriver() {
699        return driver;
700    }
701
702    /**
703     * Allows to find component by text.
704     */
705    public static class TextComponentByTextFinder implements ComponentChooser {
706
707        String label;
708        StringComparator comparator;
709
710        /**
711         * Constructs TextComponentByTextFinder.
712         *
713         * @param lb a text pattern
714         * @param comparator specifies string comparision algorithm.
715         */
716        public TextComponentByTextFinder(String lb, StringComparator comparator) {
717            label = lb;
718            this.comparator = comparator;
719        }
720
721        /**
722         * Constructs TextComponentByTextFinder.
723         *
724         * @param lb a text pattern
725         */
726        public TextComponentByTextFinder(String lb) {
727            this(lb, Operator.getDefaultStringComparator());
728        }
729
730        @Override
731        public boolean checkComponent(Component comp) {
732            if (comp instanceof TextComponent) {
733                if (((TextComponent) comp).getText() != null) {
734                    return (comparator.equals(((TextComponent) comp).getText(),
735                            label));
736                }
737            }
738            return false;
739        }
740
741        @Override
742        public String getDescription() {
743            return "TextComponent with text \"" + label + "\"";
744        }
745
746        @Override
747        public String toString() {
748            return "TextComponentByTextFinder{" + "label=" + label + ", comparator=" + comparator + '}';
749        }
750    }
751
752    /**
753     * Checks component type.
754     */
755    public static class TextComponentFinder extends Finder {
756
757        /**
758         * Constructs TextComponentFinder.
759         *
760         * @param sf other searching criteria.
761         */
762        public TextComponentFinder(ComponentChooser sf) {
763            super(TextComponent.class, sf);
764        }
765
766        /**
767         * Constructs TextComponentFinder.
768         */
769        public TextComponentFinder() {
770            super(TextComponent.class);
771        }
772    }
773}
774