/*
 * Copyright (C) 2018-2020, 2022, 2023 Andrew Gegg
 *
 *	This file is part of the Garden Notebook application
 *
 * The Garden Notebook application is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
	Change log
	2.5.0   Support Sales
    2.6.0   Prevent illegal ancestor/descendant links
            Allow leaf items to be disconnected from ancestors
    2.6.1   Set date column width to fit data
    2.9.6	When a Diary entry is added/changed, make sure updated comments are shown
    3.0.1	Show icons in the table
    3.0.5	Changes to use the DiaryBean Factory version
	3.1.0	Pass extra info when adding descendants to check dates
			Disallow addition of certain descendants
 */

package uk.co.gardennotebook;

import javafx.beans.property.SimpleStringProperty;
import javafx.scene.layout.Region;
import javafx.scene.shape.SVGPath;
import uk.co.gardennotebook.fxbean.AfflictionBean;
import uk.co.gardennotebook.fxbean.AfflictionEventBean;
import uk.co.gardennotebook.fxbean.HusbandryClassBean;
import uk.co.gardennotebook.fxbean.PurchaseBean;
import uk.co.gardennotebook.fxbean.PlantVarietyBean;
import uk.co.gardennotebook.fxbean.GroundworkActivityBean;
//import uk.co.gardennotebook.fxbean.WeatherConditionBean;
//import uk.co.gardennotebook.fxbean.RetailerBean;
import uk.co.gardennotebook.fxbean.GroundworkBean;
import uk.co.gardennotebook.fxbean.HusbandryBean;
import uk.co.gardennotebook.fxbean.PlantSpeciesBean;
//import uk.co.gardennotebook.fxbean.WildlifeSpeciesBean;
import uk.co.gardennotebook.fxbean.PurchaseItemBean;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.BiConsumer;
//import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Control;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.WindowEvent;
import uk.co.gardennotebook.spi.NotebookEntryType;
import uk.co.gardennotebook.util.SimpleMoney;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.EntryMessage;
import uk.co.gardennotebook.fxbean.SaleBean;
import uk.co.gardennotebook.fxbean.SaleItemBean;
import uk.co.gardennotebook.spi.GNDBException;
import uk.co.gardennotebook.util.StoryLineTree;

/**
 * Controller for the Storyline (History) display tab
 *
 * @author Andy Gegg
*	@version	3.1.0
*	@since	1.0
 */
final class StoryLineTab extends AnchorPane implements INotebookLoadable
{

	private static final Logger LOGGER = LogManager.getLogger();

	private static final SVGPath afflictionGraphic = DiaryIcons.afflictionGraphic;
	private static final SVGPath groundworkGraphic = DiaryIcons.groundworkGraphic;
	private static final SVGPath husbandryGraphic = DiaryIcons.husbandryGraphic;
	private static final SVGPath journalGraphic = DiaryIcons.journalGraphic;
	private static final SVGPath purchaseGraphic = DiaryIcons.purchaseGraphic;
	private static final SVGPath saleGraphic = DiaryIcons.saleGraphic;
	private static final SVGPath weatherGraphic = DiaryIcons.weatherGraphic;
	private static final SVGPath wildlifeGraphic = DiaryIcons.wildlifeGraphic;

	@FXML
	private MenuButton btnAdd;
	@FXML
	private MenuItem mnuHusbandry;
	@FXML
	private MenuItem mnuWeather;
	@FXML
	private MenuItem ctxmnuDelete;
	@FXML
	private MenuItem ctxmnuTopHistory;
	@FXML
	private MenuItem ctxmnuAncestors;
	@FXML
	private MenuItem ctxmnuDescendants;
	@FXML
	private MenuItem ctxmnuNewDescendant;
	@FXML
	private MenuItem ctxmnuNewHusbandry;
	@FXML
	private MenuItem ctxmnuNewSale;
	@FXML
	private MenuItem ctxmnuDropLeaf;
	@FXML
	private Button btnChange;
	@FXML
	private TreeTableView<DiaryBean> diaryTree;
	@FXML
	private TreeTableColumn<DiaryBean, DiaryBean> diaryColDate;
	@FXML
	private TreeTableColumn<DiaryBean, DiaryBean> colMainTitle;
	@FXML
	private TreeTableColumn<DiaryBean, DiaryBean> colSubTitle;
	@FXML
	private TreeTableColumn<DiaryBean, DiaryBean> colVariety;
	@FXML
	private TreeTableColumn<DiaryBean, DiaryBean> colDetail;
	@FXML
	private TreeTableColumn<DiaryBean, DiaryBean> colComment;

	@FXML
	private ResourceBundle resources;

	private Consumer<Node> loadSplit;
	private Consumer<Node> clearSplit;
	private BiConsumer<String, Node> loadTab;
	private Consumer<Node> clearTab;
	
	//	an observable property that the caller can monitor when a new item is being created
	private SimpleObjectProperty<DiaryBean> newBean;

	private StoryLineTree<DiaryBean> theHistory = null;
	
	// the width of the TableView, less the scroll bar
	private double diaryTableContentWidth;

	StoryLineTab()
	{
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/StoryLineTab.fxml"),
			ResourceBundle.getBundle("notebook") );
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }

	}

	/*
	 * Initializes the controller class.
	 */
	@FXML
	public void initialize()
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("initialize()");

		diaryColDate.setCellValueFactory(cdf -> new SimpleObjectProperty<>(cdf.getValue().getValue()));
		diaryColDate.setCellFactory(x -> new DateTableCell());
		colMainTitle.setCellValueFactory(cdf -> new SimpleObjectProperty<>(cdf.getValue().getValue()));
		colMainTitle.setCellFactory(x -> new TextTableCell());
		colSubTitle.setCellValueFactory(cdf -> new SimpleObjectProperty<>(cdf.getValue().getValue()));
		colSubTitle.setCellFactory(x -> new TextTableCell());
		colVariety.setCellValueFactory(cdf -> new SimpleObjectProperty<>(cdf.getValue().getValue()));
		colVariety.setCellFactory(x -> new TextTableCell());
		colDetail.setCellValueFactory(cdf -> new SimpleObjectProperty<>(cdf.getValue().getValue()));
		colDetail.setCellFactory(x -> new TextTableCell());
		colComment.setCellValueFactory(cdf -> new SimpleObjectProperty<>(cdf.getValue().getValue()));
		colComment.setCellFactory(x -> new CommentTableCell());

		// set a custom resize policy so that the Comments column takes up all available space on the right
		diaryTree.setColumnResizePolicy(NotebookResizer.using(diaryTree));

		
		// only allow change and delete if there is a row selected
		btnChange.disableProperty().bind(diaryTree.getSelectionModel().selectedItemProperty().isNull());
		LOGGER.traceExit(log4jEntryMsg);
	}	

	void setHistory(StoryLineTree<DiaryBean> history)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("setHistory()");
		theHistory = history;
		StoryLineTree<DiaryBean> currNode = history;
		TreeItem<DiaryBean> root = new TreeItem<>();
		buildTree(root, currNode);
		diaryTree.setRoot(root);
		root.setExpanded(true);
        
        //  2.6.1
        final int maxDepth = getTreeDepth(root, 1);
        LOGGER.debug("setHistory(): maxDepth: {}", maxDepth);
        double nameWidth = new Text(diaryColDate.getText()).getLayoutBounds().getWidth();
        double dateWidth = new Text(LocalDate.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT))).getLayoutBounds().getWidth();
        dateWidth += (maxDepth-1) * (dateWidth/4);
        if (dateWidth > nameWidth)
            nameWidth = dateWidth;
        
        diaryColDate.setPrefWidth(nameWidth+10);

		//	3.0.5	It's not obvious why the next 2 lines were here and they throw out the sort with the DiaryBean factory version
		//			(Purchases are pushed to the END of the tree instead of the TOP - and it's not clear why)
//		diaryTree.getSortOrder().add(diaryColDate);
//		diaryTree.sort();
		LOGGER.traceExit(log4jEntryMsg);
	}
	
	private void buildTree(TreeItem<DiaryBean> root, StoryLineTree<DiaryBean> node)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("buildTree(): root: {}, node: {}", root, node);
		if (node == null)
		{
			LOGGER.debug("buildTree(): node is null");
			return;
		}
		if (root == null)
		{
			LOGGER.debug("buildTree(): root is null");
			return;
		}
        //  2.6.0
		if (node.isEmpty())
		{
			LOGGER.debug("buildTree(): node is empty");
			return;
		}
		if (node.getValue().getItemType() == NotebookEntryType.PURCHASEITEM)
		{
			PurchaseItemBean pib = (PurchaseItemBean)(node.getValue().getItem());
			PurchaseBean bean = pib.getPurchase();
			DiaryBean db = DiaryBean.from(bean);
			TreeItem<DiaryBean> purNode = new TreeItem<>(db);
			root.getChildren().add(purNode);
		}
		if (node.getValue().getItemType() == NotebookEntryType.SALEITEM)
		{
			SaleItemBean pib = (SaleItemBean)(node.getValue().getItem());
			SaleBean bean = pib.getSale();
//			DiaryBean db = new DiaryBean(bean);
			DiaryBean db = DiaryBean.from(bean);
			TreeItem<DiaryBean> purNode = new TreeItem<>(db);
			root.getChildren().add(purNode);
		}
		TreeItem<DiaryBean> exNode = new TreeItem<>(node.getValue());
		root.getChildren().add(exNode);
		for (StoryLineTree<DiaryBean> kid : node.getChildren())
		{
			buildTree(exNode, kid);
		}
		exNode.setExpanded(true);
		LOGGER.traceExit(log4jEntryMsg);
	}

    /*
     * Get the maximum depth of the tree.
     * This is used to set the width of the date column.
     * 
     * @return  the tree depth
     * 
     * @since   2.6.1
     */
    private int getTreeDepth(TreeItem<DiaryBean> node, int currDepth)
    {
        int maxDepth = currDepth;
        if (node != null)
        {
            for (TreeItem<DiaryBean> child : node.getChildren())
            {
                int newDepth = getTreeDepth(child, currDepth+1);
                if (newDepth > maxDepth)
                    maxDepth = newDepth;
            }
        }
        return maxDepth;
    }


	@FXML
	private void btnChangeOnAction(ActionEvent event) 
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("btnChangeOnAction()");
		ctxmnuChangeOnAction(event);
		LOGGER.traceExit(log4jEntryMsg);
	}


	@FXML
	private void ctxmnuTopOnAction(WindowEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuTopOnAction()");
		DiaryBean ixBean = (DiaryBean)(diaryTree.getSelectionModel().getSelectedItem().getValue());
		if (ixBean == null)
		{
			LOGGER.debug("no item selected in Table");
			return;
		}

		ctxmnuTopHistory.setDisable(ixBean.getItemType() == NotebookEntryType.PURCHASE ||
									ixBean.getItemType() == NotebookEntryType.SALE);
        boolean hasAncestor = false;
        boolean hasDescendant = false;
		try {
            hasAncestor = ixBean.hasAncestor();
            hasDescendant = ixBean.hasDescendant();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}

        ctxmnuAncestors.setDisable(!hasAncestor);
        ctxmnuDescendants.setDisable(!hasDescendant);
        
        ctxmnuNewDescendant.setDisable(ixBean.getItemType() == NotebookEntryType.SALEITEM);
        ctxmnuNewSale.setDisable(ixBean.getItemType() == NotebookEntryType.PURCHASE ||
                                 ixBean.getItemType() == NotebookEntryType.PURCHASEITEM );

		final boolean noPlants = ixBean.getItemType() == NotebookEntryType.PURCHASE ||
				(ixBean.getItemType() == NotebookEntryType.PURCHASEITEM && !((PurchaseItemBean)(ixBean.getItem())).hasPlantSpecies()) ||
				(ixBean.getItemType() == NotebookEntryType.AFFLICTIONEVENT && !((AfflictionEventBean)(ixBean.getItem())).hasPlantSpecies()) ||
				(ixBean.getItemType() == NotebookEntryType.GROUNDWORK && !((GroundworkBean)(ixBean.getItem())).hasPlantSpecies());
		ctxmnuNewHusbandry.setDisable(noPlants);
		ctxmnuNewSale.setDisable(noPlants);

        ctxmnuDropLeaf.setDisable(ixBean.getItemType() == NotebookEntryType.PURCHASE ||
                                 ixBean.getItemType() == NotebookEntryType.PURCHASEITEM ||
                                 hasDescendant  || !hasAncestor );

		LOGGER.traceExit(log4jEntryMsg);
	}

	@FXML
	private void ctxmnuChangeOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuChangeOnAction()");
		DiaryBean selBean = diaryTree.getSelectionModel().getSelectedItem().getValue();
		if (selBean == null)
		{
			return;
		}
		switch (selBean.getItemType())
		{
			case HUSBANDRY -> {
				LOGGER.debug("StoryLineTab: ctxmnuChangeOnAction: husbandry: {}", selBean);
				HusbandryEditor tabCon = new HusbandryEditor((HusbandryBean) (selBean.getItem()));
				loadTab.accept(resources.getString("tab.husbandry"), tabCon);
				editorTabSetUp(tabCon);
			}
			case PURCHASE -> {
				LOGGER.debug("StoryLineTab: ctxmnuChangeOnAction: purchase: {}", selBean);
				PurchaseEditor tabCon = new PurchaseEditor((PurchaseBean) (selBean.getItem()));
				loadTab.accept(resources.getString("tab.purchase"), tabCon);
				editorTabSetUp(tabCon);
			}
			case PURCHASEITEM -> {
				LOGGER.debug("StoryLineTab: ctxmnuChangeOnAction: purchaseItem: {}", selBean);
				PurchaseItemBean pur = (PurchaseItemBean) (selBean.getItem());
				PurchaseEditor tabCon = new PurchaseEditor(pur.getPurchase());
				loadTab.accept(resources.getString("tab.purchase"), tabCon);
				editorTabSetUp(tabCon);
			}
			case SALE -> {
				LOGGER.debug("StoryLineTab: ctxmnuChangeOnAction: sale: {}", selBean);
				SaleEditor tabCon = new SaleEditor((SaleBean) (selBean.getItem()));
				loadTab.accept(resources.getString("tab.sales"), tabCon);
				editorTabSetUp(tabCon);
			}
			case SALEITEM -> {
				LOGGER.debug("StoryLineTab: ctxmnuChangeOnAction: saleItem: {}", selBean);
				SaleEditor tabCon = new SaleEditor(((SaleItemBean) (selBean.getItem())).getSale());
				loadTab.accept(resources.getString("tab.sales"), tabCon);
				editorTabSetUp(tabCon);
			}
			case GROUNDWORK -> {
				LOGGER.debug("StoryLineTab: ctxmnuChangeOnAction: groundwork: {}", selBean);
				GroundworkEditor tabCon = new GroundworkEditor((GroundworkBean) (selBean.getItem()));
				loadTab.accept(resources.getString("tab.groundwork"), tabCon);
				editorTabSetUp(tabCon);
			}
			case AFFLICTIONEVENT -> {
				LOGGER.debug("StoryLineTab: ctxmnuChangeOnAction: affliction event: {}", selBean);
				AfflictionEventEditor tabCon = new AfflictionEventEditor((AfflictionEventBean) (selBean.getItem()));
				loadTab.accept(resources.getString("tab.affliction"), tabCon);
				editorTabSetUp(tabCon);
			}
		}
		LOGGER.traceExit(log4jEntryMsg);
	}

	@FXML
	private void ctxmnuAncestorsOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuAncestorsOnAction()");
		DiaryBean ixBean = diaryTree.getSelectionModel().getSelectedItem().getValue();
		if (ixBean == null)
		{
			return;
		}
		boolean hasAncestor = false;
		try {
			hasAncestor = ixBean.hasAncestor();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		if (!hasAncestor)
		{
			return;
		}
		StoryLineTree<DiaryBean> history = StoryLineTree.<DiaryBean>emptyTree();
		try {
			history = ixBean.getAncestors();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		StoryLineTab tabCon = new StoryLineTab();
		loadTab.accept(resources.getString("tab.ancestors"), tabCon);
		tabCon.setHistory(history);
		LOGGER.traceExit(log4jEntryMsg);
	}	//	ctxmnuAncestorsOnAction()
	
	@FXML
	private void ctxmnuDescendantsOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuDescendantsOnAction()");
		DiaryBean ixBean = diaryTree.getSelectionModel().getSelectedItem().getValue();
		LOGGER.debug("ctxmnuDescendantsOnAction(): ixBean: {}", ixBean);
		if (ixBean == null)
		{
			return;
		}
		boolean hasDescendant = false;
		try {
			hasDescendant = ixBean.hasDescendant();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		if (!hasDescendant)
		{
			LOGGER.debug("ctxmnuDescendantsOnAction(): ixBean: no descendant!");
			return;
		}
		LOGGER.debug("ctxmnuDescendantsOnAction(): about to fetch descendants for {}", ixBean);
		StoryLineTree<DiaryBean> history = StoryLineTree.<DiaryBean>emptyTree();
		try {
			history = ixBean.getDescendants();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		LOGGER.debug("ctxmnuDescendantsOnAction(): after fetching descendants: history: {}", history);
		StoryLineTab tabCon = new StoryLineTab();
		loadTab.accept(resources.getString("tab.descendants"), tabCon);
		tabCon.setHistory(history);
		LOGGER.traceExit(log4jEntryMsg);
	}	//	ctxmnuDescendantsOnAction()
	
	@FXML
	private void ctxmnuDescendantHusbandryOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuNewHusbandryOnAction()");
		final TreeItem<DiaryBean> selNode = diaryTree.getSelectionModel().getSelectedItem();
		final DiaryBean ixBean = selNode.getValue();
		if (ixBean == null)
		{
			return;
		}
		
//		PlantSpeciesBean psBean = null;
//		PlantVarietyBean pvBean = null;
//		switch (ixBean.getItemType())
//		{
//			case HUSBANDRY -> {
//				psBean = ((HusbandryBean) (ixBean.getItem())).getPlantSpecies();
//				pvBean = ((HusbandryBean) (ixBean.getItem())).getPlantVariety();
//			}
//			case PURCHASEITEM -> {
//				PurchaseItemBean piBean = ((PurchaseItemBean) (ixBean.getItem()));
//				if (piBean.getProduct().getProductCategory().isPlantLike())
//				{
//					psBean = piBean.getProduct().getPlantSpecies();
//					pvBean = piBean.getProduct().getPlantVariety();
//				}
//			}
//			case GROUNDWORK -> {
//				psBean = ((GroundworkBean) (ixBean.getItem())).getPlantSpecies();
//				pvBean = ((GroundworkBean) (ixBean.getItem())).getPlantVariety();
//			}
//			case AFFLICTIONEVENT -> {
//				psBean = ((AfflictionEventBean) (ixBean.getItem())).getPlantSpecies();
//				pvBean = ((AfflictionEventBean) (ixBean.getItem())).getPlantVariety();
//			}
//			default -> {
//				LOGGER.debug("ctxmnuDescendantHusbandryOnAction(): illegal parent type: {}", ixBean);
//				return;
//			}
//		}
//		final HusbandryEditor tabCon = new HusbandryEditor(psBean, pvBean);

		//	For a Husbandry, there must be a PlantSpecies to inherit
		switch (ixBean.getItemType())
		{
			case PURCHASE -> {
				return;
			}
			case PURCHASEITEM -> {if ( !((PurchaseItemBean)(ixBean.getItem())).getProductCategory().isPlantLike() )
			{
				return;
			}}
			case GROUNDWORK -> {
				if (!((GroundworkBean) (ixBean.getItem())).hasPlantSpecies())
					return;
			}
			case AFFLICTIONEVENT -> {
				if (!((AfflictionEventBean) (ixBean.getItem())).hasPlantSpecies())
					return;
			}
			default ->	{}
		}

		final HusbandryEditor tabCon = new HusbandryEditor(ixBean.getItem(), true);
		loadTab.accept(resources.getString("tab.husbandry"), tabCon);
		editorTabSetUp(tabCon);
		LOGGER.debug("ctxmnuDescendantHusbandryOnAction(): after pop-up");
		tabCon.newBeanProperty().addListener((obs, oldVal, newVal) -> {
				//			diaryTree..add(new DiaryBean(newVal));
				DiaryBean db = DiaryBean.from(newVal);
				TreeItem<DiaryBean> purNode = new TreeItem<>(db);
				selNode.getChildren().add(purNode);
				LOGGER.debug("ctxmnuDescendantHusbandryOnAction(): newBean listener: newVal: {}", newVal);
				try {
					switch (ixBean.getItemType())
					{
						case HUSBANDRY -> newVal.setAncestor((HusbandryBean) (ixBean.getItem()));
						case PURCHASEITEM -> newVal.setAncestor((PurchaseItemBean) (ixBean.getItem()));
						case GROUNDWORK -> newVal.setAncestor((GroundworkBean) (ixBean.getItem()));
						case AFFLICTIONEVENT -> newVal.setAncestor((AfflictionEventBean) (ixBean.getItem()));
						default -> {
							LOGGER.debug("ctxmnuDescendantHusbandryOnAction(): illegal parent type: {}", ixBean);
							return;
						}
					}
				} catch (GNDBException ex) {
					PanicHandler.panic(ex);
				}
				newBeanProperty().setValue(db);
		});
		LOGGER.traceExit(log4jEntryMsg);
	}	//	ctxmnuNewHusbandryOnAction()

	@FXML
	private void ctxmnuDescendantGroundworkOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuNewGroundworkOnAction()");
		final TreeItem<DiaryBean> selNode = diaryTree.getSelectionModel().getSelectedItem();
		final DiaryBean ixBean = selNode.getValue();
		if (ixBean == null)
		{
			return;
		}
		
//		PlantSpeciesBean psBean = null;
//		PlantVarietyBean pvBean = null;
//		switch (ixBean.getItemType())
//		{
//			case HUSBANDRY -> {
//				psBean = ((HusbandryBean) (ixBean.getItem())).getPlantSpecies();
//				pvBean = ((HusbandryBean) (ixBean.getItem())).getPlantVariety();
//			}
//			case PURCHASEITEM -> {
//				PurchaseItemBean piBean = ((PurchaseItemBean) (ixBean.getItem()));
//				if (piBean.getProduct().getProductCategory().isPlantLike())
//				{
//					psBean = piBean.getProduct().getPlantSpecies();
//					pvBean = piBean.getProduct().getPlantVariety();
//				}
//			}
//			case GROUNDWORK -> {
//				psBean = ((GroundworkBean) (ixBean.getItem())).getPlantSpecies();
//				pvBean = ((GroundworkBean) (ixBean.getItem())).getPlantVariety();
//			}
//			case AFFLICTIONEVENT -> {
//				psBean = ((AfflictionEventBean) (ixBean.getItem())).getPlantSpecies();
//				pvBean = ((AfflictionEventBean) (ixBean.getItem())).getPlantVariety();
//			}
//			default -> {
//				LOGGER.debug("ctxmnuDescendantGroundworkOnAction(): illegal parent type: {}", ixBean);
//				return;
//			}
//		}
//		final GroundworkEditor tabCon = new GroundworkEditor(psBean, pvBean);
		final GroundworkEditor tabCon = new GroundworkEditor(ixBean.getItem(), true);
		loadTab.accept(resources.getString("tab.groundwork"), tabCon);
		editorTabSetUp(tabCon);
		LOGGER.debug("ctxmnuDescendantGroundworkOnAction(): after pop-up");
		tabCon.newBeanProperty().addListener((obs, oldVal, newVal) -> {
				DiaryBean db = DiaryBean.from(newVal);
				TreeItem<DiaryBean> purNode = new TreeItem<>(db);
				selNode.getChildren().add(purNode);
				LOGGER.debug("ctxmnuDescendantGroundworkOnAction(): newBean listener: newVal: {}", newVal);
				try {
					switch (ixBean.getItemType())
					{
						case HUSBANDRY -> newVal.setAncestor((HusbandryBean) (ixBean.getItem()));
						case PURCHASEITEM -> newVal.setAncestor((PurchaseItemBean) (ixBean.getItem()));
						case GROUNDWORK -> newVal.setAncestor((GroundworkBean) (ixBean.getItem()));
						case AFFLICTIONEVENT -> newVal.setAncestor((AfflictionEventBean) (ixBean.getItem()));
						default -> {
							LOGGER.debug("ctxmnuDescendantGroundworkOnAction(): illegal parent type: {}", ixBean);
							return;
						}
					}
				} catch (GNDBException ex) {
					PanicHandler.panic(ex);
				}
				newBeanProperty().setValue(db);
		});
		LOGGER.traceExit(log4jEntryMsg);
	}	//	ctxmnuNewGroundworkOnAction()

	@FXML
	private void ctxmnuDescendantAfflictionOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuNewAfflictionOnAction()");
		final TreeItem<DiaryBean> selNode = diaryTree.getSelectionModel().getSelectedItem();
		final DiaryBean ixBean = selNode.getValue();
		if (ixBean == null)
		{
			return;
		}
		
//		PlantSpeciesBean psBean = null;
//		PlantVarietyBean pvBean = null;
//		switch (ixBean.getItemType())
//		{
//			case HUSBANDRY -> {
//				psBean = ((HusbandryBean) (ixBean.getItem())).getPlantSpecies();
//				pvBean = ((HusbandryBean) (ixBean.getItem())).getPlantVariety();
//			}
//			case PURCHASEITEM -> {
//				PurchaseItemBean piBean = ((PurchaseItemBean) (ixBean.getItem()));
//				if (piBean.getProduct().getProductCategory().isPlantLike())
//				{
//					psBean = piBean.getProduct().getPlantSpecies();
//					pvBean = piBean.getProduct().getPlantVariety();
//				}
//			}
//			case GROUNDWORK -> {
//				psBean = ((GroundworkBean) (ixBean.getItem())).getPlantSpecies();
//				pvBean = ((GroundworkBean) (ixBean.getItem())).getPlantVariety();
//			}
//			case AFFLICTIONEVENT -> {
//				psBean = ((AfflictionEventBean) (ixBean.getItem())).getPlantSpecies();
//				pvBean = ((AfflictionEventBean) (ixBean.getItem())).getPlantVariety();
//			}
//			default -> {
//				LOGGER.debug("ctxmnuDescendantAfflictionOnAction(): illegal parent type: {}", ixBean);
//				return;
//			}
//		}
//		final AfflictionEventEditor tabCon = new AfflictionEventEditor(psBean, pvBean);
		final AfflictionEventEditor tabCon = new AfflictionEventEditor(ixBean.getItem(), true);
		loadTab.accept(resources.getString("tab.affliction"), tabCon);
		editorTabSetUp(tabCon);
		LOGGER.debug("ctxmnuDescendantAfflictionOnAction(): after pop-up");
		tabCon.newBeanProperty().addListener((obs, oldVal, newVal) -> {
				DiaryBean db = DiaryBean.from(newVal);
				TreeItem<DiaryBean> purNode = new TreeItem<>(db);
				selNode.getChildren().add(purNode);
				LOGGER.debug("ctxmnuDescendantAfflictionOnAction(): newBean listener: newVal: {}", newVal);
				try {
					switch (ixBean.getItemType())
					{
						case HUSBANDRY -> newVal.setAncestor((HusbandryBean) (ixBean.getItem()));
						case PURCHASEITEM -> newVal.setAncestor((PurchaseItemBean) (ixBean.getItem()));
						case GROUNDWORK -> newVal.setAncestor((GroundworkBean) (ixBean.getItem()));
						case AFFLICTIONEVENT -> newVal.setAncestor((AfflictionEventBean) (ixBean.getItem()));
						default -> {
							LOGGER.debug("ctxmnuDescendantAfflictionOnAction(): illegal parent type: {}", ixBean);
							return;
						}
					}
				} catch (GNDBException ex) {
					PanicHandler.panic(ex);
				}
				newBeanProperty().setValue(db);
		});
		LOGGER.traceExit(log4jEntryMsg);
	}	//	ctxmnuNewAfflictionOnAction()

	@FXML
	private void ctxmnuDescendantSaleOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuDescendantSaleOnAction()");
		final TreeItem<DiaryBean> selNode = diaryTree.getSelectionModel().getSelectedItem();
		final DiaryBean ixBean = selNode.getValue();
		if (ixBean == null)
		{
			return;
		}
        if (ixBean.getItemType() == NotebookEntryType.PURCHASEITEM)
        {// SaleItems cannot be direct descendants of PurchaseItems
            return;
        }
		
		
//		PlantSpeciesBean psBean = null;
//		PlantVarietyBean pvBean = null;
//		switch (ixBean.getItemType())
//		{
//			case HUSBANDRY -> {
//				psBean = ((HusbandryBean) (ixBean.getItem())).getPlantSpecies();
//				pvBean = ((HusbandryBean) (ixBean.getItem())).getPlantVariety();
//			}
//			case GROUNDWORK -> {
//				psBean = ((GroundworkBean) (ixBean.getItem())).getPlantSpecies();
//				pvBean = ((GroundworkBean) (ixBean.getItem())).getPlantVariety();
//			}
//			case AFFLICTIONEVENT -> {
//				psBean = ((AfflictionEventBean) (ixBean.getItem())).getPlantSpecies();
//				pvBean = ((AfflictionEventBean) (ixBean.getItem())).getPlantVariety();
//			}
//			default -> {
//				LOGGER.debug("ctxmnuDescendantSaleOnAction(): illegal parent type: {}", ixBean);
//				return;
//			}
//		}
//		final SaleEditor tabCon = new SaleEditor(psBean, pvBean);

		//	For a Sale, there must be a PlantSpecies to inherit
		switch (ixBean.getItemType())
		{
			case PURCHASE, PURCHASEITEM -> {
				return;
			}
			case GROUNDWORK -> {
				if (!((GroundworkBean) (ixBean.getItem())).hasPlantSpecies())
					return;
			}
			case AFFLICTIONEVENT -> {
				if (!((AfflictionEventBean) (ixBean.getItem())).hasPlantSpecies())
					return;
			}
			default ->	{}
		}

		final SaleEditor tabCon = new SaleEditor(ixBean.getItem(), true);
		loadTab.accept(resources.getString("tab.sales"), tabCon);
		editorTabSetUp(tabCon);
		LOGGER.debug("ctxmnuDescendantSaleOnAction(): after pop-up");
        
		tabCon.newLinkedItemBeanProperty().addListener((obs, oldVal, newVal) -> {
			LOGGER.debug("ctxmnuDescendantSaleOnAction(): newBean listener: newVal: {}", newVal);   //  newVal is a SaleItemBean
            SaleBean newSale = newVal.getSale();    //  the new Sale which has the SaleItem newVal
//            DiaryBean db = new DiaryBean(newSale);
			DiaryBean db = DiaryBean.from(newSale);
            TreeItem<DiaryBean> purNode = new TreeItem<>(db);
            selNode.getChildren().add(purNode); //  selNode is the parent node in the tree
            try {
                for (SaleItemBean newItem : newSale.getSaleItem())
                {
//                    selNode.getChildren().add(new TreeItem<>(new DiaryBean(newItem)));
					selNode.getChildren().add(new TreeItem<>(DiaryBean.from(newItem)));
                }
            } catch (GNDBException ex) {
				PanicHandler.panic(ex);
            }
			try {
				//  ixBean is the value Bean in the parent node selNode
				switch (ixBean.getItemType())
				{
					case HUSBANDRY -> newVal.setAncestor((HusbandryBean) (ixBean.getItem()));
					case GROUNDWORK -> newVal.setAncestor((GroundworkBean) (ixBean.getItem()));
					case AFFLICTIONEVENT -> newVal.setAncestor((AfflictionEventBean) (ixBean.getItem()));
					default -> {
						LOGGER.debug("ctxmnuDescendantSaleOnAction(): illegal parent type: {}", ixBean);
						return;
					}
				}
				newBeanProperty().setValue(db);
			} catch (GNDBException ex) {
				PanicHandler.panic(ex);
			}
		});
		LOGGER.traceExit(log4jEntryMsg);
	}	//	ctxmnuDescendantSaleOnAction()

    @FXML
    /*
     * Disconnect a leaf item from its ancestor
     * @since 2.6.0
     */    
	private void ctxmnuDropLeafOnAction(ActionEvent event) throws GNDBException
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuDropLeafOnAction()");
		final DiaryBean selBean = diaryTree.getSelectionModel().getSelectedItem().getValue();
		if (selBean == null)
		{
			return;
		}
        
        if (selBean.getItemType() == NotebookEntryType.PURCHASEITEM)
        {// PurchaseItems cannot have ancestors
            return;
        }
        
        if (selBean.hasDescendant())
        {// not a leaf, cannot disconnect a subtree
            return;
        }
        if (!selBean.hasAncestor())
        {// nothing to do!
            return;
        }
		
		Alert checkDelete = new Alert(Alert.AlertType.CONFIRMATION, resources.getString("alert.confirmdropleaf"), ButtonType.NO, ButtonType.YES);
		Optional<ButtonType> result = checkDelete.showAndWait();
		LOGGER.debug("after dropleaf dialog: result:{}, result.get:{}",result, result.get());
		if (result.isPresent() && result.get() == ButtonType.YES)
		{
			LOGGER.debug("after delete confirmed: bean type:{}", selBean.getItemType());
			switch (selBean.getItemType())
			{
				case HUSBANDRY -> ((HusbandryBean) (selBean.getItem())).dropLeaf();
				case SALEITEM -> ((SaleItemBean) (selBean.getItem())).dropLeaf();
				case GROUNDWORK -> ((GroundworkBean) (selBean.getItem())).dropLeaf();
				case AFFLICTIONEVENT -> ((AfflictionEventBean) (selBean.getItem())).dropLeaf();
				default -> {
					LOGGER.debug("ctxmnuDropLeafOnAction(): illegal parent type: {}", selBean);
					return;
				}
			}
		}
		LOGGER.traceExit(log4jEntryMsg);
    }   //  ctxmnuDropLeafOnAction()
    
	private void editorTabSetUp(INotebookLoadable tabCon)
	{
		tabCon.setLoadSplit(loadSplit);
		tabCon.setClearSplit(clearSplit);
		tabCon.setLoadTab(loadTab);
	}

	@Override
	public void setLoadSplit(Consumer<Node> code)
	{
		loadSplit = code;
	}
	
	@Override
	public void setClearSplit(Consumer<Node> code)
	{
		clearSplit = code;
	}
	
	@Override
	public void setLoadTab(BiConsumer<String, Node> code)
	{
		loadTab = code;
	}

	@Override
	public void setClearTab(Consumer<Node> code)
	{
		clearTab = code;
	}

	SimpleObjectProperty<DiaryBean> newBeanProperty()
	{
		if (newBean == null)
		{
			newBean = new SimpleObjectProperty<>();
		}
		return newBean;
	}

	private class DateTableCell extends TreeTableCell<DiaryBean, DiaryBean>
	{
		@Override
		protected void updateItem(DiaryBean item, boolean empty) {
			super.updateItem(item, empty);
			if (item == null || empty)
			{
				setText(null);
				setGraphic(null);
				return;
			}
			updateViewMode();
		}			

		@Override
		public void startEdit() {
			super.startEdit();
			updateViewMode();
		}

		@Override
		public void cancelEdit() {
			super.cancelEdit();
			updateViewMode();
		}
		private void updateViewMode()
		{
			setGraphic(null);
			setText(null);
			if (getItem() == null)
				return;
			
			this.setEditable(true);
			
			if (isEditing())
			{
				VBox vBox = new VBox();
				DatePicker dp = new DatePicker(getItem().getDate());
				dp.setOnAction(ev ->  {
					getItem().setDate(dp.getValue());
					commitEdit(getItem());
					});
				vBox.getChildren().add(dp);
				setGraphic(vBox);
			}
			else
			{
				setText(getItem().getDate().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)));
			}

		}
	}
		
	private class TextTableCell extends TreeTableCell<DiaryBean, DiaryBean>
	{
		StringBinding thisFieldAsText;
		Function<ObjectProperty<AfflictionBean>, Control> afflictionEditor = (item) -> {
				AfflictionCombo cb = new AfflictionCombo(item.get());
				cb.setEditable(true);
				cb.setMandatory(true);
				cb.setOnAction(ev -> {
//					System.out.println("setOnAction: ev: "+ev);
//					System.out.println("setOnAction: value: "+cb.getValue());
//					System.out.println("setOnAction: selection: "+cb.getSelectionModel().getSelectedItem());
					item.set(cb.getSelectionModel().getSelectedItem());
					commitEdit(getItem());
				});
				return cb;
			};
		Function<ObjectProperty<GroundworkActivityBean>, Control> groundworkActivityEditor = (item) -> {
				GroundworkActivityCombo cb = new GroundworkActivityCombo(item.get());
				cb.setEditable(true);
				cb.setMandatory(true);
				cb.setOnAction(ev -> {
//					System.out.println("setOnAction: ev: "+ev);
//					System.out.println("setOnAction: value: "+cb.getValue());
//					System.out.println("setOnAction: selection: "+cb.getSelectionModel().getSelectedItem());
					item.set(cb.getSelectionModel().getSelectedItem());
					commitEdit(getItem());
				});
				return cb;
			};
		Function<ObjectProperty<HusbandryClassBean>, Control> husbandryClassEditor = (item) -> {
				HusbandryClassCombo cb = new HusbandryClassCombo(item.get());
				cb.setEditable(true);
				cb.setMandatory(true);
				cb.setOnAction(ev -> {
//					System.out.println("setOnAction: ev: "+ev);
//					System.out.println("setOnAction: value: "+cb.getValue());
//					System.out.println("setOnAction: selection: "+cb.getSelectionModel().getSelectedItem());
					item.set(cb.getSelectionModel().getSelectedItem());
					commitEdit(getItem());
				});
				return cb;
			};
Function<Property<SimpleMoney>, Control> moneyEditor = (item) -> {
				TextField tf = new TextField(item.getValue().toString());
				tf.setOnAction(ev ->  {
					item.setValue(SimpleMoney.parse(tf.getText()));
					commitEdit(getItem());
					});
				return tf;
			};
//		Function<Property<String>, Control> freeTextEditor = (item) -> {
//				TextField tf = new TextField(item.getValue());
//				tf.setOnAction(ev ->  {
//					item.setValue(tf.getText());
//					commitEdit(getItem());
//					});
//				return tf;
//			};

		//	freeTextEditor2 is for the specific case of free text in colMainTitle.  It needs further
		//	help in DiaryBean - it's a complicated and far from obvious mess.
		//	See handling of Sale (purchasedBy).
		Function<ObjectProperty<SimpleStringProperty>, Control> freeTextEditor2 = (item) -> {
			TextField tf = new TextField( ((item.getValue())).getValue() );
			tf.setOnAction(ev ->  {
				item.getValue().setValue(tf.getText());
				commitEdit(getItem());
			});
			return tf;
		};

		//	for unused columns
		Supplier<Control> nullTextEditor = Label::new;

		@Override
		protected void updateItem(DiaryBean item, boolean empty) {
			super.updateItem(item, empty);
			if (item == null || empty)
			{
				setText(null);
				setGraphic(null);
				return;
			}
			updateViewMode();
		}			

		@Override
		public void startEdit() {
			super.startEdit();
			updateViewMode();
		}

		@Override
		public void cancelEdit() {
			super.cancelEdit();
			updateViewMode();
		}
		private void updateViewMode()
		{
			setGraphic(null);
			setText(null);
			if (getItem() == null)
				return;
			
			if (this.getTableColumn() == colMainTitle)
			{
				thisFieldAsText = getItem().getMainTitleText();
			}
			else if (this.getTableColumn() == colSubTitle)
			{
				thisFieldAsText = getItem().getSubTitleText();
			}
			else if (this.getTableColumn() == colVariety)
			{
				thisFieldAsText = getItem().getVarietyText();
			}
			else if (this.getTableColumn() == colDetail)
			{
				thisFieldAsText = getItem().getDetailText();
			}

			//	By definition, everything here is in a StoryLine so the PlantSpecies and Variety must not be editable
			boolean isCellEditable = true;
			switch (getItem().getItemType())
			{
				case AFFLICTIONEVENT, GROUNDWORK, HUSBANDRY, SALEITEM -> {
					if (this.getTableColumn() == colSubTitle || this.getTableColumn() == colVariety)
					{//	PlantSpecies and Variety
						isCellEditable = false;
					}
				}
				case PURCHASE -> {
					if (this.getTableColumn() == colMainTitle || this.getTableColumn() == colSubTitle)
					{	//	mainTitle is the Retailer and that means changing all the products
						//	subTitle is the text 'Total'
						isCellEditable = false;
					}
				}

				case PURCHASEITEM -> {
					if (this.getTableColumn() == colSubTitle || this.getTableColumn() == colVariety || this.getTableColumn() == colDetail)
					{//	ProductType, PlantSpecies and Variety
						isCellEditable = false;	//	it's just too difficult!
					}
				}

				case SALE ->  {
					if (this.getTableColumn() == colSubTitle)
					{//	The text 'Total'
						isCellEditable = false;
					}
				}

			}
			
			VBox vBox = new VBox();
			if (isEditing()  && isCellEditable)
			{
				switch (getItem().getItemType())
				{
					case AFFLICTIONEVENT:
						if (this.getTableColumn() == colMainTitle)
						{
							vBox.getChildren().add(this.afflictionEditor.apply(getItem().mainTitleProperty()));
						}
						else
						{
							vBox.getChildren().add(this.nullTextEditor.get());
						}
						break;
					case GROUNDWORK:
						if (this.getTableColumn() == colMainTitle)
						{
							vBox.getChildren().add(this.groundworkActivityEditor.apply(getItem().mainTitleProperty()));
						}
						else
						{
							vBox.getChildren().add(this.nullTextEditor.get());
						}
						break;
					case HUSBANDRY:
						if (this.getTableColumn() == colMainTitle)
						{
							vBox.getChildren().add(this.husbandryClassEditor.apply(getItem().mainTitleProperty()));
						}
						else
						{
							vBox.getChildren().add(this.nullTextEditor.get());
						}
						break;
					case PURCHASE:
						if (this.getTableColumn() == colMainTitle)
						{
//							vBox.getChildren().add(this.retailerEditor.apply(getItem().mainTitleProperty()));
						}
						else if (this.getTableColumn() == colVariety)
						{// Total value
							vBox.getChildren().add(this.moneyEditor.apply(getItem().varietyProperty()));
						}
						else
						{
							vBox.getChildren().add(this.nullTextEditor.get());
						}
						break;
					case PURCHASEITEM:
						if (this.getTableColumn() == colMainTitle)
						{
							vBox.getChildren().add(this.nullTextEditor.get());
						}
						else
						{
							vBox.getChildren().add(this.nullTextEditor.get());
						}
						break;
					case SALE:
						if (this.getTableColumn() == colMainTitle)
						{
//							vBox.getChildren().add(this.freeTextEditor.apply( (StringProperty)(getItem().getItem().mainTitleProperty().get()) ));
							vBox.getChildren().add(this.freeTextEditor2.apply((getItem().mainTitleProperty()) ));
						}
						else if (this.getTableColumn() == colVariety)
						{// Total value
							vBox.getChildren().add(this.moneyEditor.apply(getItem().varietyProperty()));
						}
						else
						{
							vBox.getChildren().add(this.nullTextEditor.get());
						}
						break;
					case SALEITEM:
						if (this.getTableColumn() == colMainTitle)
						{
							vBox.getChildren().add(this.nullTextEditor.get());
						}
						else if (this.getTableColumn() == colDetail)
						{//	unit price
							vBox.getChildren().add(this.moneyEditor.apply( (ObjectProperty<SimpleMoney>) getItem().detailProperty()));
						}
						break;
				}
			}
			else if (getItem() != null)
			{
				Label lbl = new Label();
				lbl.textProperty().bind(thisFieldAsText);
				if (this.getTableColumn() == colMainTitle)
				{
					if (getItem().getItemType() == NotebookEntryType.WEATHER)
					{
						lbl.setGraphic(setGraphicOnLabel(weatherGraphic, "black"));
					}
					else if (getItem().getItemType() == NotebookEntryType.HUSBANDRY)
					{
						lbl.setGraphic(setGraphicOnLabel(husbandryGraphic, "black"));
					}
					else if (getItem().getItemType() == NotebookEntryType.AFFLICTIONEVENT)
					{
						lbl.setGraphic(setGraphicOnLabel(afflictionGraphic, "red"));
					}
					else if (getItem().getItemType() == NotebookEntryType.PURCHASE)
					{
						lbl.setGraphic(setGraphicOnLabel(purchaseGraphic, "black"));
					}
					else if (getItem().getItemType() == NotebookEntryType.SALE)
					{
						lbl.setGraphic(setGraphicOnLabel(saleGraphic, "black"));
					}
					else if (getItem().getItemType() == NotebookEntryType.WILDLIFE)
					{
						lbl.setGraphic(setGraphicOnLabel(wildlifeGraphic, "black"));
					}
					else if (getItem().getItemType() == NotebookEntryType.GROUNDWORK)
					{
						lbl.setGraphic(setGraphicOnLabel(groundworkGraphic, "black"));
					}
					else if (getItem().getItemType() == NotebookEntryType.JOURNAL)
					{
						lbl.setGraphic(setGraphicOnLabel(journalGraphic, "black"));
					}
				}
				vBox.getChildren().add(lbl);
			}
			setGraphic(vBox);

		}
	}

	private Region setGraphicOnLabel(SVGPath shape, String colour)
	{
		Region svgRegion = new Region();
		svgRegion.setShape(shape);
		svgRegion.setMinSize(20.0, 20.0);
		svgRegion.setMaxSize(20.0, 20.0);
		svgRegion.setPrefSize(20.0, 20.0);
		svgRegion.setStyle("-fx-background-color: "+colour+";");
		return svgRegion;
	}

	private class CommentTableCell extends TreeTableCell<DiaryBean, DiaryBean>
	{
		private final CommentCellImpl trueCell = new CommentCellImpl(resources);	//	2.9.6
		private double cellHeight;
		
		@Override
		protected void updateItem(DiaryBean item, boolean empty)
		{
			super.updateItem(item, empty);
			
			if (item == null || empty)
			{
				setText(null);
				setGraphic(null);
				return;
			}

			cellHeight = this.getHeight();
			trueCell.setParent(item.getItem());	//	2.9.6
			trueCell.updateViewMode(this, item.getItem().getComments());	//	2.9.6
		}

		@Override
		public void startEdit() {
			super.startEdit();
			cellHeight = this.getHeight();
			trueCell.setParent(getItem().getItem());
			trueCell.updateViewMode(this, getItem().getComments());
		}

		@Override
		public void cancelEdit() {
			super.cancelEdit();
			trueCell.updateViewMode(this, getItem().getComments());
			this.setPrefHeight(cellHeight);
		}
	}

}
