MoneyWiseCategoryTable.java
/*
* MoneyWise: Finance Application
* Copyright 2012-2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.moneywise.ui.base;
import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataNamedItem;
import io.github.tonywasher.joceanus.metis.list.MetisListKey;
import io.github.tonywasher.joceanus.metis.ui.MetisAction;
import io.github.tonywasher.joceanus.metis.ui.MetisErrorPanel;
import io.github.tonywasher.joceanus.metis.ui.MetisIcon;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicResource;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCategoryBase;
import io.github.tonywasher.joceanus.moneywise.ui.MoneyWiseUIResource;
import io.github.tonywasher.joceanus.moneywise.views.MoneyWiseView;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataResource;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusStaticDataItem;
import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
import io.github.tonywasher.joceanus.tethys.api.base.TethysUIEvent;
import io.github.tonywasher.joceanus.tethys.api.button.TethysUIButton;
import io.github.tonywasher.joceanus.tethys.api.button.TethysUIButtonFactory;
import io.github.tonywasher.joceanus.tethys.api.button.TethysUIScrollButtonManager;
import io.github.tonywasher.joceanus.tethys.api.control.TethysUIControl.TethysUIIconMapSet;
import io.github.tonywasher.joceanus.tethys.api.control.TethysUILabel;
import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;
import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollItem;
import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollMenu;
import io.github.tonywasher.joceanus.tethys.api.pane.TethysUIBoxPaneManager;
import io.github.tonywasher.joceanus.tethys.api.table.TethysUITableManager;
import java.util.List;
import java.util.Objects;
/**
* MoneyWise Category Table.
*
* @param <T> the Category Data type
* @param <S> the Static Data type
*/
public abstract class MoneyWiseCategoryTable<T extends MoneyWiseCategoryBase, S extends PrometheusStaticDataItem>
extends MoneyWiseBaseTable<T> {
/**
* Filter Prompt.
*/
private static final String TITLE_FILTER = MoneyWiseUIResource.CATEGORY_PROMPT_FILTER.getValue();
/**
* Filter Parents Title.
*/
private static final String FILTER_PARENTS = MoneyWiseUIResource.CATEGORY_FILTER_PARENT.getValue();
/**
* The filter panel.
*/
private final TethysUIBoxPaneManager theFilterPanel;
/**
* The select button.
*/
private final TethysUIScrollButtonManager<T> theSelectButton;
/**
* Active parent.
*/
private T theParent;
/**
* Constructor.
*
* @param pView the view
* @param pEditSet the editSet
* @param pError the error panel
* @param pClazz the dataType class
* @param pDataType the dataType
*/
protected MoneyWiseCategoryTable(final MoneyWiseView pView,
final PrometheusEditSet pEditSet,
final MetisErrorPanel pError,
final Class<T> pClazz,
final MetisListKey pDataType) {
/* Store parameters */
super(pView, pEditSet, pError, pDataType);
/* Access Gui factory */
final TethysUIFactory<?> myGuiFactory = pView.getGuiFactory();
final TethysUITableManager<MetisDataFieldId, T> myTable = getTable();
/* Create new button */
final TethysUIButtonFactory<?> myButtons = myGuiFactory.buttonFactory();
final TethysUIButton myNewButton = myButtons.newButton();
MetisIcon.configureNewIconButton(myNewButton);
/* Create the filter components */
final TethysUILabel myPrompt = myGuiFactory.controlFactory().newLabel(TITLE_FILTER);
theSelectButton = myButtons.newScrollButton(pClazz);
theSelectButton.setValue(null, FILTER_PARENTS);
/* Create a filter panel */
theFilterPanel = myGuiFactory.paneFactory().newHBoxPane();
theFilterPanel.addSpacer();
theFilterPanel.addNode(myPrompt);
theFilterPanel.addNode(theSelectButton);
theFilterPanel.addSpacer();
theFilterPanel.addNode(myNewButton);
/* Set table configuration */
myTable.setDisabled(MoneyWiseCategoryBase::isDisabled)
.setComparator(MoneyWiseCategoryBase::compareTo);
/* Create the short name column */
myTable.declareStringColumn(MoneyWiseBasicResource.CATEGORY_SUBCAT)
.setValidator(this::isValidName)
.setCellValueFactory(this::getShortName)
.setEditable(true)
.setColumnWidth(WIDTH_NAME)
.setOnCommit((r, v) -> updateField(MoneyWiseCategoryBase::setSubCategoryName, r, v));
/* Create the full name column */
myTable.declareStringColumn(PrometheusDataResource.DATAITEM_FIELD_NAME)
.setCellValueFactory(MoneyWiseCategoryBase::getName)
.setEditable(false)
.setColumnWidth(WIDTH_NAME);
/* Create the category type column */
addCategoryTypeColumn();
/* Create the description column */
myTable.declareStringColumn(PrometheusDataResource.DATAITEM_FIELD_DESC)
.setValidator(this::isValidDesc)
.setCellValueFactory(MoneyWiseCategoryBase::getDesc)
.setEditable(true)
.setColumnWidth(WIDTH_NAME)
.setOnCommit((r, v) -> updateField(MoneyWiseCategoryBase::setDescription, r, v));
/* Create the Active column */
final TethysUIIconMapSet<MetisAction> myActionMapSet = MetisIcon.configureStatusIconButton(myGuiFactory);
myTable.declareIconColumn(PrometheusDataResource.DATAITEM_TOUCH, MetisAction.class)
.setIconMapSet(r -> myActionMapSet)
.setCellValueFactory(r -> r.isActive() ? MetisAction.ACTIVE : MetisAction.DELETE)
.setName(MoneyWiseUIResource.STATICDATA_ACTIVE.getValue())
.setEditable(true)
.setCellEditable(r -> !r.isActive())
.setColumnWidth(WIDTH_ICON)
.setOnCommit((r, v) -> updateField(this::deleteRow, r, v));
/* Add listeners */
myNewButton.getEventRegistrar().addEventListener(e -> addNewItem());
theSelectButton.getEventRegistrar().addEventListener(TethysUIEvent.NEWVALUE, e -> handleParentSelection());
theSelectButton.setMenuConfigurator(e -> buildSelectMenu());
}
/**
* Add categoryType column.
*/
protected abstract void addCategoryTypeColumn();
/**
* Obtain the filter panel.
*
* @return the filter panel
*/
public TethysUIBoxPaneManager getFilterPanel() {
return theFilterPanel;
}
/**
* Obtain the short name.
*
* @param pCategory the category
* @return the name
*/
protected String getShortName(final T pCategory) {
final String myName = pCategory.getSubCategory();
return myName == null ? pCategory.getName() : myName;
}
/**
* Obtain the parent.
*
* @return the parent
*/
protected T getParent() {
return theParent;
}
/**
* Update parent.
*
* @param pParent the parent
*/
protected void updateParent(final T pParent) {
theParent = pParent;
theSelectButton.setValue(theParent);
}
/**
* Select parent.
*
* @param pParent the parent category
*/
protected void selectParent(final T pParent) {
/* If the parent is being changed */
if (!MetisDataDifference.isEqual(pParent, theParent)) {
/* Store new value */
theParent = pParent;
/* Update select button */
if (pParent == null) {
theSelectButton.setValue(null, FILTER_PARENTS);
} else {
theSelectButton.setValue(pParent);
}
/* Notify table of change */
updateTableData();
}
}
/**
* Handle parent selection.
*/
private void handleParentSelection() {
selectParent(theSelectButton.getValue());
}
/**
* Build the category type list for the item.
*
* @param pCategory the item
* @param pMenu the menu to build
*/
protected abstract void buildCategoryTypeMenu(T pCategory,
TethysUIScrollMenu<S> pMenu);
/**
* Obtain the categories.
*
* @return the categories
*/
protected abstract List<T> getCategories();
/**
* Is the categoryType a child category.
*
* @param pCategoryType the categoryType
* @return true/false
*/
protected abstract boolean isChildCategory(S pCategoryType);
/**
* Build Select menu.
*/
private void buildSelectMenu() {
/* Clear the menu */
final TethysUIScrollMenu<T> myCategoryMenu = theSelectButton.getMenu();
myCategoryMenu.removeAllItems();
/* Cope if we have no categories */
final List<T> myCategories = getCategories();
if (myCategories == null) {
return;
}
/* Record active item */
TethysUIScrollItem<T> myActive = null;
/* Create the no filter MenuItem and add it to the popUp */
TethysUIScrollItem<T> myItem = myCategoryMenu.addItem(null, FILTER_PARENTS);
/* If this is the active parent */
if (theParent == null) {
/* Record it */
myActive = myItem;
}
/* Loop through the available category values */
for (T myCurr : myCategories) {
@SuppressWarnings("unchecked") final S myType = (S) myCurr.getCategoryType();
/* Ignore category if it is deleted or a child */
if (myCurr.isDeleted() || isChildCategory(myType)) {
continue;
}
/* Create a new MenuItem and add it to the popUp */
myItem = myCategoryMenu.addItem(myCurr);
/* If this is the active parent */
if (myCurr.equals(theParent)) {
/* Record it */
myActive = myItem;
}
}
/* Ensure active item is visible */
if (myActive != null) {
myActive.scrollToItem();
}
}
/**
* New item.
*/
protected abstract void addNewItem();
@Override
protected String getInvalidNameChars() {
return ":";
}
@Override
protected boolean isDuplicateName(final String pNewName,
final T pRow,
final MetisDataNamedItem pCheck) {
/* Check for duplicate */
final MoneyWiseCategoryBase myCheck = (MoneyWiseCategoryBase) pCheck;
return Objects.equals(myCheck.getParentCategory(), pRow.getParentCategory())
&& pNewName.equals(myCheck.getSubCategory());
}
}