/*
 * Copyright 2017 DSATool team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dsatool.ui;

import java.lang.reflect.Method;
import java.util.function.Consumer;
import java.util.function.Supplier;

import dsatool.util.ErrorLogger;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.ButtonBase;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Control;
import javafx.scene.control.Spinner;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;

public abstract class GraphicTableCell<S, T> extends TableCell<S, T> {

	static Method setFocused;
	static {
		try {
			setFocused = Node.class.getDeclaredMethod("setFocused", boolean.class);
			setFocused.setAccessible(true);
		} catch (final Exception e) {
			ErrorLogger.logError(e);
		}
	}

	private final boolean alwaysVisible;

	protected Node graphic;

	public GraphicTableCell(final boolean alwaysVisible) {
		this.alwaysVisible = alwaysVisible;
	}

	@Override
	public void cancelEdit() {
		super.cancelEdit();

		if (!alwaysVisible) {
			setText(getItem() != null ? getItem().toString() : "");
			setPadding(new Insets(2, 2, 2, 2));
			setGraphic(graphic);
		}
	}

	protected abstract void createGraphic();

	protected void createGraphic(final Control graphic, final Supplier<T> getText, final Consumer<T> setText) {
		setPadding(Insets.EMPTY);
		graphic.setMaxWidth(Double.MAX_VALUE);
		graphic.setContextMenu(getContextMenu());
		final ObjectProperty<T> string = itemProperty();
		setText.accept(string.get());
		setText(null);
		setGraphic(graphic);
		final TableView<S> table = getTableView();
		final TableColumn<S, T> column = getTableColumn();
		graphic.focusedProperty().addListener(new ChangeListener<Boolean>() {
			@Override
			public void changed(final ObservableValue<? extends Boolean> observable, final Boolean oldValue, final Boolean newValue) {
				if (oldValue && !newValue) {
					graphic.focusedProperty().removeListener(this);
					final CellEditEvent<S, T> editEvent = new CellEditEvent<>(table, new TablePosition<>(table, getTableRow().getIndex(), column),
							TableColumn.editCommitEvent(),
							getText.get());
					Event.fireEvent(getTableColumn(), editEvent);
					cancelEdit();
				}
			}
		});
		if (graphic instanceof TextField) {
			((TextField) graphic).setOnAction(e -> {
				requestFocus();
			});
		} else if (graphic instanceof ButtonBase) {
			((ButtonBase) graphic).setOnAction(e -> {
				requestFocus();
			});
		} else if (graphic instanceof ComboBox) {
			((ComboBox<?>) graphic).setOnAction(e -> {
				requestFocus();
			});
			graphic.focusedProperty().addListener((o, oldV, newV) -> {
				if (newV) {
					try {
						setFocused.invoke(((ComboBox<?>) graphic).getEditor(), true);
					} catch (final Exception e) {
						ErrorLogger.logError(e);
					}
				}
			});
		} else if (graphic instanceof Spinner) {
			graphic.focusedProperty().addListener((o, oldV, newV) -> {
				try {
					setFocused.invoke(((Spinner<?>) graphic).getEditor(), true);
				} catch (final Exception e) {
					ErrorLogger.logError(e);
				}
			});
		}
	}

	@Override
	public void startEdit() {
		graphic = getGraphic();
		if (!isEmpty() && !alwaysVisible) {
			createGraphic();
		}
		getGraphic().requestFocus();
	}

	@Override
	public void updateItem(final T item, final boolean empty) {
		super.updateItem(item, empty);

		if (empty) {
			setText(null);
			setGraphic(null);
		} else {
			if (alwaysVisible || isEditing()) {
				if (getGraphic() == null) {
					createGraphic();
					setText(null);
					setGraphic(getGraphic());
				}
			} else {
				if (item == null) {
					setText("");
				} else {
					setText(item.toString());
				}
				setPadding(new Insets(2, 2, 2, 2));
				setGraphic(null);
			}
		}
	}
}

@@ -610,41 +610,8 @@
 i;%0A%0A
-import java.lang.reflect.Method;%0A
 impo
@@ -679,16 +679,73 @@
 plier;%0A%0A
+import com.sun.javafx.scene.control.FakeFocusTextField;%0A%0A
 import d
@@ -1468,231 +1468,8 @@
  %7B%0A%0A
-%09static Method setFocused;%0A%09static %7B%0A%09%09try %7B%0A%09%09%09setFocused = Node.class.getDeclaredMethod(%22setFocused%22, boolean.class);%0A%09%09%09setFocused.setAccessible(true);%0A%09%09%7D catch (final Exception e) %7B%0A%09%09%09ErrorLogger.logError(e);%0A%09%09%7D%0A%09%7D%0A%0A
 %09pri
@@ -3345,34 +3345,38 @@
 %7B%0A%09%09%09%09%09%09
-setFocused.invoke(
+((FakeFocusTextField) 
 ((ComboB
@@ -3394,34 +3394,47 @@
 hic).getEditor()
-, 
+).setFakeFocus(
 true);%0A%09%09%09%09%09%7D ca
@@ -3623,37 +3623,59 @@
 %09%09%09%09
-try %7B%0A%09%09%09%09%09setFocused.invoke(
+if (newV) %7B%0A%09%09%09%09%09try %7B%0A%09%09%09%09%09%09((FakeFocusTextField) 
 ((Sp
@@ -3708,17 +3708,31 @@
 or()
-, 
+).setFakeFocus(
 true);%0A
+%09
 %09%09%09%09
@@ -3762,24 +3762,25 @@
 n e) %7B%0A%09%09%09%09%09
+%09
 ErrorLogger.
@@ -3788,24 +3788,31 @@
 ogError(e);%0A
+%09%09%09%09%09%7D%0A
 %09%09%09%09%7D%0A%09%09%09%7D);

Revert von Fokusänderung in GraphicTableCell