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.oceanus.decimal.OceanusMoney;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
22  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
23  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicResource;
24  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCash;
25  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCash.MoneyWiseCashList;
26  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCashCategory;
27  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCashCategory.MoneyWiseCashCategoryList;
28  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePayee;
29  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePayee.MoneyWisePayeeList;
30  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory;
31  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory.MoneyWiseTransCategoryList;
32  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseAccountInfoClass;
33  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseAssetCategory;
34  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCashCategoryClass;
35  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
36  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency.MoneyWiseCurrencyList;
37  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticDataType;
38  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
39  import io.github.tonywasher.joceanus.moneywise.ui.MoneyWiseIcon;
40  import io.github.tonywasher.joceanus.moneywise.ui.base.MoneyWiseAssetTable;
41  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataResource;
42  import io.github.tonywasher.joceanus.prometheus.ui.fieldset.PrometheusFieldSet;
43  import io.github.tonywasher.joceanus.prometheus.ui.fieldset.PrometheusFieldSetEvent;
44  import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
45  import io.github.tonywasher.joceanus.tethys.api.control.TethysUIControl.TethysUIIconMapSet;
46  import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;
47  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUICharArrayTextAreaField;
48  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUIIconButtonField;
49  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUIMoneyEditField;
50  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUIScrollButtonField;
51  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUIStringEditField;
52  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIFieldFactory;
53  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollItem;
54  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollMenu;
55  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollSubMenu;
56  
57  import java.util.HashMap;
58  import java.util.Iterator;
59  import java.util.Map;
60  
61  /**
62   * Panel to display/edit/create a Cash.
63   */
64  public class MoneyWiseCashDialog
65          extends MoneyWiseAssetDialog<MoneyWiseCash> {
66      /**
67       * The fieldSet.
68       */
69      private final PrometheusFieldSet<MoneyWiseCash> theFieldSet;
70  
71      /**
72       * The Closed State.
73       */
74      private Boolean theClosedState = Boolean.FALSE;
75  
76      /**
77       * Constructor.
78       *
79       * @param pFactory the GUI factory
80       * @param pEditSet the edit set
81       * @param pOwner   the owning table
82       */
83      public MoneyWiseCashDialog(final TethysUIFactory<?> pFactory,
84                                 final PrometheusEditSet pEditSet,
85                                 final MoneyWiseAssetTable<MoneyWiseCash> pOwner) {
86          /* Initialise the panel */
87          super(pFactory, pEditSet, pOwner);
88  
89          /* Access the fieldSet */
90          theFieldSet = getFieldSet();
91          theFieldSet.setReporter(pOwner::showValidateError);
92  
93          /* Build the main panel */
94          buildMainPanel(pFactory);
95  
96          /* Build the details panel */
97          buildDetailsPanel(pFactory);
98  
99          /* Build the notes panel */
100         buildNotesPanel(pFactory);
101     }
102 
103     /**
104      * Build Main subPanel.
105      *
106      * @param pFactory the GUI factory
107      */
108     private void buildMainPanel(final TethysUIFactory<?> pFactory) {
109         /* Create the text fields */
110         final TethysUIFieldFactory myFields = pFactory.fieldFactory();
111         final TethysUIStringEditField myName = myFields.newStringField();
112         final TethysUIStringEditField myDesc = myFields.newStringField();
113 
114         /* Create the buttons */
115         final TethysUIScrollButtonField<MoneyWiseAssetCategory> myCategoryButton = myFields.newScrollField(MoneyWiseAssetCategory.class);
116         final TethysUIScrollButtonField<MoneyWiseCurrency> myCurrencyButton = myFields.newScrollField(MoneyWiseCurrency.class);
117         final TethysUIIconButtonField<Boolean> myClosedButton = myFields.newIconField(Boolean.class);
118 
119         /* Assign the fields to the panel */
120         theFieldSet.addField(PrometheusDataResource.DATAITEM_FIELD_NAME, myName, MoneyWiseCash::getName);
121         theFieldSet.addField(PrometheusDataResource.DATAITEM_FIELD_DESC, myDesc, MoneyWiseCash::getDesc);
122         theFieldSet.addField(MoneyWiseBasicResource.CATEGORY_NAME, myCategoryButton, MoneyWiseCash::getCategory);
123         theFieldSet.addField(MoneyWiseStaticDataType.CURRENCY, myCurrencyButton, MoneyWiseCash::getAssetCurrency);
124         theFieldSet.addField(MoneyWiseBasicResource.ASSET_CLOSED, myClosedButton, MoneyWiseCash::isClosed);
125 
126         /* Configure the menuBuilders */
127         myCategoryButton.setMenuConfigurator(c -> buildCategoryMenu(c, getItem()));
128         myCurrencyButton.setMenuConfigurator(c -> buildCurrencyMenu(c, getItem()));
129         final Map<Boolean, TethysUIIconMapSet<Boolean>> myMapSets = MoneyWiseIcon.configureLockedIconButton(pFactory);
130         myClosedButton.setIconMapSet(() -> myMapSets.get(theClosedState));
131 
132         /* Configure validation checks */
133         myName.setValidator(this::isValidName);
134         myDesc.setValidator(this::isValidDesc);
135     }
136 
137     /**
138      * Build account subPanel.
139      *
140      * @param pFactory the GUI factory
141      */
142     private void buildDetailsPanel(final TethysUIFactory<?> pFactory) {
143         /* Create a new panel */
144         theFieldSet.newPanel(TAB_DETAILS);
145 
146         /* Allocate fields */
147         final TethysUIFieldFactory myFields = pFactory.fieldFactory();
148         final TethysUIMoneyEditField myOpening = myFields.newMoneyField();
149 
150         /* Create the buttons */
151         final TethysUIScrollButtonField<MoneyWiseTransCategory> myAutoExpenseButton = myFields.newScrollField(MoneyWiseTransCategory.class);
152         final TethysUIScrollButtonField<MoneyWisePayee> myAutoPayeeButton = myFields.newScrollField(MoneyWisePayee.class);
153 
154         /* Assign the fields to the panel */
155         theFieldSet.addField(MoneyWiseAccountInfoClass.AUTOEXPENSE, myAutoExpenseButton, MoneyWiseCash::getAutoExpense);
156         theFieldSet.addField(MoneyWiseAccountInfoClass.AUTOPAYEE, myAutoPayeeButton, MoneyWiseCash::getAutoPayee);
157         theFieldSet.addField(MoneyWiseAccountInfoClass.OPENINGBALANCE, myOpening, MoneyWiseCash::getOpeningBalance);
158 
159         /* Configure the menuBuilders */
160         myAutoExpenseButton.setMenuConfigurator(c -> buildAutoExpenseMenu(c, getItem()));
161         myAutoPayeeButton.setMenuConfigurator(c -> buildAutoPayeeMenu(c, getItem()));
162         myOpening.setDeemedCurrency(() -> getItem().getCurrency());
163     }
164 
165     /**
166      * Build Notes subPanel.
167      *
168      * @param pFactory the GUI factory
169      */
170     private void buildNotesPanel(final TethysUIFactory<?> pFactory) {
171         /* Allocate fields */
172         final TethysUICharArrayTextAreaField myNotes = pFactory.fieldFactory().newCharArrayAreaField();
173 
174         /* Assign the fields to the panel */
175         theFieldSet.newTextArea(TAB_NOTES, MoneyWiseAccountInfoClass.NOTES, myNotes, MoneyWiseCash::getNotes);
176 
177         /* Configure notes checks */
178         myNotes.setValidator(this::isValidNotes);
179     }
180 
181     @Override
182     public void refreshData() {
183         /* If we have an item */
184         final MoneyWiseCash myItem = getItem();
185         if (myItem != null) {
186             final MoneyWiseCashList myCash = getDataList(MoneyWiseBasicDataType.CASH, MoneyWiseCashList.class);
187             setItem(myCash.findItemById(myItem.getIndexedId()));
188         }
189 
190         /* Make sure that the item is not editable */
191         setEditable(false);
192     }
193 
194     @Override
195     protected void adjustFields(final boolean isEditable) {
196         /* Access the item */
197         final MoneyWiseCash myCash = getItem();
198         final boolean bIsClosed = myCash.isClosed();
199         final boolean bIsActive = myCash.isActive();
200         final boolean bIsRelevant = myCash.isRelevant();
201         final boolean isAutoExpense = myCash.isAutoExpense();
202         final boolean bIsChangeable = !bIsActive && isEditable;
203 
204         /* Determine whether the closed button should be visible */
205         final boolean bShowClosed = bIsClosed || (bIsActive && !bIsRelevant);
206         theFieldSet.setFieldVisible(MoneyWiseBasicResource.ASSET_CLOSED, bShowClosed);
207 
208         /* Determine the state of the closed button */
209         final boolean bEditClosed = bIsClosed || !bIsRelevant;
210         theFieldSet.setFieldEditable(MoneyWiseBasicResource.ASSET_CLOSED, isEditable && bEditClosed);
211         theClosedState = bEditClosed;
212 
213         /* Determine whether the description field should be visible */
214         final boolean bShowDesc = isEditable || myCash.getDesc() != null;
215         theFieldSet.setFieldVisible(PrometheusDataResource.DATAITEM_FIELD_DESC, bShowDesc);
216 
217         /* AutoExpense/Payee is hidden unless we are autoExpense */
218         theFieldSet.setFieldVisible(MoneyWiseAccountInfoClass.AUTOEXPENSE, isAutoExpense);
219         theFieldSet.setFieldVisible(MoneyWiseAccountInfoClass.AUTOPAYEE, isAutoExpense);
220 
221         /* OpeningBalance is hidden if we are autoExpense */
222         final boolean bHasOpening = myCash.getOpeningBalance() != null;
223         final boolean bShowOpening = bIsChangeable || bHasOpening;
224         theFieldSet.setFieldVisible(MoneyWiseAccountInfoClass.OPENINGBALANCE, !isAutoExpense && bShowOpening);
225 
226         /* Determine whether to show notes */
227         final boolean bShowNotes = isEditable || myCash.getNotes() != null;
228         theFieldSet.setFieldVisible(MoneyWiseAccountInfoClass.NOTES, bShowNotes);
229 
230         /* Category/Currency cannot be changed if the item is active */
231         theFieldSet.setFieldEditable(MoneyWiseBasicResource.CATEGORY_NAME, bIsChangeable);
232         theFieldSet.setFieldEditable(MoneyWiseStaticDataType.CURRENCY, bIsChangeable && !bHasOpening);
233 
234         /* AutoExpense/Payee cannot be changed for closed item */
235         final boolean canEdit = isEditable && !bIsClosed;
236         theFieldSet.setFieldEditable(MoneyWiseAccountInfoClass.AUTOEXPENSE, canEdit);
237         theFieldSet.setFieldEditable(MoneyWiseAccountInfoClass.AUTOPAYEE, canEdit);
238     }
239 
240     @Override
241     protected void updateField(final PrometheusFieldSetEvent pUpdate) throws OceanusException {
242         /* Access the field */
243         final MetisDataFieldId myField = pUpdate.getFieldId();
244         final MoneyWiseCash myCash = getItem();
245 
246         /* Process updates */
247         if (PrometheusDataResource.DATAITEM_FIELD_NAME.equals(myField)) {
248             /* Update the Name */
249             myCash.setName(pUpdate.getValue(String.class));
250         } else if (PrometheusDataResource.DATAITEM_FIELD_DESC.equals(myField)) {
251             /* Update the Description */
252             myCash.setDescription(pUpdate.getValue(String.class));
253         } else if (MoneyWiseBasicResource.CATEGORY_NAME.equals(myField)) {
254             /* Update the Category */
255             myCash.setCategory(pUpdate.getValue(MoneyWiseCashCategory.class));
256             myCash.autoCorrect();
257         } else if (MoneyWiseStaticDataType.CURRENCY.equals(myField)) {
258             /* Update the Currency */
259             myCash.setAssetCurrency(pUpdate.getValue(MoneyWiseCurrency.class));
260         } else if (MoneyWiseBasicResource.ASSET_CLOSED.equals(myField)) {
261             /* Update the Closed indication */
262             myCash.setClosed(pUpdate.getValue(Boolean.class));
263         } else if (MoneyWiseAccountInfoClass.AUTOEXPENSE.equals(myField)) {
264             /* Update the AutoExpense */
265             myCash.setAutoExpense(pUpdate.getValue(MoneyWiseTransCategory.class));
266         } else if (MoneyWiseAccountInfoClass.AUTOPAYEE.equals(myField)) {
267             /* Update the AutoPayee */
268             myCash.setAutoPayee(pUpdate.getValue(MoneyWisePayee.class));
269         } else if (MoneyWiseAccountInfoClass.OPENINGBALANCE.equals(myField)) {
270             /* Update the OpeningBalance */
271             myCash.setOpeningBalance(pUpdate.getValue(OceanusMoney.class));
272         } else if (MoneyWiseAccountInfoClass.NOTES.equals(myField)) {
273             /* Update the OpeningBalance */
274             myCash.setNotes(pUpdate.getValue(char[].class));
275         }
276     }
277 
278     @Override
279     protected void declareGoToItems(final boolean pUpdates) {
280         final MoneyWiseCash myItem = getItem();
281         final MoneyWisePayee myAutoPayee = myItem.getAutoPayee();
282         if (!pUpdates) {
283             final MoneyWiseCashCategory myCategory = myItem.getCategory();
284             final MoneyWiseTransCategory myAutoExpense = myItem.getAutoExpense();
285             final MoneyWiseCurrency myCurrency = myItem.getAssetCurrency();
286             declareGoToItem(myCategory);
287             declareGoToItem(myCurrency);
288             declareGoToItem(myAutoExpense);
289         }
290         declareGoToItem(myAutoPayee);
291     }
292 
293     /**
294      * Build the category menu for an item.
295      *
296      * @param pMenu the menu
297      * @param pCash the cash to build for
298      */
299     public void buildCategoryMenu(final TethysUIScrollMenu<MoneyWiseAssetCategory> pMenu,
300                                   final MoneyWiseCash pCash) {
301         /* Clear the menu */
302         pMenu.removeAllItems();
303 
304         /* Record active item */
305         final MoneyWiseCashCategory myCurr = pCash.getCategory();
306         TethysUIScrollItem<MoneyWiseAssetCategory> myActive = null;
307 
308         /* Access Cash Categories */
309         final MoneyWiseCashCategoryList myCategories = getDataList(MoneyWiseBasicDataType.CASHCATEGORY, MoneyWiseCashCategoryList.class);
310 
311         /* Create a simple map for top-level categories */
312         final Map<String, TethysUIScrollSubMenu<MoneyWiseAssetCategory>> myMap = new HashMap<>();
313 
314         /* Loop through the available category values */
315         final Iterator<MoneyWiseCashCategory> myIterator = myCategories.iterator();
316         while (myIterator.hasNext()) {
317             final MoneyWiseCashCategory myCategory = myIterator.next();
318 
319             /* Ignore deleted or parent */
320             final boolean bIgnore = myCategory.isDeleted() || myCategory.isCategoryClass(MoneyWiseCashCategoryClass.PARENT);
321             if (bIgnore) {
322                 continue;
323             }
324 
325             /* Determine menu to add to */
326             final MoneyWiseCashCategory myParent = myCategory.getParentCategory();
327             final String myParentName = myParent.getName();
328             final TethysUIScrollSubMenu<MoneyWiseAssetCategory> myMenu = myMap.computeIfAbsent(myParentName, pMenu::addSubMenu);
329 
330             /* Create a new MenuItem and add it to the popUp */
331             final TethysUIScrollItem<MoneyWiseAssetCategory> myItem = myMenu.getSubMenu().addItem(myCategory, myCategory.getSubCategory());
332 
333             /* Note active category */
334             if (myCategory.equals(myCurr)) {
335                 myActive = myItem;
336             }
337         }
338 
339         /* Ensure active item is visible */
340         if (myActive != null) {
341             myActive.scrollToItem();
342         }
343     }
344 
345     /**
346      * Build the autoExpense menu for an item.
347      *
348      * @param pMenu the menu
349      * @param pCash the cash to build for
350      */
351     private void buildAutoExpenseMenu(final TethysUIScrollMenu<MoneyWiseTransCategory> pMenu,
352                                       final MoneyWiseCash pCash) {
353         /* Clear the menu */
354         pMenu.removeAllItems();
355 
356         /* Record active item */
357         final MoneyWiseTransCategory myCurr = pCash.getAutoExpense();
358         TethysUIScrollItem<MoneyWiseTransCategory> myActive = null;
359 
360         /* Access Transaction Categories */
361         final MoneyWiseTransCategoryList myCategories = getDataList(MoneyWiseBasicDataType.TRANSCATEGORY, MoneyWiseTransCategoryList.class);
362 
363         /* Create a simple map for top-level categories */
364         final Map<String, TethysUIScrollSubMenu<MoneyWiseTransCategory>> myMap = new HashMap<>();
365 
366         /* Loop through the available category values */
367         final Iterator<MoneyWiseTransCategory> myIterator = myCategories.iterator();
368         while (myIterator.hasNext()) {
369             final MoneyWiseTransCategory myCategory = myIterator.next();
370 
371             /* Ignore deleted or non-expense-subTotal items */
372             final MoneyWiseTransCategoryClass myClass = myCategory.getCategoryTypeClass();
373             boolean bIgnore = myCategory.isDeleted() || myClass.canParentCategory();
374             bIgnore |= !myClass.isExpense();
375             if (bIgnore) {
376                 continue;
377             }
378 
379             /* Determine menu to add to */
380             final MoneyWiseTransCategory myParent = myCategory.getParentCategory();
381             final String myParentName = myParent.getName();
382             final TethysUIScrollSubMenu<MoneyWiseTransCategory> myMenu = myMap.computeIfAbsent(myParentName, pMenu::addSubMenu);
383 
384             /* Create a new MenuItem and add it to the popUp */
385             final TethysUIScrollItem<MoneyWiseTransCategory> myItem = myMenu.getSubMenu().addItem(myCategory);
386 
387             /* Note active category */
388             if (myCategory.equals(myCurr)) {
389                 myActive = myItem;
390             }
391         }
392 
393         /* Ensure active item is visible */
394         if (myActive != null) {
395             myActive.scrollToItem();
396         }
397     }
398 
399     /**
400      * Build the autoPayee menu for an item.
401      *
402      * @param pMenu the menu
403      * @param pCash the cash to build for
404      */
405     private void buildAutoPayeeMenu(final TethysUIScrollMenu<MoneyWisePayee> pMenu,
406                                     final MoneyWiseCash pCash) {
407         /* Clear the menu */
408         pMenu.removeAllItems();
409 
410         /* Record active item */
411         final MoneyWisePayee myCurr = pCash.getAutoPayee();
412         TethysUIScrollItem<MoneyWisePayee> myActive = null;
413 
414         /* Access Payees */
415         final MoneyWisePayeeList myPayees = getDataList(MoneyWiseBasicDataType.PAYEE, MoneyWisePayeeList.class);
416 
417         /* Loop through the Payees */
418         final Iterator<MoneyWisePayee> myIterator = myPayees.iterator();
419         while (myIterator.hasNext()) {
420             final MoneyWisePayee myPayee = myIterator.next();
421 
422             /* Ignore deleted/closed */
423             if (myPayee.isDeleted() || myPayee.isClosed()) {
424                 continue;
425             }
426 
427             /* Create a new action for the payee */
428             final TethysUIScrollItem<MoneyWisePayee> myItem = pMenu.addItem(myPayee);
429 
430             /* If this is the active parent */
431             if (myPayee.equals(myCurr)) {
432                 /* Record it */
433                 myActive = myItem;
434             }
435         }
436 
437         /* Ensure active item is visible */
438         if (myActive != null) {
439             myActive.scrollToItem();
440         }
441     }
442 
443     /**
444      * Build the currency menu for an item.
445      *
446      * @param pMenu the menu
447      * @param pCash the cash to build for
448      */
449     public void buildCurrencyMenu(final TethysUIScrollMenu<MoneyWiseCurrency> pMenu,
450                                   final MoneyWiseCash pCash) {
451         /* Clear the menu */
452         pMenu.removeAllItems();
453 
454         /* Record active item */
455         final MoneyWiseCurrency myCurr = pCash.getAssetCurrency();
456         TethysUIScrollItem<MoneyWiseCurrency> myActive = null;
457 
458         /* Access Currencies */
459         final MoneyWiseCurrencyList myCurrencies = getDataList(MoneyWiseStaticDataType.CURRENCY, MoneyWiseCurrencyList.class);
460 
461         /* Loop through the AccountCurrencies */
462         final Iterator<MoneyWiseCurrency> myIterator = myCurrencies.iterator();
463         while (myIterator.hasNext()) {
464             final MoneyWiseCurrency myCurrency = myIterator.next();
465 
466             /* Ignore deleted or disabled */
467             final boolean bIgnore = myCurrency.isDeleted() || !myCurrency.getEnabled();
468             if (bIgnore) {
469                 continue;
470             }
471 
472             /* Create a new action for the currency */
473             final TethysUIScrollItem<MoneyWiseCurrency> myItem = pMenu.addItem(myCurrency);
474 
475             /* If this is the active currency */
476             if (myCurrency.equals(myCurr)) {
477                 /* Record it */
478                 myActive = myItem;
479             }
480         }
481 
482         /* Ensure active item is visible */
483         if (myActive != null) {
484             myActive.scrollToItem();
485         }
486     }
487 }