/*******************************************************************************
* 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.javafx.tester.adapter;
import java.awt.Rectangle;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import org.eclipse.jubula.rc.common.exception.StepExecutionException;
import org.eclipse.jubula.rc.common.tester.adapter.interfaces.ITabbedComponent;
import org.eclipse.jubula.rc.javafx.driver.EventThreadQueuerJavaFXImpl;
import org.eclipse.jubula.rc.javafx.tester.util.NodeBounds;
import org.eclipse.jubula.tools.internal.objects.event.EventFactory;
import org.eclipse.jubula.tools.internal.objects.event.TestErrorEvent;
/**
* Adapter for a {@link TabPane}.
*
*/
public class TabPaneAdapter extends JavaFXComponentAdapter<TabPane>
implements ITabbedComponent, IContainerAdapter {
/**
* Parameter types of the method to retrieve a Tab Header Skin.
*/
private static final Class<?>[] GET_TAB_HEADER_SKIN_PARAMETER_TYPES =
new Class<?>[]{Tab.class};
/**
* Name of the method to retrieve a Tab Header Skin.
*/
private static final String GET_TAB_HEADER_SKIN_METHOD_NAME =
"getTabHeaderSkin"; //$NON-NLS-1$
/**
* Name of the class containing the method to retrieve a Tab Header Skin.
*/
private static final String TAB_HEADER_AREA_CLASSNAME =
"com.sun.javafx.scene.control.skin.TabPaneSkin$TabHeaderArea"; //$NON-NLS-1$
/**
*
* @param objectToAdapt
*/
public TabPaneAdapter(TabPane objectToAdapt) {
super(objectToAdapt);
}
/**
* {@inheritDoc}
*/
public int getTabCount() {
return EventThreadQueuerJavaFXImpl.invokeAndWait(
"getTabCount", new Callable<Integer>() { //$NON-NLS-1$
public Integer call() {
return getRealComponent().getTabs().size();
}
});
}
/**
* {@inheritDoc}
*/
public String getTitleofTab(final int index) {
return EventThreadQueuerJavaFXImpl.invokeAndWait(
"getTitleOfTab", new Callable<String>() { //$NON-NLS-1$
public String call() {
return getTabAt(index).getText();
}
});
}
/**
* {@inheritDoc}
*/
public Rectangle getBoundsAt(final int index) {
return EventThreadQueuerJavaFXImpl.invokeAndWait(
"getBoundsAt", new Callable<Rectangle>() { //$NON-NLS-1$
public Rectangle call() {
Tab targetTab = getTabAt(index);
Node targetNode = getNodeFor(targetTab);
return NodeBounds.getRelativeBounds(
targetNode, getRealComponent());
}
});
}
/**
* {@inheritDoc}
*/
public boolean isEnabledAt(final int index) {
return EventThreadQueuerJavaFXImpl.invokeAndWait(
"isEnabledAt", new Callable<Boolean>() { //$NON-NLS-1$
public Boolean call() {
return !getTabAt(index).isDisabled();
}
});
}
/**
* {@inheritDoc}
*/
public int getSelectedIndex() {
return EventThreadQueuerJavaFXImpl.invokeAndWait(
"getSelectedIndex", new Callable<Integer>() { //$NON-NLS-1$
@Override
public Integer call() throws Exception {
return getRealComponent().getSelectionModel()
.getSelectedIndex();
}
});
}
/**
* This method must be called on the FX Thread.
*
* @param index zero based index.
* @return the Tab at the zero based index in the TabPane's Tab list.
*/
private Tab getTabAt(int index) {
EventThreadQueuerJavaFXImpl.checkEventThread();
return getRealComponent().getTabs().get(index);
}
/**
* This method must be called on the FX Thread.
*
* @param targetTab the Tab for which to get the bounding node.
*
* @return the Node representing the bounding box of <code>targetTab</code>,
* or <code>null</code> if no such Node could be found.
*
* @throws StepExecutionException if the Skin or structure of the real
* component is not supported.
*/
private Node getNodeFor(Tab targetTab) throws StepExecutionException {
EventThreadQueuerJavaFXImpl.checkEventThread();
Skin<?> tabPaneSkin = getRealComponent().getSkin();
if (!(tabPaneSkin instanceof SkinBase)) {
throw new StepExecutionException(
"Skin not supported: " + tabPaneSkin.getClass(), //$NON-NLS-1$
EventFactory.createActionError(
TestErrorEvent.RENDERER_NOT_SUPPORTED));
}
ClassLoader skinClassLoader = targetTab.getClass().getClassLoader();
try {
Class<?> tabHeaderAreaClass =
skinClassLoader.loadClass(TAB_HEADER_AREA_CLASSNAME);
for (Node tabPaneSkinChild
: ((SkinBase<?>)tabPaneSkin).getChildren()) {
if (tabHeaderAreaClass.isInstance(tabPaneSkinChild)) {
Method method = tabHeaderAreaClass.getDeclaredMethod(
GET_TAB_HEADER_SKIN_METHOD_NAME,
GET_TAB_HEADER_SKIN_PARAMETER_TYPES);
method.setAccessible(true);
Object tabHeaderSkin = method.invoke(
tabPaneSkinChild, new Object[]{ targetTab });
if (tabHeaderSkin == null
|| tabHeaderSkin instanceof Node) {
return (Node) tabHeaderSkin;
}
throw new StepExecutionException(
"Skin not supported: " + tabHeaderSkin.getClass(), //$NON-NLS-1$
EventFactory.createActionError(
TestErrorEvent.RENDERER_NOT_SUPPORTED));
}
}
} catch (ClassNotFoundException | NoSuchMethodException e) {
// These exceptions indicate that internal implementation details
// of JavaFX are not what we were expecting (either something was
// changed or we are dealing with an alternate implementation).
// We *could* implement a fallback here, but, since we don't
// currently have any backup plan, we just fall-through instead.
} catch (IllegalAccessException | InvocationTargetException e) {
// These types of exceptions give us no information about whether
// the Skin is supported, so just wrap and rethrow.
throw new StepExecutionException(e);
}
throw new StepExecutionException(
"Skin / structure not supported: " + tabPaneSkin.getClass(), //$NON-NLS-1$
EventFactory.createActionError(
TestErrorEvent.RENDERER_NOT_SUPPORTED));
}
/**
* {@inheritDoc}
* In this case the Content-Node of the currently selected Tab will be returned
*/
@Override
public List<Node> getContent() {
return EventThreadQueuerJavaFXImpl.invokeAndWait("getContent", //$NON-NLS-1$
new Callable<List<Node>>() {
@Override
public List<Node> call() throws Exception {
ArrayList<Node> list = new ArrayList<>();
list.add(getRealComponent().getSelectionModel()
.getSelectedItem().getContent());
return list;
}
});
}
}