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.lethe.ui.controls;
18  
19  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateRange;
20  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventManager;
21  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar;
22  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar.OceanusEventProvider;
23  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
24  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
25  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCashCategory;
26  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCashCategoryClass;
27  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysis;
28  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisCashBucket;
29  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisCashBucket.MoneyWiseAnalysisCashBucketList;
30  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisCashCategoryBucket;
31  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisCashCategoryBucket.MoneyWiseAnalysisCashCategoryBucketList;
32  import io.github.tonywasher.joceanus.moneywise.lethe.views.MoneyWiseAnalysisFilter;
33  import io.github.tonywasher.joceanus.moneywise.lethe.views.MoneyWiseAnalysisFilter.MoneyWiseAnalysisCashFilter;
34  import io.github.tonywasher.joceanus.prometheus.views.PrometheusDataEvent;
35  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIComponent;
36  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIConstant;
37  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIEvent;
38  import io.github.tonywasher.joceanus.tethys.api.button.TethysUIButtonFactory;
39  import io.github.tonywasher.joceanus.tethys.api.button.TethysUIScrollButtonManager;
40  import io.github.tonywasher.joceanus.tethys.api.control.TethysUIControlFactory;
41  import io.github.tonywasher.joceanus.tethys.api.control.TethysUILabel;
42  import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;
43  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollItem;
44  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollMenu;
45  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollSubMenu;
46  import io.github.tonywasher.joceanus.tethys.api.pane.TethysUIBoxPaneManager;
47  
48  import java.util.HashMap;
49  import java.util.Iterator;
50  import java.util.Map;
51  
52  /**
53   * Cash Analysis Selection.
54   */
55  public class MoneyWiseCashAnalysisSelect
56          implements MoneyWiseAnalysisFilterSelection, OceanusEventProvider<PrometheusDataEvent> {
57      /**
58       * Text for Category Label.
59       */
60      private static final String NLS_CATEGORY = MoneyWiseBasicDataType.CASHCATEGORY.getItemName();
61  
62      /**
63       * Text for Account Label.
64       */
65      private static final String NLS_CASH = MoneyWiseBasicDataType.CASH.getItemName();
66  
67      /**
68       * The Event Manager.
69       */
70      private final OceanusEventManager<PrometheusDataEvent> theEventManager;
71  
72      /**
73       * The panel.
74       */
75      private final TethysUIBoxPaneManager thePanel;
76  
77      /**
78       * The cash button.
79       */
80      private final TethysUIScrollButtonManager<MoneyWiseAnalysisCashBucket> theCashButton;
81  
82      /**
83       * The category button.
84       */
85      private final TethysUIScrollButtonManager<MoneyWiseCashCategory> theCatButton;
86  
87      /**
88       * Category menu.
89       */
90      private final TethysUIScrollMenu<MoneyWiseCashCategory> theCategoryMenu;
91  
92      /**
93       * Cash menu.
94       */
95      private final TethysUIScrollMenu<MoneyWiseAnalysisCashBucket> theCashMenu;
96  
97      /**
98       * The active category bucket list.
99       */
100     private MoneyWiseAnalysisCashCategoryBucketList theCategories;
101 
102     /**
103      * The active cash bucket list.
104      */
105     private MoneyWiseAnalysisCashBucketList theCash;
106 
107     /**
108      * The state.
109      */
110     private MoneyWiseCashState theState;
111 
112     /**
113      * The savePoint.
114      */
115     private MoneyWiseCashState theSavePoint;
116 
117     /**
118      * Constructor.
119      *
120      * @param pFactory the GUI factory
121      */
122     protected MoneyWiseCashAnalysisSelect(final TethysUIFactory<?> pFactory) {
123         /* Create the cash button */
124         final TethysUIButtonFactory<?> myButtons = pFactory.buttonFactory();
125         theCashButton = myButtons.newScrollButton(MoneyWiseAnalysisCashBucket.class);
126 
127         /* Create the category button */
128         theCatButton = myButtons.newScrollButton(MoneyWiseCashCategory.class);
129 
130         /* Create Event Manager */
131         theEventManager = new OceanusEventManager<>();
132 
133         /* Create the labels */
134         final TethysUIControlFactory myControls = pFactory.controlFactory();
135         final TethysUILabel myCatLabel = myControls.newLabel(NLS_CATEGORY + TethysUIConstant.STR_COLON);
136         final TethysUILabel myCshLabel = myControls.newLabel(NLS_CASH + TethysUIConstant.STR_COLON);
137 
138         /* Define the layout */
139         thePanel = pFactory.paneFactory().newHBoxPane();
140         thePanel.addSpacer();
141         thePanel.addNode(myCatLabel);
142         thePanel.addNode(theCatButton);
143         thePanel.addStrut();
144         thePanel.addNode(myCshLabel);
145         thePanel.addNode(theCashButton);
146 
147         /* Create initial state */
148         theState = new MoneyWiseCashState();
149         theState.applyState();
150 
151         /* Access the menus */
152         theCategoryMenu = theCatButton.getMenu();
153         theCashMenu = theCashButton.getMenu();
154 
155         /* Create the listeners */
156         OceanusEventRegistrar<TethysUIEvent> myRegistrar = theCatButton.getEventRegistrar();
157         myRegistrar.addEventListener(TethysUIEvent.NEWVALUE, e -> handleNewCategory());
158         theCatButton.setMenuConfigurator(e -> buildCategoryMenu());
159         myRegistrar = theCashButton.getEventRegistrar();
160         myRegistrar.addEventListener(TethysUIEvent.NEWVALUE, e -> handleNewCash());
161         theCashButton.setMenuConfigurator(e -> buildCashMenu());
162     }
163 
164     @Override
165     public TethysUIComponent getUnderlying() {
166         return thePanel;
167     }
168 
169     @Override
170     public OceanusEventRegistrar<PrometheusDataEvent> getEventRegistrar() {
171         return theEventManager.getEventRegistrar();
172     }
173 
174     @Override
175     public MoneyWiseAnalysisCashFilter getFilter() {
176         return theState.getFilter();
177     }
178 
179     @Override
180     public boolean isAvailable() {
181         return theCash != null
182                 && !theCash.isEmpty();
183     }
184 
185     /**
186      * Create SavePoint.
187      */
188     protected void createSavePoint() {
189         /* Create the savePoint */
190         theSavePoint = new MoneyWiseCashState(theState);
191     }
192 
193     /**
194      * Restore SavePoint.
195      */
196     protected void restoreSavePoint() {
197         /* Restore the savePoint */
198         theState = new MoneyWiseCashState(theSavePoint);
199 
200         /* Apply the state */
201         theState.applyState();
202     }
203 
204     @Override
205     public void setEnabled(final boolean bEnabled) {
206         /* Determine whether there are any Accounts to select */
207         final boolean csAvailable = bEnabled && isAvailable();
208 
209         /* Pass call on to buttons */
210         theCashButton.setEnabled(csAvailable);
211         theCatButton.setEnabled(csAvailable);
212     }
213 
214     @Override
215     public void setVisible(final boolean pVisible) {
216         thePanel.setVisible(pVisible);
217     }
218 
219     /**
220      * Set analysis.
221      *
222      * @param pAnalysis the analysis.
223      */
224     public void setAnalysis(final MoneyWiseAnalysis pAnalysis) {
225         /* Access buckets */
226         theCategories = pAnalysis.getCashCategories();
227         theCash = pAnalysis.getCash();
228 
229         /* Obtain the current cash */
230         MoneyWiseAnalysisCashBucket myCash = theState.getCash();
231 
232         /* Switch to versions from the analysis */
233         myCash = myCash != null
234                 ? theCash.getMatchingCash(myCash.getAccount())
235                 : theCash.getDefaultCash();
236 
237         /* Set the cash */
238         theState.setTheCash(myCash);
239         theState.setDateRange(pAnalysis.getDateRange());
240         theState.applyState();
241     }
242 
243     @Override
244     public void setFilter(final MoneyWiseAnalysisFilter<?, ?> pFilter) {
245         /* If this is the correct filter type */
246         if (pFilter instanceof MoneyWiseAnalysisCashFilter) {
247             /* Access filter */
248             final MoneyWiseAnalysisCashFilter myFilter = (MoneyWiseAnalysisCashFilter) pFilter;
249 
250             /* Obtain the filter bucket */
251             MoneyWiseAnalysisCashBucket myCash = myFilter.getBucket();
252 
253             /* Obtain equivalent bucket */
254             myCash = theCash.getMatchingCash(myCash.getAccount());
255 
256             /* Set the cash */
257             theState.setTheCash(myCash);
258             theState.setDateRange(myFilter.getDateRange());
259             theState.applyState();
260         }
261     }
262 
263     /**
264      * Obtain the default Cash for the category.
265      *
266      * @param pCategory the category
267      * @return the bucket
268      */
269     protected MoneyWiseAnalysisCashBucket getDefaultCash(final MoneyWiseCashCategory pCategory) {
270         return theCash.getDefaultCash(pCategory);
271     }
272 
273     /**
274      * Handle new Category.
275      */
276     private void handleNewCategory() {
277         /* Select the new category */
278         if (theState.setCategory(theCatButton.getValue())) {
279             theState.applyState();
280             theEventManager.fireEvent(PrometheusDataEvent.SELECTIONCHANGED);
281         }
282     }
283 
284     /**
285      * Handle new Cash.
286      */
287     private void handleNewCash() {
288         /* Select the new cash */
289         if (theState.setCash(theCashButton.getValue())) {
290             theState.applyState();
291             theEventManager.fireEvent(PrometheusDataEvent.SELECTIONCHANGED);
292         }
293     }
294 
295     /**
296      * Build Category menu.
297      */
298     private void buildCategoryMenu() {
299         /* Reset the popUp menu */
300         theCategoryMenu.removeAllItems();
301 
302         /* Create a simple map for top-level categories */
303         final Map<String, TethysUIScrollSubMenu<MoneyWiseCashCategory>> myMap = new HashMap<>();
304 
305         /* Record active item */
306         final MoneyWiseCashCategory myCurrent = theState.getCategory();
307         TethysUIScrollItem<MoneyWiseCashCategory> myActive = null;
308 
309         /* Loop through the available category values */
310         final Iterator<MoneyWiseAnalysisCashCategoryBucket> myIterator = theCategories.iterator();
311         while (myIterator.hasNext()) {
312             final MoneyWiseAnalysisCashCategoryBucket myBucket = myIterator.next();
313 
314             /* Only process low-level items */
315             if (myBucket.getAccountCategory().isCategoryClass(MoneyWiseCashCategoryClass.PARENT)) {
316                 continue;
317             }
318 
319             /* Determine menu to add to */
320             final MoneyWiseCashCategory myParent = myBucket.getAccountCategory().getParentCategory();
321             final String myParentName = myParent.getName();
322             final TethysUIScrollSubMenu<MoneyWiseCashCategory> myMenu = myMap.computeIfAbsent(myParentName, theCategoryMenu::addSubMenu);
323 
324             /* Create a new JMenuItem and add it to the popUp */
325             final MoneyWiseCashCategory myCategory = myBucket.getAccountCategory();
326             final TethysUIScrollItem<MoneyWiseCashCategory> myItem = myMenu.getSubMenu().addItem(myCategory, myCategory.getSubCategory());
327 
328             /* If this is the active category */
329             if (myCategory.equals(myCurrent)) {
330                 /* Record it */
331                 myActive = myItem;
332             }
333         }
334 
335         /* Ensure active item is visible */
336         if (myActive != null) {
337             myActive.scrollToItem();
338         }
339     }
340 
341     /**
342      * Build Cash menu.
343      */
344     private void buildCashMenu() {
345         /* Reset the popUp menu */
346         theCashMenu.removeAllItems();
347 
348         /* Access current category and Account */
349         final MoneyWiseCashCategory myCategory = theState.getCategory();
350         final MoneyWiseAnalysisCashBucket myCash = theState.getCash();
351 
352         /* Record active item */
353         TethysUIScrollItem<MoneyWiseAnalysisCashBucket> myActive = null;
354 
355         /* Loop through the available account values */
356         final Iterator<MoneyWiseAnalysisCashBucket> myIterator = theCash.iterator();
357         while (myIterator.hasNext()) {
358             final MoneyWiseAnalysisCashBucket myBucket = myIterator.next();
359 
360             /* Ignore if not the correct category */
361             if (!MetisDataDifference.isEqual(myCategory, myBucket.getCategory())) {
362                 continue;
363             }
364 
365             /* Create a new JMenuItem and add it to the popUp */
366             final TethysUIScrollItem<MoneyWiseAnalysisCashBucket> myItem = theCashMenu.addItem(myBucket);
367 
368             /* If this is the active cash */
369             if (myBucket.equals(myCash)) {
370                 /* Record it */
371                 myActive = myItem;
372             }
373         }
374 
375         /* Ensure active item is visible */
376         if (myActive != null) {
377             myActive.scrollToItem();
378         }
379     }
380 
381     /**
382      * SavePoint values.
383      */
384     private final class MoneyWiseCashState {
385         /**
386          * The active Category.
387          */
388         private MoneyWiseCashCategory theCategory;
389 
390         /**
391          * The active CashBucket.
392          */
393         private MoneyWiseAnalysisCashBucket theCash;
394 
395         /**
396          * The dateRange.
397          */
398         private OceanusDateRange theDateRange;
399 
400         /**
401          * The active Filter.
402          */
403         private MoneyWiseAnalysisCashFilter theFilter;
404 
405         /**
406          * Constructor.
407          */
408         private MoneyWiseCashState() {
409         }
410 
411         /**
412          * Constructor.
413          *
414          * @param pState state to copy from
415          */
416         private MoneyWiseCashState(final MoneyWiseCashState pState) {
417             /* Initialise state */
418             theCash = pState.getCash();
419             theCategory = pState.getCategory();
420             theDateRange = pState.getDateRange();
421             theFilter = pState.getFilter();
422         }
423 
424         /**
425          * Obtain the Cash Bucket.
426          *
427          * @return the Cash
428          */
429         private MoneyWiseAnalysisCashBucket getCash() {
430             return theCash;
431         }
432 
433         /**
434          * Obtain the Category.
435          *
436          * @return the category
437          */
438         private MoneyWiseCashCategory getCategory() {
439             return theCategory;
440         }
441 
442         /**
443          * Obtain the dateRange.
444          *
445          * @return the dateRange
446          */
447         private OceanusDateRange getDateRange() {
448             return theDateRange;
449         }
450 
451         /**
452          * Obtain the Filter.
453          *
454          * @return the filter
455          */
456         private MoneyWiseAnalysisCashFilter getFilter() {
457             return theFilter;
458         }
459 
460         /**
461          * Set new Cash Account.
462          *
463          * @param pCash the Cash Account
464          * @return true/false did a change occur
465          */
466         private boolean setCash(final MoneyWiseAnalysisCashBucket pCash) {
467             /* Adjust the selected cash */
468             if (!MetisDataDifference.isEqual(pCash, theCash)) {
469                 /* Store the cash */
470                 setTheCash(pCash);
471                 return true;
472             }
473             return false;
474         }
475 
476         /**
477          * Set the Cash.
478          *
479          * @param pCash the Cash
480          */
481         private void setTheCash(final MoneyWiseAnalysisCashBucket pCash) {
482             /* Access category for account */
483             final MoneyWiseCashCategory myCategory = pCash == null
484                     ? null
485                     : pCash.getCategory();
486             setTheCash(myCategory, pCash);
487         }
488 
489         /**
490          * Set the Cash.
491          *
492          * @param pCategory the category
493          * @param pCash     the Cash
494          */
495         private void setTheCash(final MoneyWiseCashCategory pCategory,
496                                 final MoneyWiseAnalysisCashBucket pCash) {
497             /* Store the cash */
498             theCash = pCash;
499             theCategory = pCategory;
500 
501             /* Access filter */
502             if (theCash != null) {
503                 theFilter = new MoneyWiseAnalysisCashFilter(theCash);
504                 theFilter.setDateRange(theDateRange);
505             } else {
506                 theFilter = null;
507             }
508         }
509 
510         /**
511          * Set new Category.
512          *
513          * @param pCategory the Category
514          * @return true/false did a change occur
515          */
516         private boolean setCategory(final MoneyWiseCashCategory pCategory) {
517             /* Adjust the selected category */
518             if (!MetisDataDifference.isEqual(pCategory, theCategory)) {
519                 setTheCash(pCategory, getDefaultCash(pCategory));
520                 return true;
521             }
522             return false;
523         }
524 
525         /**
526          * Set the dateRange.
527          *
528          * @param pRange the dateRange
529          */
530         private void setDateRange(final OceanusDateRange pRange) {
531             /* Store the dateRange */
532             theDateRange = pRange;
533             if (theFilter != null) {
534                 theFilter.setDateRange(theDateRange);
535             }
536         }
537 
538         /**
539          * Apply the State.
540          */
541         private void applyState() {
542             /* Adjust the lock-down */
543             setEnabled(true);
544             theCashButton.setValue(theCash);
545             theCatButton.setValue(theCategory);
546         }
547     }
548 }