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.dialog;
18  
19  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
20  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
21  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
22  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicResource;
23  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory;
24  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory.MoneyWiseTransCategoryList;
25  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticDataType;
26  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
27  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryType;
28  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryType.MoneyWiseTransCategoryTypeList;
29  import io.github.tonywasher.joceanus.moneywise.ui.base.MoneyWiseBaseTable;
30  import io.github.tonywasher.joceanus.moneywise.ui.base.MoneyWiseItemPanel;
31  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataResource;
32  import io.github.tonywasher.joceanus.prometheus.ui.fieldset.PrometheusFieldSet;
33  import io.github.tonywasher.joceanus.prometheus.ui.fieldset.PrometheusFieldSetEvent;
34  import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
35  import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;
36  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUIScrollButtonField;
37  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUIStringEditField;
38  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIFieldFactory;
39  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollItem;
40  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollMenu;
41  
42  import java.util.Iterator;
43  
44  /**
45   * Dialog to display/edit/create a TransactionCategory.
46   */
47  public class MoneyWiseTransCategoryDialog
48          extends MoneyWiseItemPanel<MoneyWiseTransCategory> {
49      /**
50       * Constructor.
51       *
52       * @param pFactory the GUI factory
53       * @param pEditSet the edit set
54       * @param pOwner   the owning table
55       */
56      public MoneyWiseTransCategoryDialog(final TethysUIFactory<?> pFactory,
57                                          final PrometheusEditSet pEditSet,
58                                          final MoneyWiseBaseTable<MoneyWiseTransCategory> pOwner) {
59          /* Initialise the panel */
60          super(pFactory, pEditSet, pOwner);
61  
62          /* Create a new panel */
63          final PrometheusFieldSet<MoneyWiseTransCategory> myFieldSet = getFieldSet();
64  
65          /* Create the text fields */
66          final TethysUIFieldFactory myFields = pFactory.fieldFactory();
67          final TethysUIStringEditField myName = myFields.newStringField();
68          final TethysUIStringEditField mySubName = myFields.newStringField();
69          final TethysUIStringEditField myDesc = myFields.newStringField();
70  
71          /* Create the buttons */
72          final TethysUIScrollButtonField<MoneyWiseTransCategoryType> myTypeButton = myFields.newScrollField(MoneyWiseTransCategoryType.class);
73          final TethysUIScrollButtonField<MoneyWiseTransCategory> myParentButton = myFields.newScrollField(MoneyWiseTransCategory.class);
74  
75          /* Assign the fields to the panel */
76          myFieldSet.addField(PrometheusDataResource.DATAITEM_FIELD_NAME, myName, MoneyWiseTransCategory::getName);
77          myFieldSet.addField(MoneyWiseBasicResource.CATEGORY_SUBCAT, mySubName, MoneyWiseTransCategory::getSubCategory);
78          myFieldSet.addField(PrometheusDataResource.DATAITEM_FIELD_DESC, myDesc, MoneyWiseTransCategory::getDesc);
79          myFieldSet.addField(MoneyWiseStaticDataType.TRANSTYPE, myTypeButton, MoneyWiseTransCategory::getCategoryType);
80          myFieldSet.addField(PrometheusDataResource.DATAGROUP_PARENT, myParentButton, MoneyWiseTransCategory::getParentCategory);
81  
82          /* Configure the menuBuilders */
83          myTypeButton.setMenuConfigurator(c -> buildCategoryTypeMenu(c, getItem()));
84          myParentButton.setMenuConfigurator(c -> buildParentMenu(c, getItem()));
85  
86          /* Configure name checks */
87          mySubName.setValidator(this::isValidName);
88          mySubName.setReporter(pOwner::showValidateError);
89  
90          /* Configure description checks */
91          myDesc.setValidator(this::isValidDesc);
92          myDesc.setReporter(pOwner::showValidateError);
93      }
94  
95      @Override
96      public void refreshData() {
97          /* If we have an item */
98          final MoneyWiseTransCategory myItem = getItem();
99          if (myItem != null) {
100             final MoneyWiseTransCategoryList myCategories = getDataList(MoneyWiseBasicDataType.TRANSCATEGORY, MoneyWiseTransCategoryList.class);
101             setItem(myCategories.findItemById(myItem.getIndexedId()));
102         }
103 
104         /* Make sure that the item is not editable */
105         setEditable(false);
106     }
107 
108     @Override
109     protected void adjustFields(final boolean isEditable) {
110         /* Access the fieldSet */
111         final PrometheusFieldSet<MoneyWiseTransCategory> myFieldSet = getFieldSet();
112 
113         /* Determine whether parent/full-name fields are visible */
114         final MoneyWiseTransCategory myCategory = getItem();
115         final MoneyWiseTransCategoryType myType = myCategory.getCategoryType();
116         final CategoryType myCurrType = CategoryType.determineType(myType);
117         final boolean showParent = myCurrType.hasSubCatName();
118 
119         /* Determine whether the description field should be visible */
120         final boolean bShowDesc = isEditable || myCategory.getDesc() != null;
121         myFieldSet.setFieldVisible(PrometheusDataResource.DATAITEM_FIELD_DESC, bShowDesc);
122 
123         /* Set visibility */
124         myFieldSet.setFieldVisible(PrometheusDataResource.DATAGROUP_PARENT, showParent);
125         myFieldSet.setFieldVisible(MoneyWiseBasicResource.CATEGORY_SUBCAT, showParent);
126 
127         /* Category type cannot be changed if the item is active */
128         final boolean canEdit = isEditable && !myCategory.isActive() && myCurrType.isChangeable();
129         myFieldSet.setFieldEditable(MoneyWiseStaticDataType.TRANSTYPE, canEdit);
130 
131         /* If the category is not a parent then we cannot edit the full name */
132         myFieldSet.setFieldEditable(PrometheusDataResource.DATAITEM_FIELD_NAME, isEditable && !showParent);
133     }
134 
135     @Override
136     protected void updateField(final PrometheusFieldSetEvent pUpdate) throws OceanusException {
137         /* Access the field */
138         final MetisDataFieldId myField = pUpdate.getFieldId();
139         final MoneyWiseTransCategory myCategory = getItem();
140 
141         /* Process updates */
142         if (PrometheusDataResource.DATAITEM_FIELD_NAME.equals(myField)) {
143             /* Update the SUBCATEGORY(!!) Name */
144             myCategory.setSubCategoryName(pUpdate.getValue(String.class));
145         } else if (MoneyWiseBasicResource.CATEGORY_SUBCAT.equals(myField)) {
146             /* Update the SubCategory */
147             myCategory.setSubCategoryName(pUpdate.getValue(String.class));
148         } else if (PrometheusDataResource.DATAGROUP_PARENT.equals(myField)) {
149             /* Update the Parent */
150             myCategory.setParentCategory(pUpdate.getValue(MoneyWiseTransCategory.class));
151         } else if (PrometheusDataResource.DATAITEM_FIELD_DESC.equals(myField)) {
152             /* Update the Description */
153             myCategory.setDescription(pUpdate.getValue(String.class));
154         } else if (MoneyWiseStaticDataType.TRANSTYPE.equals(myField)) {
155             /* Update the Category Type */
156             myCategory.setCategoryType(pUpdate.getValue(MoneyWiseTransCategoryType.class));
157         }
158     }
159 
160     @Override
161     protected void declareGoToItems(final boolean pUpdates) {
162         final MoneyWiseTransCategory myItem = getItem();
163         final MoneyWiseTransCategory myParent = myItem.getParentCategory();
164         if (!pUpdates) {
165             final MoneyWiseTransCategoryType myType = myItem.getCategoryType();
166             declareGoToItem(myType);
167         }
168         declareGoToItem(myParent);
169     }
170 
171     /**
172      * Build the category type list for an item.
173      *
174      * @param pMenu     the menu
175      * @param pCategory the category to build for
176      */
177     public void buildCategoryTypeMenu(final TethysUIScrollMenu<MoneyWiseTransCategoryType> pMenu,
178                                       final MoneyWiseTransCategory pCategory) {
179         /* Clear the menu */
180         pMenu.removeAllItems();
181 
182         /* Record active item */
183         final MoneyWiseTransCategoryType myCurr = pCategory.getCategoryType();
184         final CategoryType myCurrType = CategoryType.determineType(myCurr);
185         TethysUIScrollItem<MoneyWiseTransCategoryType> myActive = null;
186 
187         /* Access Transaction Category types */
188         final MoneyWiseTransCategoryTypeList myCategoryTypes = getDataList(MoneyWiseStaticDataType.TRANSTYPE, MoneyWiseTransCategoryTypeList.class);
189 
190         /* Loop through the TransCategoryTypes */
191         final Iterator<MoneyWiseTransCategoryType> myIterator = myCategoryTypes.iterator();
192         while (myIterator.hasNext()) {
193             final MoneyWiseTransCategoryType myType = myIterator.next();
194 
195             /* Ignore deleted or disabled */
196             boolean bIgnore = myType.isDeleted() || !myType.getEnabled();
197 
198             /* Ignore category if wrong type */
199             bIgnore |= !myCurrType.equals(CategoryType.determineType(myType));
200             if (bIgnore) {
201                 continue;
202             }
203 
204             /* Create a new action for the type */
205             final TethysUIScrollItem<MoneyWiseTransCategoryType> myItem = pMenu.addItem(myType);
206 
207             /* If this is the active type */
208             if (myType.equals(myCurr)) {
209                 /* Record it */
210                 myActive = myItem;
211             }
212         }
213 
214         /* Ensure active item is visible */
215         if (myActive != null) {
216             myActive.scrollToItem();
217         }
218     }
219 
220     /**
221      * Build the parent menu for the item.
222      *
223      * @param pMenu     the menu
224      * @param pCategory the category to build for
225      */
226     private void buildParentMenu(final TethysUIScrollMenu<MoneyWiseTransCategory> pMenu,
227                                  final MoneyWiseTransCategory pCategory) {
228         /* Clear the menu */
229         pMenu.removeAllItems();
230 
231         /* Record active item */
232         final MoneyWiseTransCategory myCurr = pCategory.getParentCategory();
233         final CategoryType myCurrType = CategoryType.determineType(pCategory);
234         TethysUIScrollItem<MoneyWiseTransCategory> myActive = null;
235 
236         /* Loop through the TransactionCategories */
237         final MoneyWiseTransCategoryList myCategories = getItem().getList();
238         final Iterator<MoneyWiseTransCategory> myIterator = myCategories.iterator();
239         while (myIterator.hasNext()) {
240             final MoneyWiseTransCategory myCat = myIterator.next();
241 
242             /* Ignore deleted and non-subTotal items */
243             final MoneyWiseTransCategoryClass myClass = myCat.getCategoryTypeClass();
244             if (myCat.isDeleted() || !myClass.isSubTotal()) {
245                 continue;
246             }
247 
248             /* If we are interested */
249             if (myCurrType.isParentMatch(myClass)) {
250                 /* Create a new action for the type */
251                 final TethysUIScrollItem<MoneyWiseTransCategory> myItem = pMenu.addItem(myCat);
252 
253                 /* If this is the active parent */
254                 if (myCat.equals(myCurr)) {
255                     /* Record it */
256                     myActive = myItem;
257                 }
258             }
259         }
260 
261         /* Ensure active item is visible */
262         if (myActive != null) {
263             myActive.scrollToItem();
264         }
265     }
266 
267     /**
268      * Category Type.
269      */
270     private enum CategoryType {
271         /**
272          * Income.
273          */
274         INCOME,
275 
276         /**
277          * Expense.
278          */
279         EXPENSE,
280 
281         /**
282          * Totals.
283          */
284         TOTALS,
285 
286         /**
287          * SubTotal.
288          */
289         SUBTOTAL,
290 
291         /**
292          * Singular.
293          */
294         SINGULAR,
295 
296         /**
297          * SecurityXfer.
298          */
299         SECURITYXFER,
300 
301         /**
302          * Transfer.
303          */
304         XFER;
305 
306         /**
307          * Determine type.
308          *
309          * @param pCategory the transaction category
310          * @return the category type
311          */
312         public static CategoryType determineType(final MoneyWiseTransCategory pCategory) {
313             return determineType(pCategory.getCategoryType());
314         }
315 
316         /**
317          * Determine type.
318          *
319          * @param pType the transaction category type
320          * @return the category type
321          */
322         public static CategoryType determineType(final MoneyWiseTransCategoryType pType) {
323             /* Access class */
324             final MoneyWiseTransCategoryClass myClass = pType.getCategoryClass();
325 
326             /* Handle Totals */
327             if (myClass.isTotals()) {
328                 return TOTALS;
329             }
330 
331             /* Handle SubTotals */
332             if (myClass.isSubTotal()) {
333                 return SUBTOTAL;
334             }
335 
336             /* Handle Singular */
337             if (myClass.isSingular()) {
338                 return SINGULAR;
339             }
340 
341             /* Handle Income */
342             if (myClass.isIncome()) {
343                 return INCOME;
344             }
345 
346             /* Handle Transfer */
347             if (myClass.isTransfer()) {
348                 return myClass.isSecurityTransfer()
349                         ? SECURITYXFER
350                         : XFER;
351             }
352 
353             /* Must be expense */
354             return EXPENSE;
355         }
356 
357         /**
358          * Is this type changeable?
359          *
360          * @return true/false
361          */
362         public boolean isChangeable() {
363             switch (this) {
364                 case TOTALS:
365                 case XFER:
366                 case SINGULAR:
367                     return false;
368                 default:
369                     return true;
370             }
371         }
372 
373         /**
374          * Is this type changeable?
375          *
376          * @return true/false
377          */
378         public boolean hasSubCatName() {
379             switch (this) {
380                 case TOTALS:
381                 case SUBTOTAL:
382                     return false;
383                 default:
384                     return true;
385             }
386         }
387 
388         /**
389          * is this parent class a match?
390          *
391          * @param pClass the parent class
392          * @return true/false
393          */
394         public boolean isParentMatch(final MoneyWiseTransCategoryClass pClass) {
395             switch (this) {
396                 case INCOME:
397                     return pClass.isIncome();
398                 case EXPENSE:
399                     return pClass.isExpense();
400                 case SECURITYXFER:
401                     return pClass.isSecurityTransfer();
402                 default:
403                     return false;
404             }
405         }
406     }
407 }