All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.rpgframework.jfx.rules.SkillTable Maven / Gradle / Ivy

package de.rpgframework.jfx.rules;

import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.text.Collator;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.function.Function;

import org.prelle.javafx.CloseType;
import org.prelle.javafx.JavaFXConstants;
import org.prelle.javafx.ResponsiveControl;
import org.prelle.javafx.ResponsiveControlManager;
import org.prelle.javafx.SymbolIcon;
import org.prelle.javafx.WindowMode;
import org.prelle.javafx.public_skins.GridPaneTableViewSkin;

import de.rpgframework.ResourceI18N;
import de.rpgframework.character.RuleSpecificCharacterObject;
import de.rpgframework.genericrpg.NumericalValueController;
import de.rpgframework.genericrpg.NumericalValueWith1PoolController;
import de.rpgframework.genericrpg.NumericalValueWith2PoolsController;
import de.rpgframework.genericrpg.NumericalValueWith3PoolsController;
import de.rpgframework.genericrpg.Pool;
import de.rpgframework.genericrpg.PoolCalculation;
import de.rpgframework.genericrpg.Possible;
import de.rpgframework.genericrpg.ValueType;
import de.rpgframework.genericrpg.chargen.OperationResult;
import de.rpgframework.genericrpg.chargen.RecommendationState;
import de.rpgframework.genericrpg.data.ASkillValue;
import de.rpgframework.genericrpg.data.IAttribute;
import de.rpgframework.genericrpg.data.ISkill;
import de.rpgframework.genericrpg.data.OneAttributeSkill;
import de.rpgframework.genericrpg.data.SkillSpecializationValue;
import de.rpgframework.genericrpg.data.TwoAttributeSkill;
import de.rpgframework.jfx.NumericalValueTableCell;
import de.rpgframework.jfx.PoolCell;
import de.rpgframework.jfx.RPGFrameworkJFXConstants;
import de.rpgframework.jfx.cells.RecommendingDataItemValueTableCell;
import de.rpgframework.jfx.rules.skin.Properties;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.SetChangeListener;
import javafx.css.PseudoClass;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Skin;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.util.Callback;

/**
 * @author Stefan Prelle
 *
 */
public class SkillTable> extends TableView implements ResponsiveControl {

	private static PropertyResourceBundle RES = (PropertyResourceBundle) ResourceBundle.getBundle(SkillTable.class.getName());

	private final static Logger logger = System.getLogger(SkillTable.class.getPackageName()+".skill");

	/** How many attributes are assigned to a skill */
	public static enum Mode {
		NO_ATTRIB,
		ONE_ATTRIB,
		TWO_ATTRIB
	}

	private ObjectProperty> model = new SimpleObjectProperty>();
	private ObjectProperty> controller = new SimpleObjectProperty>();
	/**
	 * Use expert mode for priority generators
	 */
	private BooleanProperty useExpertMode = new SimpleBooleanProperty(false);
	protected BooleanProperty expertModeAvailable = new SimpleBooleanProperty(false);
	protected BooleanProperty hideValueColumns = new SimpleBooleanProperty(false);
	protected BooleanProperty hidePoolColumn = new SimpleBooleanProperty(false);

    /**
     * Callback to open an edit action dialog
     */
	private ObjectProperty> actionCallback = new SimpleObjectProperty<>();

//	private ObservableList columns = FXCollections.observableArrayList();

	private TableColumn colRec;
	private TableColumn colName;
	private TableColumn colAttrib1;
	private TableColumn colAttrib2;
	/** Value means ENABLED(true) oR DISABLED(false) */
	protected TableColumn colDec;
	/** Value means ENABLED(true) oR DISABLED(false) */
	protected TableColumn colInc;
	protected TableColumn colPoints1;
	protected TableColumn colPoints2;
	protected TableColumn colPoints1Only;
	protected TableColumn colPoints2Only;
	private TableColumn colValue;
	private TableColumn> colFinal;
	private TableColumn colExtra;
	private ToggleButton headBtnPoints, headBtnPoints2;
	private ToggleGroup toggles = new ToggleGroup();
	private Mode attribMode = Mode.NO_ATTRIB;

	/** Shall the attribute columns be present in non-minimal mode? */
	private BooleanProperty showAttributes = new SimpleBooleanProperty(true);
	private Function cellVisibilityFactory;

	private boolean isUpdating;

	//-------------------------------------------------------------------
	public SkillTable() {
		this.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);
		initColumns();
		getStyleClass().add("skill-table");
		initInteractivity();
	}

	//-------------------------------------------------------------------
	public SkillTable(List skillse) {
		this();
		setData( skillse );
		refresh();
	}

	//-------------------------------------------------------------------
	public SkillTable setAttributeMode(Mode value) {
		attribMode=value;
		updateLayout();
		return this; }


	//-------------------------------------------------------------------
	public SkillTable setData(List data) {
		logger.log(Level.DEBUG, "setData() with {0} items", data.size());

		getItems().setAll(data);

		if (data.isEmpty()) {
			logger.log(Level.WARNING,"Empty skill list");
			return this;
		}

		S cls = data.get(0).getModifyable();
		attribMode = Mode.NO_ATTRIB;

		if (cls instanceof OneAttributeSkill) {
			attribMode = Mode.ONE_ATTRIB;
			colAttrib1.setCellValueFactory(new Callback, ObservableValue>() {
				@SuppressWarnings("unchecked")
				public ObservableValue call(CellDataFeatures param) {
					return new SimpleObjectProperty(  ((OneAttributeSkill)param.getValue().getModifyable()).getAttribute() );
				}
			});
		}
		if (cls instanceof TwoAttributeSkill) {
			attribMode = Mode.TWO_ATTRIB;
			colAttrib2.setCellValueFactory(new Callback, ObservableValue>() {
				@SuppressWarnings("unchecked")
				public ObservableValue call(CellDataFeatures param) {
					return new SimpleObjectProperty(  ((TwoAttributeSkill)param.getValue().getModifyable()).getAttribute2() );
				}
			});
		}
		refresh();
		return this;
	}

	//-------------------------------------------------------------------
	public BooleanProperty useExpertModeProperty() { return useExpertMode; }
	public boolean isUseExpertMode() { return useExpertMode.get(); }
	public SkillTable setUseExpertMode(boolean value) { useExpertMode.set(value); return this; }

	//-------------------------------------------------------------------
	public ReadOnlyBooleanProperty expertModeAvailableProperty() { return expertModeAvailable; }
	public boolean isExpertModeAvailable() { return expertModeAvailable.get(); }

	//-------------------------------------------------------------------
	public ObjectProperty> actionCallbackProperty() { return actionCallback; }
	public Callback getActionCallback() { return actionCallback.get(); }
	public SkillTable setActionCallback(Callback value) { actionCallback.set(value); return this; }

	//-------------------------------------------------------------------
	public ReadOnlyObjectProperty selectedItemProperty() { return getSelectionModel().selectedItemProperty(); }
	public V getSelectedItem() { return getSelectionModel().getSelectedItem(); }

	//-------------------------------------------------------------------
	public BooleanProperty hideValueColumnsProperty() { return hideValueColumns; }
	public boolean isHideValueColumns() { return hideValueColumns.get(); }
	public SkillTable setHideValueColumns(boolean value) { hideValueColumns.set(value); return this; }

	//-------------------------------------------------------------------
	public BooleanProperty hidePoolColumnProperty() { return hidePoolColumn; }
	public boolean isHidePoolColumn() { return hidePoolColumn.get(); }
	public SkillTable setHidePoolColumn(boolean value) { hidePoolColumn.set(value); return this; }

	//-------------------------------------------------------------------
	@SuppressWarnings("unchecked")
	private void initColumns() {
		colRec  = new TableColumn("Rec");
		colRec.setCellValueFactory(new Callback, ObservableValue>() {
			public ObservableValue call(CellDataFeatures param) {
				if (param.getValue()==null) {
					logger.log(Level.ERROR, "No SkillValue for cell "+param);
					return new SimpleObjectProperty<>(RecommendationState.NEUTRAL);
				}
				if (controller.get()==null) {
					return new SimpleObjectProperty<>(RecommendationState.NEUTRAL);
				}
				return new SimpleObjectProperty(controller.get().getRecommendationState(param.getValue().getModifyable()));
			}
		});
		colRec.setCellFactory(lv -> new RecommendingDataItemValueTableCell());
		colRec.setPrefWidth(30);
		colRec.setMaxWidth(30);

		colName = new TableColumn("Name");
//		colName.setCellValueFactory(new Callback, ObservableValue>() {
//			public ObservableValue call(CellDataFeatures param) {
//				return new SimpleStringProperty(param.getValue().getModifyable().getName());
//			}
//		});
		colName.setCellValueFactory(cdv -> new SimpleStringProperty(
				cdv.getValue().getModifyable().getChoices().isEmpty()
				?
				cdv.getValue().getModifyable().getName(Locale.getDefault())
				:
					(cdv.getValue().getDecision(cdv.getValue().getModifyable().getChoices().get(0).getUUID())!=null)
					?
							cdv.getValue().getDecision(cdv.getValue().getModifyable().getChoices().get(0).getUUID()).getValue()
							:"?"
				));
		colName.setCellFactory( (lv) -> new SkillNameTableCell(this));
		colName.setPrefWidth(160);
		colName.setMaxWidth(180);

		colValue = new TableColumn("Value");
		colValue.setCellValueFactory(new Callback, ObservableValue>() {
			public ObservableValue call(CellDataFeatures param) {
				return new SimpleIntegerProperty(param.getValue().getDistributed());
			}
		});
		colValue.setCellFactory( (tc) -> new NumericalValueTableCell(controller, v -> isFinalCellVisible(v)));
		colValue.setPrefWidth(80);
		colValue.setMaxWidth(80);

		colAttrib1 = new TableColumn();
		colAttrib2 = new TableColumn();
		colAttrib1.setPrefWidth(45);
		colAttrib1.setMaxWidth(50);
		colAttrib2.setPrefWidth(45);
		colAttrib2.setMaxWidth(50);
		colAttrib1.setCellFactory( (tc) -> new TableCell(){
			public void updateItem(A item, boolean empty) {
				super.updateItem(item, empty);
				if (empty || item==null) {
					setText(null);
				} else {
					setText( item.getShortName(Locale.getDefault()));
				}
			}
		});
		colAttrib2.setCellFactory( (tc) -> new TableCell(){
			public void updateItem(A item, boolean empty) {
				super.updateItem(item, empty);
				if (empty || item==null) {
					setText(null);
				} else {
					setText( item.getShortName(Locale.getDefault()));
				}
			}
		});

		headBtnPoints = createToggle(null);
		headBtnPoints2= createToggle(null);
		colPoints1 = new TableColumn<>("?");
		colPoints1Only = new TableColumn<>();
		colPoints1Only.setGraphic(headBtnPoints);
		colPoints1Only.setPrefWidth(50);
		colPoints2 = new TableColumn<>("?");
		colPoints2Only = new TableColumn<>();
		colPoints2Only.setGraphic(headBtnPoints2);
		colPoints2Only.setPrefWidth(46);

		colDec = new TableColumn<>();
		colDec.setPrefWidth(36);
		colDec.setCellValueFactory(cdv -> new SimpleBooleanProperty(
				(toggles.getSelectedToggle()==headBtnPoints)?(((NumericalValueWith1PoolController)controller.get()).canBeDecreasedPoints(cdv.getValue()).get() || ((NumericalValueWith2PoolsController)controller.get()).canBeDecreasedPoints2(cdv.getValue()).get() )
				:
					controller.get().canBeDecreased(cdv.getValue()).get()));

		colDec.setCellFactory(cdv -> new TableCell<>() {
			public void updateItem(Boolean item, boolean empty) {
				super.updateItem(item, empty);
				if (empty) {
					setGraphic(null);
				} else {
					Button btn = new Button(null, new SymbolIcon("remove"));
					btn.setDisable(!item);
					btn.getStyleClass().add("mini-button");
					btn.setOnAction(ev -> {
					if (toggles.getSelectedToggle()==headBtnPoints) {
						logger.log(Level.DEBUG, "DEC");
						V val = getTableRow().getItem();
						setVisible(isFinalCellVisible(val));
						if (toggles.getSelectedToggle()==headBtnPoints) {
							logger.log(Level.DEBUG, "INC Points 1");
							((NumericalValueWith1PoolController)controller.get()).decreasePoints(val);
						} else {
							logger.log(Level.DEBUG, "INC Points 2");
							((NumericalValueWith2PoolsController)controller.get()).decreasePoints2(val);
						}
						refresh();
					}
					});
					setGraphic(btn);
				}
			}
		});

		colInc = new TableColumn<>();
		colInc.setPrefWidth(40);
		colInc.setCellValueFactory(cdv -> new SimpleBooleanProperty(
				(toggles.getSelectedToggle()==headBtnPoints)?(((NumericalValueWith1PoolController)controller.get()).canBeIncreasedPoints(cdv.getValue()).get() || ((NumericalValueWith3PoolsController)controller.get()).canBeIncreasedPoints3(cdv.getValue()).get() )
				:
					controller.get().canBeIncreased(cdv.getValue()).get()));

		colInc.setCellFactory(cdv -> new TableCell<>() {
			@SuppressWarnings("rawtypes")
			public void updateItem(Boolean item, boolean empty) {
				super.updateItem(item, empty);
				if (empty) {
					setGraphic(null);
				} else {
					Button btn = new Button(null, new SymbolIcon("add"));
					btn.setDisable(!item);
					btn.getStyleClass().add("mini-button");
					V val = getTableRow().getItem();
					setVisible(isFinalCellVisible(val));
					btn.setOnAction(ev -> {
						if (toggles.getSelectedToggle()==headBtnPoints) {
							logger.log(Level.DEBUG, "INC Points 1");
							((NumericalValueWith1PoolController)controller.get()).increasePoints(val);
						} else {
							logger.log(Level.DEBUG, "INC Points 2");
							((NumericalValueWith2PoolsController)controller.get()).increasePoints2(val);
						}
						refresh();
					});
					setGraphic(btn);
				}
			}
		});
		colFinal = new TableColumn>(ResourceI18N.get(RES, "column.pool"));
		colFinal.setCellValueFactory(new Callback>, ObservableValue>>() {
			public ObservableValue> call(CellDataFeatures> param) {
				Pool pool = param.getValue().getPool();
//				logger.log(Level.WARNING, "Pool for skill {0} is {1}", param.getValue(), pool);
				if (pool == null) {
					pool = new Pool();
					S skill = param.getValue().getModifyable();
					if (model.get() != null) {
						if (skill instanceof TwoAttributeSkill) {
							A a1 = ((TwoAttributeSkill) skill).getAttribute();
							A a2 = ((TwoAttributeSkill) skill).getAttribute2();
							if (a1 != null)
								pool.addStep(ValueType.NATURAL, new PoolCalculation(
										model.get().getAttribute(a1).getModifiedValue(), a1.getName()));
							if (a2 != null)
								pool.addStep(ValueType.NATURAL, new PoolCalculation(
										model.get().getAttribute(a2).getModifiedValue(), a2.getName()));
						} else if (skill instanceof OneAttributeSkill) {
							A a1 = ((OneAttributeSkill) skill).getAttribute();
							if (a1 != null)
								pool.addStep(ValueType.NATURAL, new PoolCalculation(
										model.get().getAttribute(a1).getModifiedValue(), a1.getName()));
						}
					}
				}

				return new SimpleObjectProperty>(pool);
			}
		});
		colFinal.setCellFactory( (tc) -> new PoolCell());

		colFinal.setPrefWidth(30);
//		colFinal.setMaxWidth(40);
		//colFinal.setStyle("-fx-pref-width: 2em; -fx-max-width: 3em");
		colFinal.setMaxWidth(50);

		colExtra = new TableColumn();

		updateLayout();
	}

	//-------------------------------------------------------------------
	private boolean isFinalCellVisible(V sVal) {
		if (cellVisibilityFactory!=null) {
			return cellVisibilityFactory.apply(sVal);
		} else
			return true;
	}

	//-------------------------------------------------------------------
	private ToggleButton createToggle(String key) {
		ToggleButton lab = new ToggleButton( (key!=null)?ResourceI18N.get(RES, key):"");
		lab.getStyleClass().add(JavaFXConstants.STYLE_TABLE_HEAD+"-toggle");
		lab.setMaxWidth(Double.MAX_VALUE);
		lab.setToggleGroup(toggles);
		return lab;
	}

	//-------------------------------------------------------------------
	private void initInteractivity() {
		useExpertModeProperty().addListener( ev -> updateLayout());
        hideValueColumnsProperty().addListener(ev -> updateLayout());
		controller.addListener( (ov,o,n) -> {
			if (n instanceof NumericalValueWith1PoolController) {
				headBtnPoints.setText(  ((NumericalValueWith1PoolController)n).getColumn1() );
			}
			if (n instanceof NumericalValueWith2PoolsController) {
				headBtnPoints2.setText(  ((NumericalValueWith2PoolsController)n).getColumn2() );
			}
		});
        toggles.selectedToggleProperty().addListener( (ov,o,n) -> refresh());

	}

	//-------------------------------------------------------------------
	/**
	 * @see javafx.scene.control.Control#createDefaultSkin()
	 */
	@Override
	public Skin createDefaultSkin() {
		return (new GridPaneTableViewSkin(this, true)).setCenterAfter(1);
		//return (new SkillTableSkin(this, true)).setCenterAfter(1);
	}

	//-------------------------------------------------------------------
	public ObjectProperty> modelProperty() { return model; }
	public RuleSpecificCharacterObject getModel() { return model.get(); }
	public SkillTable setModel(RuleSpecificCharacterObject value) { model.set(value); refresh(); return this; }

	//-------------------------------------------------------------------
	public ObjectProperty> controllerProperty() { return controller; }
	public NumericalValueController getController() { return controller.get(); }
	public SkillTable setController(NumericalValueController value) { controller.set(value); refresh(); return this; }
	public boolean is2PointsController() { return controller.get()!=null && controller.get() instanceof NumericalValueWith2PoolsController;}


	//-------------------------------------------------------------------
	public int getPoints1(V sVal) { return (controller.get()==null)?-1: ((NumericalValueWith1PoolController)controller.get()).getPoints(sVal); }
	public int getPoints2(V sVal) { return (controller.get()==null)?-1: ((NumericalValueWith2PoolsController)controller.get()).getPoints2(sVal); }
	public int getPoints3(V sVal) { return (controller.get()==null)?-1: ((NumericalValueWith3PoolsController)controller.get()).getPoints3(sVal); }

	//-------------------------------------------------------------------
	/**
	 * Called when a controller changes
	 */
	private void updateColumns() {
		colPoints1.setText("?");
		colPoints2.setText("?");

		colPoints1.setCellValueFactory(cdv -> new SimpleIntegerProperty(getPoints1(cdv.getValue())));
		if (controller.get() instanceof NumericalValueWith1PoolController) {
			NumericalValueWith1PoolController c1 = (NumericalValueWith1PoolController) controller.get();
			colPoints1.setText(c1.getColumn1());
			colPoints1.setCellFactory(col -> {
				NumericalValueController c = new NumericalValueController() {
					public RecommendationState getRecommendationState(S item) {
						return RecommendationState.NEUTRAL;
					}
					public int getValue(V value) { return c1.getPoints(value); }

					public Possible canBeIncreased(V value) {
						return c1.canBeIncreasedPoints(value);
					}

					public Possible canBeDecreased(V value) {
						return c1.canBeDecreasedPoints(value);
					}

					public OperationResult increase(V value) {
						return c1.increasePoints(value);
					}

					public OperationResult decrease(V value) {
						return c1.decreasePoints(value);
					}
				};
				return new NumericalValueTableCell(c, true);
			});
			colPoints1Only = new TableColumn<>();
			colPoints1Only.setGraphic(headBtnPoints);
			colPoints1Only.setCellValueFactory(cdv -> new SimpleIntegerProperty(getPoints1(cdv.getValue())));
			colPoints1Only.setPrefWidth(50);
		}
		// For 2 points
		if (controller.get() instanceof NumericalValueWith3PoolsController) {
			NumericalValueWith3PoolsController c1 = (NumericalValueWith3PoolsController) controller.get();
//			colPoints2.setText(c1.getColumn3());
			colPoints2.setCellValueFactory(cdv -> new SimpleIntegerProperty(getPoints3(cdv.getValue())));
			colPoints2.setCellFactory(col -> {
				NumericalValueController c = new NumericalValueController() {
					public RecommendationState getRecommendationState(S item) {
						return RecommendationState.NEUTRAL;
					}
					public int getValue(V value) { return c1.getPoints3(value); }
					public Possible canBeIncreased(V value) {
						return c1.canBeIncreasedPoints3(value);
					}

					public Possible canBeDecreased(V value) {
						return c1.canBeDecreasedPoints3(value);
					}

					public OperationResult increase(V value) {
						logger.log(Level.WARNING, "increase3");
						return c1.increasePoints3(value);
					}

					public OperationResult decrease(V value) {
						logger.log(Level.WARNING, "decrease3");
						return c1.decreasePoints3(value);
					}
				};
				return new NumericalValueTableCell(c, false);
			});
			colPoints2Only = new TableColumn<>();
			colPoints2Only.setGraphic(headBtnPoints2);
			colPoints2Only.setCellValueFactory(cdv -> new SimpleIntegerProperty(getPoints3(cdv.getValue())));
			colPoints2Only.setPrefWidth(50);
		} else
		if (controller.get() instanceof NumericalValueWith2PoolsController) {
			NumericalValueWith2PoolsController c1 = (NumericalValueWith2PoolsController) controller.get();
			colPoints2.setText(c1.getColumn2());
			colPoints2.setCellValueFactory(cdv -> new SimpleIntegerProperty(getPoints2(cdv.getValue())));
			colPoints2.setCellFactory(col -> {
				NumericalValueController c = new NumericalValueController() {
					public RecommendationState getRecommendationState(S item) {
						return RecommendationState.NEUTRAL;
					}
					public int getValue(V value) { return c1.getPoints2(value); }

					public Possible canBeIncreased(V value) {
						return c1.canBeIncreasedPoints2(value);
					}

					public Possible canBeDecreased(V value) {
						return c1.canBeDecreasedPoints2(value);
					}

					public OperationResult increase(V value) {
						return c1.increasePoints2(value);
					}

					public OperationResult decrease(V value) {
						logger.log(Level.WARNING, "decrease2");
						return c1.decreasePoints2(value);
					}
				};
				return new NumericalValueTableCell(c, false);
			});
			colPoints2Only = new TableColumn<>();
			colPoints2Only.setGraphic(headBtnPoints2);
			colPoints2Only.setCellValueFactory(cdv -> new SimpleIntegerProperty(getPoints2(cdv.getValue())));
			colPoints2Only.setPrefWidth(50);
		}
	}

	//-------------------------------------------------------------------
	public void refresh() {
//		logger.log(Level.INFO, "refresh() with "+getItems().size()+" items");
		expertModeAvailable.set(is2PointsController());

		final Collator collator = Collator.getInstance();
		Collections.sort(getItems(), new Comparator() {
			public int compare(V v1, V v2) {
				if (v1.getModifyable()==null) return 0;
				if (v2.getModifyable()==null) return 0;
				return collator.compare(v1.getModifyable().getName(), v2.getModifyable().getName()) ;
			}
		});

		getProperties().put(Properties.RECREATE, Boolean.TRUE);
	}

	//-------------------------------------------------------------------
	private void updateLayout() {
		logger.log(Level.DEBUG, "updateLayout");
		if (isUpdating) return;
		isUpdating = true;

		synchronized (getColumns()) {
			updateColumns();
			try {
				boolean expertMode = isUseExpertMode();
				boolean enoughSpace = ResponsiveControlManager.getCurrentMode() != WindowMode.MINIMAL;
				boolean hideValues = isHideValueColumns();

				if (!expertMode) {
					toggles.selectToggle(null);
					if (enoughSpace) {
						getColumns().setAll(colRec, colName, colValue, colFinal);
					} else {
						getColumns().setAll(colRec, colName, colValue);
					}
					if (hideValues) getColumns().removeAll(colValue, colFinal);
				} else {
					if (enoughSpace) {
						toggles.selectToggle(null);
						getColumns().setAll(colRec, colName, colPoints1, colPoints2, colFinal);
					} else {
						toggles.selectToggle(headBtnPoints);
						getColumns().setAll(colRec, colName, colDec, colPoints1Only, colPoints2Only, colInc);
					}
					if (hideValues) getColumns().removeAll(colDec, colPoints1Only, colPoints2Only, colInc);
				}

				// Eventually add attribute columns
				if (enoughSpace) {
					switch (attribMode) {
					case TWO_ATTRIB:
						getColumns().add(getColumns().indexOf(colName) + 1, colAttrib2);
					case ONE_ATTRIB:
						getColumns().add(getColumns().indexOf(colName) + 1, colAttrib1);
					default:
					}
					getColumns().add(colExtra);
				}

				if (hideValueColumns.get()) {
					getColumns().removeAll(colPoints1, colPoints2, colFinal, colDec, colPoints1Only, colPoints2Only, colInc);
				}
				if (hidePoolColumn.get()) {
					getColumns().removeAll(colFinal);
				}
			} finally {
				isUpdating = false;
			}
		}
	}

	//-------------------------------------------------------------------
	/**
	 * @see org.prelle.javafx.ResponsiveControl#setResponsiveMode(org.prelle.javafx.WindowMode)
	 */
	@Override
	public void setResponsiveMode(WindowMode value) {
		getProperties().put(Properties.WINDOW_MODE, value);
		updateLayout();
		this.requestLayout();
	}

	//-------------------------------------------------------------------
	public void setExtraCellValueFactory(Callback, ObservableValue> factory) {
		colExtra.setCellValueFactory(factory);
		refresh();
	}

	//-------------------------------------------------------------------
	public void setExtraCellFactory(Callback, TableCell> factory) {
		colExtra.setCellFactory(factory);
		refresh();
	}

	//-------------------------------------------------------------------
	public TableColumn getRecommendationColumn() { return colRec; }
	public TableColumn getNameColumn() { return colName; }
	public TableColumn getValueColumn() { return colValue; }
	public TableColumn getAttribute1Column() { return colAttrib1; }
	public TableColumn getAttribute2Column() { return colAttrib2; }
	public TableColumn> getFinalValueColumn() { return colFinal; }
	public TableColumn getExtraColumn() { return colExtra; }

	public BooleanProperty showAttributesProperty() { return showAttributes; }

	//-------------------------------------------------------------------
	public void setCellVisibilityFactory(Function factory) {
		cellVisibilityFactory = factory;
	}
}

//-------------------------------------------------------------------
//-------------------------------------------------------------------
class SkillNameTableCell> extends TableCell {

	private final static Logger logger = System.getLogger(RPGFrameworkJFXConstants.BASE_LOGGER_NAME+".skill");

	private SkillTable parent;
	private Button button;
	private StackPane stack;
	private Label lbName;
	private VBox box ;

	//-------------------------------------------------------------------
	public SkillNameTableCell(SkillTable parent) {
		this.parent = parent;
		lbName = new Label();
		lbName.getStyleClass().add(JavaFXConstants.STYLE_HEADING5);
		lbName.setMaxWidth(Double.MAX_VALUE);
		box = new VBox(2, lbName);
		box.setAlignment(Pos.TOP_LEFT);

		button = new Button(null, new SymbolIcon("Edit"));
		button.getStyleClass().add("mini-button");
		button.setVisible(false);

		stack = new StackPane();
		stack.getChildren().addAll(box, button);
		StackPane.setAlignment(button, Pos.CENTER_RIGHT);
		StackPane.setAlignment(lbName, Pos.CENTER_LEFT);

		button.setOnAction(ev -> {
			Callback callback = parent.getActionCallback();
			logger.log(Level.INFO, "Clicked Edit - calling "+callback);
			if (callback!=null) {
				CloseType close = callback.call(getTableRow().getItem());
				logger.log(Level.WARNING, "Returned "+close);
				if (close==CloseType.OK || close==CloseType.APPLY) {
					parent.refresh();
				}
			} else {
				logger.log(Level.WARNING, "No callback to handle click on SkillTable");
			}

		});

		// Only show the edit button, when cursor hovers above the row
		getPseudoClassStates().addListener(new SetChangeListener() {
			public void onChanged(Change change) {
				if (change.getElementRemoved()!=null && change.getElementRemoved().getPseudoClassName().equals("rowhover")) {
					button.setVisible(false);
				}
				if (change.getElementAdded()!=null && change.getElementAdded().getPseudoClassName().equals("rowhover")) {
					button.setVisible(true);
				}

			}});
	}

	//-------------------------------------------------------------------
	/**
	 * @see javafx.scene.control.Cell#updateItem(java.lang.Object, boolean)
	 */
	@Override
	public void updateItem(String item, boolean empty) {
		super.updateItem(item, empty);

		if (empty) {
			setText(null);
			setGraphic(null);
		} else {
			box.getChildren().retainAll(lbName);
			lbName.setText(item);
			V sVal = getTableRow().getItem();
			if (sVal==null) {
				logger.log(Level.ERROR, "No tablerow item for '" + item + "'");
			} else {
				if (!sVal.getSpecializations().isEmpty()) {
					for (SkillSpecializationValue tmp : sVal.getSpecializations()) {
						if (tmp.getResolved()!=null) {
						Label lb = new Label("- " + tmp.getResolved().getName(Locale.getDefault()));
						box.getChildren().add(lb);
						} else {
							box.getChildren().add(new Label("- "+tmp.getKey()));
						}
					}
				}
			}
			setGraphic(stack);
		}
	}
}