/******************************************************************************* * Copyright (c) 2012 BREDEX GmbH. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * BREDEX GmbH - initial API and implementation *******************************************************************************/ package org.eclipse.jubula.rc.swing.tester.adapter; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Rectangle; import javax.accessibility.Accessible; import javax.accessibility.AccessibleContext; import javax.swing.ComboBoxEditor; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JList; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import org.apache.commons.lang.Validate; import org.eclipse.jubula.rc.common.driver.ClickOptions; import org.eclipse.jubula.rc.common.driver.IRunnable; import org.eclipse.jubula.rc.common.exception.StepExecutionException; import org.eclipse.jubula.rc.common.logger.AutServerLogger; import org.eclipse.jubula.rc.common.tester.adapter.interfaces.IComboComponent; import org.eclipse.jubula.rc.swing.tester.util.TesterUtil; import org.eclipse.jubula.tools.internal.objects.event.EventFactory; import org.eclipse.jubula.tools.internal.objects.event.TestErrorEvent; /** * Implementation of the Interface <code>IComboBoxAdapter</code> as a * adapter for the <code>JComboBox</code> component. * @author BREDEX GmbH * */ public class JComboBoxAdapter extends JComponentAdapter implements IComboComponent { /** * <code>INVALID_MAX_WIDTH</code> */ public static final int NO_MAX_WIDTH = -1; /** the logger */ private static AutServerLogger log = new AutServerLogger( JComboBoxAdapter.class); /** */ private JComboBox m_comboBox; /** * * @param objectToAdapt */ public JComboBoxAdapter(Object objectToAdapt) { super(objectToAdapt); m_comboBox = (JComboBox) objectToAdapt; } /** * {@inheritDoc} */ public String getText() { String comboBoxText; if (isEditable()) { comboBoxText = TesterUtil.getRenderedText( getComboBoxEditorComponent(m_comboBox), true); } else { final int selIndex = getSelectedIndex(); if (selIndex == -1) { try { comboBoxText = getTextForSelectedItem(); } catch (Exception e) { comboBoxText = getEventThreadQueuer().invokeAndWait( "getSelectedItemText", //$NON-NLS-1$ new IRunnable<String>() { public String run() { return String.valueOf( m_comboBox.getSelectedItem()); } }); } } else { final JList jlist = new JList(m_comboBox.getModel()); String o = getEventThreadQueuer().invokeAndWait( "getText", new IRunnable<String>() { //$NON-NLS-1$ public String run() { Component disp = m_comboBox.getRenderer() .getListCellRendererComponent(jlist, jlist.getModel().getElementAt(selIndex), selIndex, true, m_comboBox.hasFocus()); return TesterUtil.getRenderedText(disp, false); } }); comboBoxText = String.valueOf(o); } } return comboBoxText; } /** * gets the text from the renderer for the selected Item * @return the text of the selected item */ private String getTextForSelectedItem() { String o = getEventThreadQueuer().invokeAndWait("getText", //$NON-NLS-1$ new IRunnable<String>() { public String run() { Component disp = m_comboBox.getRenderer() .getListCellRendererComponent(new JList(), m_comboBox.getSelectedItem(), 0, true, m_comboBox.hasFocus()); return TesterUtil.getRenderedText(disp, false); } }); return String.valueOf(o); } /** * {@inheritDoc} */ public boolean isEditable() { return getEventThreadQueuer().invokeAndWait("isEditable", //$NON-NLS-1$ new IRunnable<Boolean>() { public Boolean run() { return m_comboBox.isEditable(); } }); } /** * select the whole text of the textfield by clicking three times. */ public void selectAll() { click(new Integer(1)); getRobot().keyStroke(getRobot().getSystemModifierSpec() + " A"); //$NON-NLS-1$ } /** * {@inheritDoc} */ public int getSelectedIndex() { Integer actual = getEventThreadQueuer().invokeAndWait( JComboBoxAdapter.class.getName() + ".getSelectedIndex", //$NON-NLS-1$ new IRunnable<Integer>() { public Integer run() { return new Integer(m_comboBox.getSelectedIndex()); } }); return actual.intValue(); } /** * {@inheritDoc} */ public void select(int index) { JListAdapter list = new JListAdapter(findJList()); list.clickOnIndex(new Integer(index), ClickOptions .create().setClickCount(1), getMaxWidth()); } /** * Inputs <code>text</code> to <code>component</code>.<br> * @param text the text to type in * @param replace whether to rplace the text or not * @throws StepExecutionException if an error occurs during typing <code>text</code> * @throws IllegalArgumentException if <code>component</code> or <code>text</code> are null */ public void input(String text, boolean replace) throws StepExecutionException, IllegalArgumentException { Validate.notNull(text, "text must not be null"); //$NON-NLS-1$ Component editor = getComboBoxEditorComponent(m_comboBox); if (editor == null) { throw new StepExecutionException("could not find editor", //$NON-NLS-1$ EventFactory.createActionError(TestErrorEvent.COMP_NOT_FOUND)); } if (replace) { selectAll(); } getRobot().type(editor, text); } /** * performs a <code>count</code> -click on the textfield. * @param count the number of clicks */ public void click(Integer count) { Component editor = getComboBoxEditorComponent(m_comboBox); if (editor == null) { throw new StepExecutionException("no editor found", //$NON-NLS-1$ EventFactory.createActionError(TestErrorEvent.COMP_NOT_FOUND)); } getRobot().click(editor, null, ClickOptions.create().setClickCount( count.intValue())); } /** * @param component * the combobox * @return the editor used to render and edit the selected item in the * JComboBox field. * @throws StepExecutionException * if the editor component could not be found */ private Component getComboBoxEditorComponent(JComboBox component) throws StepExecutionException { ComboBoxEditor cbe = component.getEditor(); if (cbe == null) { throw new StepExecutionException("no ComboBoxEditor found", //$NON-NLS-1$ EventFactory.createActionError(TestErrorEvent.COMP_NOT_FOUND)); } Component c = cbe.getEditorComponent(); if (c == null) { throw new StepExecutionException("no EditorComponent found", //$NON-NLS-1$ EventFactory.createActionError(TestErrorEvent.COMP_NOT_FOUND)); } return c; } /** * Finds the <code>JList</code> of the combobox. * @return The list */ private JList findJList() { JList list = (JList)getComponentViaHierarchy(openPopupMenu(), JList.class); if (list == null) { throw new StepExecutionException("list component not found", //$NON-NLS-1$ EventFactory.createActionError(TestErrorEvent.COMP_NOT_FOUND)); } return list; } /** * Opens the combobox popup menu and returns the popup instance. May also be * called if the popup is already visible * @return The popup menu */ private JPopupMenu openPopupMenu() { if (!isPopupVisible()) { Component c = getComponentViaHierarchy(m_comboBox, JButton.class); Rectangle r = null; if ((c == null) && (!m_comboBox.isEditable())) { c = m_comboBox; } else if ((c == null) && (m_comboBox.isEditable())) { c = m_comboBox; r = findArrowIconArea(); } if (log.isDebugEnabled()) { log.debug("Opening popup by clicking on: " + c); //$NON-NLS-1$ } getRobot().click(c, r); } if (!isPopupVisible()) { log.debug("Dropdown list still not visible, must be an error"); //$NON-NLS-1$ throw new StepExecutionException("dropdown list not visible", //$NON-NLS-1$ EventFactory.createActionError( TestErrorEvent.DROPDOWN_LIST_NOT_FOUND)); } return getPopupMenu(m_comboBox); } /** * Tries to find the popup menu from the combobox * @param component the combobox * @return the popup of the combobox * @throws StepExecutionException if the popup could not be found */ private JPopupMenu getPopupMenu(JComboBox component) throws StepExecutionException { AccessibleContext ac = component.getAccessibleContext(); for (int i = 0; i < ac.getAccessibleChildrenCount(); i++) { Accessible a = ac.getAccessibleChild(i); if (a instanceof JPopupMenu) { return (JPopupMenu)a; } } throw new StepExecutionException("cannot find dropdown list", //$NON-NLS-1$ EventFactory.createActionError( TestErrorEvent.DROPDOWN_LIST_NOT_FOUND)); } /** * Tries to find the component in the component hierarchy * @param component where to search * @param c type of the component which should be found * @return the desired component */ private Component getComponentViaHierarchy(Container component, Class c) { Component[] comps = component.getComponents(); for (int i = 0; i < comps.length; i++) { if (c.isInstance(comps[i])) { return comps[i]; } } for (int i = 0; i < comps.length; i++) { if (comps[i] instanceof Container) { Component ct = getComponentViaHierarchy((Container)comps[i], c); if (ct != null) { return ct; } } } return null; } /** * @return true, if the popup of the combobox is visible */ private boolean isPopupVisible() { return getEventThreadQueuer().invokeAndWait( JComboBoxAdapter.class.getName() + "isPopupVisible", new IRunnable<Boolean>() { //$NON-NLS-1$ public Boolean run() throws StepExecutionException { return m_comboBox.isPopupVisible(); } }); } /** * @return a rectangle, where the arrow icon is expected. */ private Rectangle findArrowIconArea() { JComboBox comboBox = m_comboBox; Component editor = getComboBoxEditorComponent(comboBox); Rectangle r = null; if (editor == null) { throw new StepExecutionException("could not find editor", //$NON-NLS-1$ EventFactory.createActionError(TestErrorEvent.COMP_NOT_FOUND)); } Rectangle ra[] = SwingUtilities.computeDifference(comboBox.getBounds(), editor.getBounds()); if ((ra == null) || (ra.length < 1)) { throw new StepExecutionException("could not arrow icon", //$NON-NLS-1$ EventFactory.createActionError(TestErrorEvent.COMP_NOT_FOUND)); } r = ra[0]; // find the largest area of the returned rectangles. double bestAreaIndex = Double.MAX_VALUE; for (int i = 0; i < ra.length; i++) { if ((ra[i].height > 0) && (ra[i].width > 0)) { double areaIndex = ((double)ra[i].width) / ra[i].height - 1.0; if (areaIndex < 0) { areaIndex *= (-1); } if (areaIndex < bestAreaIndex) { bestAreaIndex = areaIndex; r = ra[i]; } } } return r; } /** * @return the maximal width for the selection; -1 if none available * e.g. the preferred width of the combo box itself is 100 pixel although * the preferred size of the embedded items is more than two times bigger * --> click outside of component (JList) #3013 */ private double getMaxWidth() { double maxWidth = NO_MAX_WIDTH; Dimension d = m_comboBox.getPreferredSize(); if (d != null) { maxWidth = d.getWidth(); } return maxWidth; } /** * {@inheritDoc} */ public boolean hasFocus() { return getEventThreadQueuer().invokeAndWait( "hasFocus", new IRunnable<Boolean>() { //$NON-NLS-1$ public Boolean run() { if (m_comboBox.isEditable()) { boolean editorFocus = m_comboBox.getEditor() .getEditorComponent().hasFocus(); if (editorFocus) { return Boolean.TRUE; } } return m_comboBox.hasFocus(); } }); } /** * {@inheritDoc} */ public String[] getValues() { JListAdapter list = new JListAdapter(findJList()); return list.getValues(); } }