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.drivers.menus;
24
25import java.awt.Component;
26
27import javax.swing.JMenu;
28import javax.swing.JMenuBar;
29import javax.swing.JMenuItem;
30import javax.swing.JPopupMenu;
31import javax.swing.MenuElement;
32
33import org.netbeans.jemmy.ComponentChooser;
34import org.netbeans.jemmy.JemmyException;
35import org.netbeans.jemmy.Waitable;
36import org.netbeans.jemmy.Waiter;
37import org.netbeans.jemmy.drivers.DriverManager;
38import org.netbeans.jemmy.drivers.LightSupportiveDriver;
39import org.netbeans.jemmy.drivers.MenuDriver;
40import org.netbeans.jemmy.drivers.MouseDriver;
41import org.netbeans.jemmy.drivers.PathChooser;
42import org.netbeans.jemmy.operators.ComponentOperator;
43import org.netbeans.jemmy.operators.JMenuBarOperator;
44import org.netbeans.jemmy.operators.JMenuItemOperator;
45import org.netbeans.jemmy.operators.JMenuOperator;
46import org.netbeans.jemmy.operators.JPopupMenuOperator;
47
48public class DefaultJMenuDriver extends LightSupportiveDriver implements MenuDriver {
49
50    public DefaultJMenuDriver() {
51        super(new String[]{"org.netbeans.jemmy.operators.JMenuOperator",
52            "org.netbeans.jemmy.operators.JMenuBarOperator",
53            "org.netbeans.jemmy.operators.JPopupMenuOperator"});
54    }
55
56    @Override
57    public Object pushMenu(ComponentOperator oper, PathChooser chooser) {
58        checkSupported(oper);
59        if (oper instanceof JMenuBarOperator
60                || oper instanceof JPopupMenuOperator) {
61            JMenuItem item;
62            if (oper instanceof JMenuBarOperator) {
63                item = waitItem(oper,
64                        (JMenuBar) oper.getSource(),
65                        chooser, 0);
66            } else {
67                item = waitItem(oper,
68                        (JPopupMenu) oper.getSource(),
69                        chooser, 0);
70            }
71            JMenuItemOperator itemOper;
72            if (item instanceof JMenu) {
73                itemOper = new JMenuOperator((JMenu) item);
74            } else {
75                itemOper = new JMenuItemOperator(item);
76            }
77            itemOper.copyEnvironment(oper);
78            return (push(itemOper, null, (oper instanceof JMenuBarOperator) ? ((JMenuBar) oper.getSource()) : null,
79                    chooser, 1, true));
80        } else {
81            return push(oper, null, null, chooser, 0, true);
82        }
83    }
84
85    protected Object push(ComponentOperator oper, ComponentOperator lastItem,
86            JMenuBar menuBar,
87            PathChooser chooser, int depth, boolean pressMouse) {
88        try {
89            oper.waitComponentVisible(true);
90            oper.waitComponentEnabled();
91        } catch (InterruptedException e) {
92            throw (new JemmyException("Interrupted!", e));
93        }
94        MouseDriver mDriver = DriverManager.getMouseDriver(oper);
95        //mDriver.enterMouse(oper);
96        //use enhanced algorithm instead
97        smartMove(lastItem, oper);
98        if (depth > chooser.getDepth() - 1) {
99            if (oper instanceof JMenuOperator
100                    && menuBar != null && getSelectedElement(menuBar) != null) {
101                //mDriver.enterMouse(oper);
102            } else {
103                DriverManager.getButtonDriver(oper).push(oper);
104            }
105            return oper.getSource();
106        }
107        if (pressMouse && !((JMenuOperator) oper).isPopupMenuVisible()
108                && !(menuBar != null && getSelectedElement(menuBar) != null)) {
109            DriverManager.getButtonDriver(oper).push(oper);
110        }
111        oper.getTimeouts().sleep("JMenuOperator.WaitBeforePopupTimeout");
112        JMenuItem item = waitItem(oper, waitPopupMenu(oper), chooser, depth);
113        mDriver.exitMouse(oper);
114        if (item instanceof JMenu) {
115            JMenuOperator mo = new JMenuOperator((JMenu) item);
116            mo.copyEnvironment(oper);
117            return push(mo, oper, null, chooser, depth + 1, false);
118        } else {
119            JMenuItemOperator mio = new JMenuItemOperator(item);
120            mio.copyEnvironment(oper);
121            try {
122                mio.waitComponentEnabled();
123            } catch (InterruptedException e) {
124                throw (new JemmyException("Interrupted!", e));
125            }
126            //move here first
127            smartMove(oper, mio);
128            DriverManager.getButtonDriver(oper).push(mio);
129            return item;
130        }
131    }
132
133    private void smartMove(ComponentOperator last, ComponentOperator oper) {
134        if (last == null) {
135            oper.enterMouse();
136            return;
137        }
138        //get all the coordinates first
139        //previous item
140        long lastXl, lastXr, lastYl, lastYr;
141        lastXl = (long) last.getSource().getLocationOnScreen().getX();
142        lastXr = lastXl + last.getSource().getWidth();
143        lastYl = (long) last.getSource().getLocationOnScreen().getY();
144        lastYr = lastYl + last.getSource().getHeight();
145        //this item
146        long operXl, operXr, operYl, operYr;
147        operXl = (long) oper.getSource().getLocationOnScreen().getX();
148        operXr = operXl + oper.getSource().getWidth();
149        operYl = (long) oper.getSource().getLocationOnScreen().getY();
150        operYr = operYl + oper.getSource().getHeight();
151        //get the overlap borders
152        long overXl, overXr, overYl, overYr;
153        overXl = (lastXl > operXl) ? lastXl : operXl;
154        overXr = (lastXr < operXr) ? lastXr : operXr;
155        overYl = (lastYl > operYl) ? lastYl : operYl;
156        overYr = (lastYr < operYr) ? lastYr : operYr;
157        //now, let's see ...
158        //what if it overlaps by x?
159        if (overXl < overXr) {
160            //good - move mose to the center of the overlap
161            last.moveMouse((int) ((overXr - overXl) / 2 - lastXl),
162                    last.getCenterY());
163            //move mouse inside
164            oper.moveMouse((int) ((overXr - overXl) / 2 - operXl),
165                    oper.getCenterY());
166            //done - now move to the center
167            oper.enterMouse();
168            return;
169        }
170        //ok, what if it overlaps by y?
171        if (overYl < overYr) {
172            //good - move mose to the center of the overlap
173            last.moveMouse(last.getCenterX(),
174                    (int) ((overYr - overYl) / 2 - lastYl));
175            //move mouse inside
176            oper.moveMouse(last.getCenterX(),
177                    (int) ((overYr - overYl) / 2 - operYl));
178            //done - now move to the center
179            oper.enterMouse();
180            return;
181        }
182        //well - can't help it
183        oper.enterMouse();
184    }
185
186    protected JPopupMenu waitPopupMenu(final ComponentOperator oper) {
187        return ((JPopupMenu) JPopupMenuOperator.waitJPopupMenu(new ComponentChooser() {
188            @Override
189            public boolean checkComponent(Component comp) {
190                return (comp == ((JMenuOperator) oper).getPopupMenu()
191                        && comp.isShowing());
192            }
193
194            @Override
195            public String getDescription() {
196                return ((JMenuOperator) oper).getText() + "'s popup";
197            }
198
199            @Override
200            public String toString() {
201                return "waitPopupMenu.ComponentChooser{description = " + getDescription() + '}';
202            }
203        }).getSource());
204    }
205
206    protected JMenuItem waitItem(ComponentOperator oper, MenuElement element, PathChooser chooser, int depth) {
207        Waiter<MenuElement, Void> waiter = new Waiter<>(new JMenuItemWaiter(element, chooser, depth));
208        waiter.setOutput(oper.getOutput().createErrorOutput());
209        waiter.setTimeouts(oper.getTimeouts());
210        try {
211            return (JMenuItem) waiter.waitAction(null);
212        } catch (InterruptedException e) {
213            throw (new JemmyException("Waiting has been interrupted", e));
214        }
215    }
216
217    public static Object getSelectedElement(JMenuBar bar) {
218        MenuElement[] subElements = bar.getSubElements();
219        for (MenuElement subElement : subElements) {
220            if (subElement instanceof JMenu
221                    && ((JMenu) subElement).isPopupMenuVisible()) {
222                return subElement;
223            }
224        }
225        return null;
226    }
227
228    private static class JMenuItemWaiter implements Waitable<MenuElement, Void> {
229
230        MenuElement cont;
231        PathChooser chooser;
232        int depth;
233
234        public JMenuItemWaiter(MenuElement cont, PathChooser chooser, int depth) {
235            this.cont = cont;
236            this.chooser = chooser;
237            this.depth = depth;
238        }
239
240        @Override
241        public MenuElement actionProduced(Void obj) {
242            if (!((Component) cont).isShowing()) {
243                return null;
244            }
245            MenuElement[] subElements = cont.getSubElements();
246            for (MenuElement subElement : subElements) {
247                if (((Component) subElement).isShowing() && ((Component) subElement).isEnabled()
248                        && chooser.checkPathComponent(depth, subElement)) {
249                    return subElement;
250                }
251            }
252            return null;
253        }
254
255        @Override
256        public String getDescription() {
257            return "";
258        }
259
260        @Override
261        public String toString() {
262            return "JMenuItemWaiter{" + "cont=" + cont + ", chooser=" + chooser + ", depth=" + depth + '}';
263        }
264    }
265}
266