View Javadoc
1   /*
2    * MoneyWise: Finance Application
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.moneywise.ui.base;
18  
19  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
20  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataNamedItem;
22  import io.github.tonywasher.joceanus.metis.list.MetisListKey;
23  import io.github.tonywasher.joceanus.metis.ui.MetisAction;
24  import io.github.tonywasher.joceanus.metis.ui.MetisErrorPanel;
25  import io.github.tonywasher.joceanus.metis.ui.MetisIcon;
26  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicResource;
27  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCategoryBase;
28  import io.github.tonywasher.joceanus.moneywise.ui.MoneyWiseUIResource;
29  import io.github.tonywasher.joceanus.moneywise.views.MoneyWiseView;
30  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataResource;
31  import io.github.tonywasher.joceanus.prometheus.data.PrometheusStaticDataItem;
32  import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
33  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIEvent;
34  import io.github.tonywasher.joceanus.tethys.api.button.TethysUIButton;
35  import io.github.tonywasher.joceanus.tethys.api.button.TethysUIButtonFactory;
36  import io.github.tonywasher.joceanus.tethys.api.button.TethysUIScrollButtonManager;
37  import io.github.tonywasher.joceanus.tethys.api.control.TethysUIControl.TethysUIIconMapSet;
38  import io.github.tonywasher.joceanus.tethys.api.control.TethysUILabel;
39  import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;
40  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollItem;
41  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollMenu;
42  import io.github.tonywasher.joceanus.tethys.api.pane.TethysUIBoxPaneManager;
43  import io.github.tonywasher.joceanus.tethys.api.table.TethysUITableManager;
44  
45  import java.util.List;
46  import java.util.Objects;
47  
48  /**
49   * MoneyWise Category Table.
50   *
51   * @param <T> the Category Data type
52   * @param <S> the Static Data type
53   */
54  public abstract class MoneyWiseCategoryTable<T extends MoneyWiseCategoryBase, S extends PrometheusStaticDataItem>
55          extends MoneyWiseBaseTable<T> {
56      /**
57       * Filter Prompt.
58       */
59      private static final String TITLE_FILTER = MoneyWiseUIResource.CATEGORY_PROMPT_FILTER.getValue();
60  
61      /**
62       * Filter Parents Title.
63       */
64      private static final String FILTER_PARENTS = MoneyWiseUIResource.CATEGORY_FILTER_PARENT.getValue();
65  
66      /**
67       * The filter panel.
68       */
69      private final TethysUIBoxPaneManager theFilterPanel;
70  
71      /**
72       * The select button.
73       */
74      private final TethysUIScrollButtonManager<T> theSelectButton;
75  
76      /**
77       * Active parent.
78       */
79      private T theParent;
80  
81      /**
82       * Constructor.
83       *
84       * @param pView     the view
85       * @param pEditSet  the editSet
86       * @param pError    the error panel
87       * @param pClazz    the dataType class
88       * @param pDataType the dataType
89       */
90      protected MoneyWiseCategoryTable(final MoneyWiseView pView,
91                                       final PrometheusEditSet pEditSet,
92                                       final MetisErrorPanel pError,
93                                       final Class<T> pClazz,
94                                       final MetisListKey pDataType) {
95          /* Store parameters */
96          super(pView, pEditSet, pError, pDataType);
97  
98          /* Access Gui factory */
99          final TethysUIFactory<?> myGuiFactory = pView.getGuiFactory();
100         final TethysUITableManager<MetisDataFieldId, T> myTable = getTable();
101 
102         /* Create new button */
103         final TethysUIButtonFactory<?> myButtons = myGuiFactory.buttonFactory();
104         final TethysUIButton myNewButton = myButtons.newButton();
105         MetisIcon.configureNewIconButton(myNewButton);
106 
107         /* Create the filter components */
108         final TethysUILabel myPrompt = myGuiFactory.controlFactory().newLabel(TITLE_FILTER);
109         theSelectButton = myButtons.newScrollButton(pClazz);
110         theSelectButton.setValue(null, FILTER_PARENTS);
111 
112         /* Create a filter panel */
113         theFilterPanel = myGuiFactory.paneFactory().newHBoxPane();
114         theFilterPanel.addSpacer();
115         theFilterPanel.addNode(myPrompt);
116         theFilterPanel.addNode(theSelectButton);
117         theFilterPanel.addSpacer();
118         theFilterPanel.addNode(myNewButton);
119 
120         /* Set table configuration */
121         myTable.setDisabled(MoneyWiseCategoryBase::isDisabled)
122                 .setComparator(MoneyWiseCategoryBase::compareTo);
123 
124         /* Create the short name column */
125         myTable.declareStringColumn(MoneyWiseBasicResource.CATEGORY_SUBCAT)
126                 .setValidator(this::isValidName)
127                 .setCellValueFactory(this::getShortName)
128                 .setEditable(true)
129                 .setColumnWidth(WIDTH_NAME)
130                 .setOnCommit((r, v) -> updateField(MoneyWiseCategoryBase::setSubCategoryName, r, v));
131 
132         /* Create the full name column */
133         myTable.declareStringColumn(PrometheusDataResource.DATAITEM_FIELD_NAME)
134                 .setCellValueFactory(MoneyWiseCategoryBase::getName)
135                 .setEditable(false)
136                 .setColumnWidth(WIDTH_NAME);
137 
138         /* Create the category type column */
139         addCategoryTypeColumn();
140 
141         /* Create the description column */
142         myTable.declareStringColumn(PrometheusDataResource.DATAITEM_FIELD_DESC)
143                 .setValidator(this::isValidDesc)
144                 .setCellValueFactory(MoneyWiseCategoryBase::getDesc)
145                 .setEditable(true)
146                 .setColumnWidth(WIDTH_NAME)
147                 .setOnCommit((r, v) -> updateField(MoneyWiseCategoryBase::setDescription, r, v));
148 
149         /* Create the Active column */
150         final TethysUIIconMapSet<MetisAction> myActionMapSet = MetisIcon.configureStatusIconButton(myGuiFactory);
151         myTable.declareIconColumn(PrometheusDataResource.DATAITEM_TOUCH, MetisAction.class)
152                 .setIconMapSet(r -> myActionMapSet)
153                 .setCellValueFactory(r -> r.isActive() ? MetisAction.ACTIVE : MetisAction.DELETE)
154                 .setName(MoneyWiseUIResource.STATICDATA_ACTIVE.getValue())
155                 .setEditable(true)
156                 .setCellEditable(r -> !r.isActive())
157                 .setColumnWidth(WIDTH_ICON)
158                 .setOnCommit((r, v) -> updateField(this::deleteRow, r, v));
159 
160         /* Add listeners */
161         myNewButton.getEventRegistrar().addEventListener(e -> addNewItem());
162         theSelectButton.getEventRegistrar().addEventListener(TethysUIEvent.NEWVALUE, e -> handleParentSelection());
163         theSelectButton.setMenuConfigurator(e -> buildSelectMenu());
164     }
165 
166     /**
167      * Add categoryType column.
168      */
169     protected abstract void addCategoryTypeColumn();
170 
171     /**
172      * Obtain the filter panel.
173      *
174      * @return the filter panel
175      */
176     public TethysUIBoxPaneManager getFilterPanel() {
177         return theFilterPanel;
178     }
179 
180     /**
181      * Obtain the short name.
182      *
183      * @param pCategory the category
184      * @return the name
185      */
186     protected String getShortName(final T pCategory) {
187         final String myName = pCategory.getSubCategory();
188         return myName == null ? pCategory.getName() : myName;
189     }
190 
191     /**
192      * Obtain the parent.
193      *
194      * @return the parent
195      */
196     protected T getParent() {
197         return theParent;
198     }
199 
200     /**
201      * Update parent.
202      *
203      * @param pParent the parent
204      */
205     protected void updateParent(final T pParent) {
206         theParent = pParent;
207         theSelectButton.setValue(theParent);
208     }
209 
210     /**
211      * Select parent.
212      *
213      * @param pParent the parent category
214      */
215     protected void selectParent(final T pParent) {
216         /* If the parent is being changed */
217         if (!MetisDataDifference.isEqual(pParent, theParent)) {
218             /* Store new value */
219             theParent = pParent;
220 
221             /* Update select button */
222             if (pParent == null) {
223                 theSelectButton.setValue(null, FILTER_PARENTS);
224             } else {
225                 theSelectButton.setValue(pParent);
226             }
227 
228             /* Notify table of change */
229             updateTableData();
230         }
231     }
232 
233     /**
234      * Handle parent selection.
235      */
236     private void handleParentSelection() {
237         selectParent(theSelectButton.getValue());
238     }
239 
240     /**
241      * Build the category type list for the item.
242      *
243      * @param pCategory the item
244      * @param pMenu     the menu to build
245      */
246     protected abstract void buildCategoryTypeMenu(T pCategory,
247                                                   TethysUIScrollMenu<S> pMenu);
248 
249     /**
250      * Obtain the categories.
251      *
252      * @return the categories
253      */
254     protected abstract List<T> getCategories();
255 
256     /**
257      * Is the categoryType a child category.
258      *
259      * @param pCategoryType the categoryType
260      * @return true/false
261      */
262     protected abstract boolean isChildCategory(S pCategoryType);
263 
264     /**
265      * Build Select menu.
266      */
267     private void buildSelectMenu() {
268         /* Clear the menu */
269         final TethysUIScrollMenu<T> myCategoryMenu = theSelectButton.getMenu();
270         myCategoryMenu.removeAllItems();
271 
272         /* Cope if we have no categories */
273         final List<T> myCategories = getCategories();
274         if (myCategories == null) {
275             return;
276         }
277 
278         /* Record active item */
279         TethysUIScrollItem<T> myActive = null;
280 
281         /* Create the no filter MenuItem and add it to the popUp */
282         TethysUIScrollItem<T> myItem = myCategoryMenu.addItem(null, FILTER_PARENTS);
283 
284         /* If this is the active parent */
285         if (theParent == null) {
286             /* Record it */
287             myActive = myItem;
288         }
289 
290         /* Loop through the available category values */
291         for (T myCurr : myCategories) {
292             @SuppressWarnings("unchecked") final S myType = (S) myCurr.getCategoryType();
293 
294             /* Ignore category if it is deleted or a child */
295             if (myCurr.isDeleted() || isChildCategory(myType)) {
296                 continue;
297             }
298 
299             /* Create a new MenuItem and add it to the popUp */
300             myItem = myCategoryMenu.addItem(myCurr);
301 
302             /* If this is the active parent */
303             if (myCurr.equals(theParent)) {
304                 /* Record it */
305                 myActive = myItem;
306             }
307         }
308 
309         /* Ensure active item is visible */
310         if (myActive != null) {
311             myActive.scrollToItem();
312         }
313     }
314 
315     /**
316      * New item.
317      */
318     protected abstract void addNewItem();
319 
320     @Override
321     protected String getInvalidNameChars() {
322         return ":";
323     }
324 
325     @Override
326     protected boolean isDuplicateName(final String pNewName,
327                                       final T pRow,
328                                       final MetisDataNamedItem pCheck) {
329         /* Check for duplicate */
330         final MoneyWiseCategoryBase myCheck = (MoneyWiseCategoryBase) pCheck;
331         return Objects.equals(myCheck.getParentCategory(), pRow.getParentCategory())
332                 && pNewName.equals(myCheck.getSubCategory());
333     }
334 }