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

de.rpgframework.jfx.ComplexDataItemControllerTwoColumnSkin Maven / Gradle / Ivy

package de.rpgframework.jfx;

import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.Arrays;
import java.util.List;

import org.prelle.javafx.AlertManager;
import org.prelle.javafx.AlertType;
import org.prelle.javafx.CloseType;
import org.prelle.javafx.FlexibleApplication;
import org.prelle.javafx.ManagedDialog;
import org.prelle.javafx.ResponsiveControlManager;
import org.prelle.javafx.SymbolIcon;
import org.prelle.javafx.WindowMode;

import de.rpgframework.ResourceI18N;
import de.rpgframework.genericrpg.Possible;
import de.rpgframework.genericrpg.chargen.ComplexDataItemController;
import de.rpgframework.genericrpg.chargen.OperationResult;
import de.rpgframework.genericrpg.data.Choice;
import de.rpgframework.genericrpg.data.ComplexDataItem;
import de.rpgframework.genericrpg.data.ComplexDataItemValue;
import de.rpgframework.genericrpg.data.Decision;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.SkinBase;
import javafx.scene.image.WritableImage;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.util.Callback;

/**
 * @author prelle
 *
 */
public class ComplexDataItemControllerTwoColumnSkin> extends SkinBase> {

	private final static Logger logger = RPGFrameworkJavaFX.logger;

	private final static String COL2_BASESTYLE = "-fx-margin: 0 1em 0 0; -fx-min-width: 23em;";

	private Button btnAdd,btnDel;

	private ListView listPossible;
	private ListView listSelected;
	private HBox line1Select;

	private Label lblAvailable;
	private Label lblSelected;

	private Label phAvailable;
	private Label phSelected;

	private Callback, ListCell> cfAvailable;
	private Callback, ListCell> cfSelected;

	private HBox columns;
	private VBox col1, col2;

	//-------------------------------------------------------------------
	protected ComplexDataItemControllerTwoColumnSkin(ComplexDataItemControllerNode control) {
		super(control);
		initComponents();
		initLayout();
		initInteractivity();
		setResponsiveMode(ResponsiveControlManager.getCurrentMode());
	}

	//-------------------------------------------------------------------
	private void initComponents() {
		btnAdd = new Button(null, new SymbolIcon("Add"));
		btnDel = new Button(null, new SymbolIcon("Delete"));
		btnDel.setDisable(true);
		cfAvailable = new Callback, ListCell>() {
			public ListCell call(ListView param) {
				ListCell cell = (getSkinnable().getAvailableCellFactory()!=null)?getSkinnable().getAvailableCellFactory().call(param):createDefaultCellImpl();
				cell.setOnMouseClicked(ev -> mouseClickedAvailable(cell, ev));
				cell.setOnDragDetected(ev -> dragDetectedAvailable(cell, ev));
				return cell;
			}
		};
		cfSelected = new Callback, ListCell>() {
			public ListCell call(ListView param) {
				ListCell cell = (getSkinnable().getSelectedCellFactory()!=null)?getSkinnable().getSelectedCellFactory().call(param):createDefaultCellImpl();
				cell.setOnMouseClicked(ev -> mouseClickedSelected(cell, ev));
				cell.setOnDragDetected(ev -> dragDetectedSelected(cell, ev));
				return cell;
			}
		};

		phAvailable = new Label();
		phAvailable.setWrapText(true);
		phSelected = new Label();
		phSelected.setWrapText(true);
		phSelected.setStyle("-fx-background-color: -fx-second-background");

		listPossible = new ListView();
		listPossible.setCellFactory(cfAvailable);
		listPossible.itemsProperty().bind(getSkinnable().availableProperty());
		listPossible.setPlaceholder(phAvailable);
		listPossible.setStyle(getSkinnable().getAvailableStyle());
		listPossible.setMaxHeight(Double.MAX_VALUE);

		listSelected = new ListView();
		listSelected.setCellFactory(cfSelected);
		listSelected.itemsProperty().bind(getSkinnable().selectedProperty());
		listSelected.setPlaceholder(phSelected);
		listSelected.setStyle(getSkinnable().getSelectedStyle());
		listSelected.setMaxHeight(Double.MAX_VALUE);

		lblAvailable = new Label(" "+getSkinnable().getAvailableHeading());
		lblSelected  = new Label(" "+getSkinnable().getSelectedHeading());
		lblAvailable.getStyleClass().addAll("text-small-subheader","list-heading");
		lblSelected.getStyleClass().addAll("text-small-subheader","list-heading");

		listPossible.setMaxHeight(Double.MAX_VALUE);
	}

	//-------------------------------------------------------------------
	private void initLayout() {
		columns = new HBox();
		columns.setStyle("-fx-spacing: 1em;");
		getChildren().add(columns);
		columns.getChildren().clear();

		Region buf = new Region();
		buf.setMaxWidth(Double.MAX_VALUE);
		line1Select = new HBox(buf,btnAdd,btnDel);
//		line1Select.setStyle("-fx-background-color:pink");
		line1Select.prefWidthProperty().bind(listSelected.widthProperty());
		HBox.setHgrow(buf, Priority.SOMETIMES);
		if (getSkinnable().getSelectedListHead()!=null)
			line1Select.getChildren().add(0,getSkinnable().getSelectedListHead());

		col1 = new VBox(listPossible);
		col2 = new VBox(line1Select,listSelected);
		VBox.setVgrow(listPossible, Priority.ALWAYS);
		VBox.setVgrow(listSelected, Priority.ALWAYS);
		col1.setFillWidth(false);
		col2.setStyle(COL2_BASESTYLE);
//		col1.setStyle("-fx-min-width: 23em");
//		col1.setStyle("-fx-max-width: 25em");
//		col2.setStyle("-fx-min-width: 22em");
//		col2.setStyle("-fx-max-width: 23em");
//		if (ResponsiveControlManager.getCurrentMode()==WindowMode.MINIMAL) {
//			col2.setStyle("-fx-min-width: 20em");
//			col2.setStyle("-fx-max-width: 23em");
//		}
		HBox.setHgrow(col1,  Priority.SOMETIMES);
		HBox.setHgrow(col2,  Priority.SOMETIMES);
//		VBox.setVgrow(listPossible, Priority.ALWAYS);
//		VBox.setVgrow(listSelected, Priority.ALWAYS);
		columns.getChildren().addAll(col1, col2);
		columns.setFillHeight(true);

		if (getSkinnable().getShowHeadings()) {
			VBox.setMargin(lblAvailable, new Insets(0,0,20,0));
			VBox.setMargin(lblSelected, new Insets(0,0,20,0));
			col1.getChildren().add(0,lblAvailable);
			col2.getChildren().add(0,lblSelected);
		}
		if (getSkinnable().filterNodeProperty().get()!=null)
			col1.getChildren().add(col1.getChildren().size()-1, getSkinnable().getFilterNode());
		if (getSkinnable().selectedListPostNodeProperty().get()!=null)
			col2.getChildren().add(getSkinnable().getSelectedListPostNode());
	}

	//-------------------------------------------------------------------
	private void initInteractivity() {

//		getSkinnable().availableCellFactoryProperty().addListener( (ov,o,n) -> listPossible.setCellFactory(n));
//		getSkinnable().selectedCellFactoryProperty().addListener( (ov,o,n) -> listSelected.setCellFactory(n));

		getSkinnable().showHeadingsProperty().addListener( (ov,o,n) -> initLayout());

		getSkinnable().availableHeadingProperty().addListener( (ov,o,n) -> lblAvailable.setText(" "+n));
		getSkinnable().selectedHeadingProperty().addListener( (ov,o,n) -> lblSelected.setText(" "+n));
		getSkinnable().availableStyleProperty().addListener( (ov,o,n) -> listPossible.setStyle(n));
		getSkinnable().selectedStyleProperty().addListener( (ov,o,n) -> listSelected.setStyle(n));
		getSkinnable().filterNodeProperty().addListener( (ov,o,n) -> {
			if (o!=null) col1.getChildren().remove(o);
			if (n!=null) col1.getChildren().add(col1.getChildren().size()-1,n);
		});
		getSkinnable().selectedListHeadProperty().addListener( (ov,o,n) -> {
			if (o!=null) line1Select.getChildren().remove(o);
			if (n!=null) line1Select.getChildren().add(0, n);
		});


		phAvailable.textProperty().bind(getSkinnable().availablePlaceholderProperty());
		phSelected.textProperty().bind(getSkinnable().selectedPlaceholderProperty());

		listPossible.getSelectionModel().selectedItemProperty().addListener( (ov,o,n) -> getSkinnable().setShowHelpFor(n));
		listSelected.getSelectionModel().selectedItemProperty().addListener( (ov,o,n) -> {
			btnDel.setDisable(n==null || !getSkinnable().getController().canBeDeselected(n).get());
			getSkinnable().setShowHelpFor((n!=null)?n.getModifyable():null);
		});
		listPossible.setOnDragOver(ev -> dragOverAvailable(ev));
		listSelected.setOnDragOver(ev -> dragOverSelected(ev));
		listPossible.setOnDragDropped(ev -> dragDroppedAvailable(ev));
		listSelected.setOnDragDropped(ev -> dragDroppedSelected(ev));

		btnAdd.setOnAction(ev -> onAddClicked());
		btnDel.setOnAction(ev -> onDeleteClicked(listSelected.getSelectionModel().getSelectedItem()));
	}

	//-------------------------------------------------------------------
    private static  ListCell createDefaultCellImpl() {
        return new ListCell() {
            @Override public void updateItem(T item, boolean empty) {
                super.updateItem(item, empty);

                if (empty) {
                    setText(null);
                    setGraphic(null);
                } else if (item instanceof Node) {
                    setText(null);
                    Node currentNode = getGraphic();
                    Node newNode = (Node) item;
                    if (currentNode == null || ! currentNode.equals(newNode)) {
                        setGraphic(newNode);
                    }
                } else {
                    /**
                     * This label is used if the item associated with this cell is to be
                     * represented as a String. While we will lazily instantiate it
                     * we never clear it, being more afraid of object churn than a minor
                     * "leak" (which will not become a "major" leak).
                     */
                    setText(item == null ? "null" : item.toString());
                    setGraphic(null);
                }
            }
        };
    }

	// -------------------------------------------------------------------
	protected void userSelects(T toSelect) {
		logger.log(Level.WARNING, "userSelects(" + toSelect + ")");
		ComplexDataItemController ctrl = getSkinnable().getController();
		Possible possible = ctrl.canBeSelected(toSelect);
		logger.log(Level.DEBUG, "possible = "+possible);
		if (possible.get()) {
			// Is there a need for a selection
			logger.log(Level.DEBUG, "ctrl = " + ctrl);
			if (!ctrl.getChoicesToDecide(toSelect).isEmpty()) {
				// Yes, user must choose
				List options = ctrl.getChoicesToDecide(toSelect);
				logger.log(Level.DEBUG, "called getChoicesToDecide returns {0} choices", options.size());
				if (getSkinnable().getOptionCallback() != null) {
					Platform.runLater(() -> {
						logger.log(Level.DEBUG, "call getOptionCallback");
						Decision[] decisions = getSkinnable().getOptionCallback().apply(toSelect, options);
						if (decisions != null) {
							logger.log(Level.WARNING, "call select(option, decision[{0}]) on {1}", decisions.length, ctrl.getClass().getSimpleName());
							logger.log(Level.WARNING, "  decisions = {0}", Arrays.toString(decisions));
							OperationResult res = ctrl.select(toSelect, decisions);
							if (res.wasSuccessful()) {
								logger.log(Level.INFO, "Selecting {0} with options was successful", toSelect);
							} else {
								logger.log(Level.WARNING, "Selecting {0} with options failed: {1}", toSelect, res.getError());
								AlertManager.showAlertAndCall(javafx.scene.control.Alert.AlertType.ERROR, "Failed adding", res.getError());
							}
						}
					});
				} else {
					logger.log(Level.ERROR, "Item {0} has choices to make, but no GUI callback defined", toSelect.getId());
				}
			} else {
				// No
				logger.log(Level.DEBUG, "call select(option)");
				OperationResult res = ctrl.select(toSelect);
				if (res.wasSuccessful()) {
					logger.log(Level.INFO, "Selecting {0} was successful", toSelect);
					listSelected.getItems().setAll(ctrl.getSelected());
				} else {
					logger.log(Level.WARNING, "Selecting {0} failed: {1}", toSelect, res.getError());
					AlertManager.showAlertAndCall(javafx.scene.control.Alert.AlertType.ERROR, "Failed adding", res.getError());
				}
			}
		} else {
			logger.log(Level.DEBUG, "can not be Selected(" + toSelect + "): " + possible.getI18NKey());

    		FlexibleApplication.getInstance().showAlertAndCall(AlertType.NOTIFICATION, "Selection failed", possible.toString());
    	}
    }

	//-------------------------------------------------------------------
	private void mouseClickedAvailable(ListCell cell, MouseEvent ev) {
		if (ev.getClickCount()==2) {
			userSelects(cell.getItem());
			getSkinnable().refresh();
		}
	}

	//-------------------------------------------------------------------
	private void mouseClickedSelected(ListCell cell, MouseEvent ev) {
		if (ev.getClickCount()==2) {
			if (getSkinnable().getController().canBeDeselected((V)cell.getItem()).get()) {
				getSkinnable().getController().deselect((V)cell.getItem());
			}
		}
	}

	//-------------------------------------------------------------------
	protected void dragDetectedAvailable(ListCell cell, MouseEvent event) {
		T data = cell.getItem();
		if (data==null)
			return;
		logger.log(Level.DEBUG, "check: can be selected");
		if (!getSkinnable().getController().canBeSelected(data).get())
			return;

		Node source = (Node) event.getSource();

		/* drag was detected, start a drag-and-drop gesture*/
        /* allow any transfer mode */
        Dragboard db = source.startDragAndDrop(TransferMode.ANY);

        /* Put a string on a dragboard */
        ClipboardContent content = new ClipboardContent();
        String id = data.getTypeString()+":"+data.getId();
        content.putString(id);
        db.setContent(content);

        /* Drag image */
        WritableImage snapshot = source.snapshot(new SnapshotParameters(), null);
        db.setDragView(snapshot);

        event.consume();
	}

	//-------------------------------------------------------------------
	protected void dragDetectedSelected(ListCell cell, MouseEvent event) {
		V data = cell.getItem();
		if (data==null)
			return;
		if (!getSkinnable().getController().canBeDeselected(data).get())
			return;

		Node source = (Node) event.getSource();

		/* drag was detected, start a drag-and-drop gesture*/
        /* allow any transfer mode */
        Dragboard db = source.startDragAndDrop(TransferMode.ANY);

        /* Put a string on a dragboard */
        ClipboardContent content = new ClipboardContent();
        String id = data.getModifyable().getTypeString()+":"+data.getModifyable().getId();
        content.putString(id);
        db.setContent(content);

        /* Drag image */
        WritableImage snapshot = source.snapshot(new SnapshotParameters(), null);
        db.setDragView(snapshot);

        event.consume();
	}

	//-------------------------------------------------------------------
	private void dragOverSelected(DragEvent event) {
		Node target = (Node) event.getSource();
		if (event.getGestureSource() != target && event.getDragboard().hasString()) {
            String enhanceID = event.getDragboard().getString();
            // Find from available
            T toSelect = null;
            for (T tmp : getSkinnable().getController().getAvailable()) {
            	String cmp = tmp.getTypeString()+":"+tmp.getId();
            	if (enhanceID.equals(cmp)) {
            		toSelect = tmp;
            		break;
            	}
            }
            if (toSelect!=null && getSkinnable().getController().canBeSelected(toSelect).get()) {
            	/* allow for both copying and moving, whatever user chooses */
            	event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
            }
        }
	}

	//-------------------------------------------------------------------
	private void dragDroppedSelected(DragEvent event) {
       /* if there is a string data on dragboard, read it and use it */
        Dragboard db = event.getDragboard();
        boolean success = false;
        if (db.hasString()) {
            String enhanceID = db.getString();
            // Find from available
            T toSelect = null;
            for (T tmp : getSkinnable().getController().getAvailable()) {
            	String cmp = tmp.getTypeString()+":"+tmp.getId();
            	if (enhanceID.equals(cmp)) {
            		toSelect = tmp;
            		break;
            	}
            }
            if (toSelect!=null) {
            	userSelects(toSelect);
            	getSkinnable().refresh();
            }
        }
        /* let the source know whether the string was successfully
         * transferred and used */
        event.setDropCompleted(success);

        event.consume();
	}

	//-------------------------------------------------------------------
	private void dragOverAvailable(DragEvent event) {
		Node target = (Node) event.getSource();
		if (event.getGestureSource() != target && event.getDragboard().hasString()) {
            /* allow for both copying and moving, whatever user chooses */
            event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
        }
	}

	//-------------------------------------------------------------------
	private void dragDroppedAvailable(DragEvent event) {
       /* if there is a string data on dragboard, read it and use it */
        Dragboard db = event.getDragboard();
        boolean success = false;
        if (db.hasString()) {
            String enhanceID = db.getString();
            // Find from available
            V toSelect = null;
            for (V tmp : getSkinnable().getController().getSelected()) {
            	String cmp = tmp.getModifyable().getTypeString()+":"+tmp.getModifyable().getId();
            	if (enhanceID.equals(cmp)) {
            		toSelect = tmp;
            		break;
            	}
            }
            if (toSelect!=null) {
            	getSkinnable().getController().deselect(toSelect);
            	getSkinnable().refresh();
            }
        }
        /* let the source know whether the string was successfully
         * transferred and used */
        event.setDropCompleted(success);

        event.consume();
	}

	//-------------------------------------------------------------------
	public void setResponsiveMode(WindowMode value) {
		logger.log(Level.TRACE,"setResponsiveMode({0})", value);
		if (value==WindowMode.MINIMAL) {
			// Column 1 should not be permanently visible
			columns.getChildren().remove(col1);
			col2.setStyle("-fx-min-width: 20em; -fx-max-width: 23em");
			btnAdd.setVisible(true);
		} else {
			btnAdd.setVisible(false);
			// Make column 1 visible if necessary
			col2.setStyle(COL2_BASESTYLE);
			if (!columns.getChildren().contains(col1)) {
				columns.getChildren().add(0,col1);
			}
		}
		columns.requestLayout();
	}

	//-------------------------------------------------------------------
	private void onAddClicked() {
    	logger.log(Level.WARNING, "Add button clicked");

    	GenericDescriptionVBox descr = new GenericDescriptionVBox(getSkinnable().getRequirementResolver(), getSkinnable().getModificationResolver());
    	descr.setStyle("-fx-min-height: 5em");
    	VBox pane = new VBox(10,col1, descr);
    	VBox.setVgrow(col1, Priority.ALWAYS);
    	VBox.setVgrow(descr, Priority.SOMETIMES);
    	listPossible.getSelectionModel().selectedItemProperty().addListener( (ov,o,n) -> {
    		descr.setData(n);
    	});

    	ManagedDialog dialog = new ManagedDialog(
    			ResourceI18N.get(RPGFrameworkJFXConstants.UI, "label.selectOne"),
    			pane, CloseType.CANCEL, CloseType.OK);
    	CloseType closed = FlexibleApplication.getInstance().showAndWait(dialog);
    	logger.log(Level.WARNING, "Closed with "+closed);
    	if (closed==CloseType.OK) {
    		T selected = listPossible.getSelectionModel().getSelectedItem();
    		if (selected!=null) {
    			userSelects(selected);
    		}
    	}
	}

	//-------------------------------------------------------------------
    private void onDeleteClicked(V selected) {
    	logger.log(Level.DEBUG, "Delete button clicked for "+selected);
		boolean success = getSkinnable().getController().deselect(selected);
		if (success) {
			listSelected.getItems().remove(selected);
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy