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.atlas.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.atlas.data.analysis.buckets.MoneyWiseXAnalysis;
25  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisCashBucket;
26  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisCashBucket.MoneyWiseXAnalysisCashBucketList;
27  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisCashCategoryBucket;
28  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisCashCategoryBucket.MoneyWiseXAnalysisCashCategoryBucketList;
29  import io.github.tonywasher.joceanus.moneywise.atlas.views.MoneyWiseXAnalysisFilter;
30  import io.github.tonywasher.joceanus.moneywise.atlas.views.MoneyWiseXAnalysisFilter.MoneyWiseXAnalysisCashFilter;
31  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
32  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCashCategory;
33  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCashCategoryClass;
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 MoneyWiseXCashAnalysisSelect
56          implements MoneyWiseXAnalysisFilterSelection, 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<MoneyWiseXAnalysisCashBucket> 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<MoneyWiseXAnalysisCashBucket> theCashMenu;
96  
97      /**
98       * The active category bucket list.
99       */
100     private MoneyWiseXAnalysisCashCategoryBucketList theCategories;
101 
102     /**
103      * The active cash bucket list.
104      */
105     private MoneyWiseXAnalysisCashBucketList 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 MoneyWiseXCashAnalysisSelect(final TethysUIFactory<?> pFactory) {
123         /* Create the cash button */
124         final TethysUIButtonFactory<?> myButtons = pFactory.buttonFactory();
125         theCashButton = myButtons.newScrollButton(MoneyWiseXAnalysisCashBucket.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 MoneyWiseXAnalysisCashFilter 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 MoneyWiseXAnalysis pAnalysis) {
225         /* Access buckets */
226         theCategories = pAnalysis.getCashCategories();
227         theCash = pAnalysis.getCash();
228 
229         /* Obtain the current cash */
230         MoneyWiseXAnalysisCashBucket 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 MoneyWiseXAnalysisFilter<?, ?> pFilter) {
245         /* If this is the correct filter type */
246         if (pFilter instanceof MoneyWiseXAnalysisCashFilter myFilter) {
247             /* Obtain the filter bucket */
248             MoneyWiseXAnalysisCashBucket myCash = myFilter.getBucket();
249 
250             /* Obtain equivalent bucket */
251             myCash = theCash.getMatchingCash(myCash.getAccount());
252 
253             /* Set the cash */
254             theState.setTheCash(myCash);
255             theState.setDateRange(myFilter.getDateRange());
256             theState.applyState();
257         }
258     }
259 
260     /**
261      * Obtain the default Cash for the category.
262      *
263      * @param pCategory the category
264      * @return the bucket
265      */
266     protected MoneyWiseXAnalysisCashBucket getDefaultCash(final MoneyWiseCashCategory pCategory) {
267         return theCash.getDefaultCash(pCategory);
268     }
269 
270     /**
271      * Handle new Category.
272      */
273     private void handleNewCategory() {
274         /* Select the new category */
275         if (theState.setCategory(theCatButton.getValue())) {
276             theState.applyState();
277             theEventManager.fireEvent(PrometheusDataEvent.SELECTIONCHANGED);
278         }
279     }
280 
281     /**
282      * Handle new Cash.
283      */
284     private void handleNewCash() {
285         /* Select the new cash */
286         if (theState.setCash(theCashButton.getValue())) {
287             theState.applyState();
288             theEventManager.fireEvent(PrometheusDataEvent.SELECTIONCHANGED);
289         }
290     }
291 
292     /**
293      * Build Category menu.
294      */
295     private void buildCategoryMenu() {
296         /* Reset the popUp menu */
297         theCategoryMenu.removeAllItems();
298 
299         /* Create a simple map for top-level categories */
300         final Map<String, TethysUIScrollSubMenu<MoneyWiseCashCategory>> myMap = new HashMap<>();
301 
302         /* Record active item */
303         final MoneyWiseCashCategory myCurrent = theState.getCategory();
304         TethysUIScrollItem<MoneyWiseCashCategory> myActive = null;
305 
306         /* Loop through the available category values */
307         final Iterator<MoneyWiseXAnalysisCashCategoryBucket> myIterator = theCategories.iterator();
308         while (myIterator.hasNext()) {
309             final MoneyWiseXAnalysisCashCategoryBucket myBucket = myIterator.next();
310 
311             /* Only process low-level items */
312             if (myBucket.getAccountCategory().isCategoryClass(MoneyWiseCashCategoryClass.PARENT)) {
313                 continue;
314             }
315 
316             /* Determine menu to add to */
317             final MoneyWiseCashCategory myParent = myBucket.getAccountCategory().getParentCategory();
318             final String myParentName = myParent.getName();
319             final TethysUIScrollSubMenu<MoneyWiseCashCategory> myMenu = myMap.computeIfAbsent(myParentName, theCategoryMenu::addSubMenu);
320 
321             /* Create a new JMenuItem and add it to the popUp */
322             final MoneyWiseCashCategory myCategory = myBucket.getAccountCategory();
323             final TethysUIScrollItem<MoneyWiseCashCategory> myItem = myMenu.getSubMenu().addItem(myCategory, myCategory.getSubCategory());
324 
325             /* If this is the active category */
326             if (myCategory.equals(myCurrent)) {
327                 /* Record it */
328                 myActive = myItem;
329             }
330         }
331 
332         /* Ensure active item is visible */
333         if (myActive != null) {
334             myActive.scrollToItem();
335         }
336     }
337 
338     /**
339      * Build Cash menu.
340      */
341     private void buildCashMenu() {
342         /* Reset the popUp menu */
343         theCashMenu.removeAllItems();
344 
345         /* Access current category and Account */
346         final MoneyWiseCashCategory myCategory = theState.getCategory();
347         final MoneyWiseXAnalysisCashBucket myCash = theState.getCash();
348 
349         /* Record active item */
350         TethysUIScrollItem<MoneyWiseXAnalysisCashBucket> myActive = null;
351 
352         /* Loop through the available account values */
353         final Iterator<MoneyWiseXAnalysisCashBucket> myIterator = theCash.iterator();
354         while (myIterator.hasNext()) {
355             final MoneyWiseXAnalysisCashBucket myBucket = myIterator.next();
356 
357             /* Ignore if not the correct category */
358             if (!MetisDataDifference.isEqual(myCategory, myBucket.getCategory())) {
359                 continue;
360             }
361 
362             /* Create a new JMenuItem and add it to the popUp */
363             final TethysUIScrollItem<MoneyWiseXAnalysisCashBucket> myItem = theCashMenu.addItem(myBucket);
364 
365             /* If this is the active cash */
366             if (myBucket.equals(myCash)) {
367                 /* Record it */
368                 myActive = myItem;
369             }
370         }
371 
372         /* Ensure active item is visible */
373         if (myActive != null) {
374             myActive.scrollToItem();
375         }
376     }
377 
378     /**
379      * SavePoint values.
380      */
381     private final class MoneyWiseCashState {
382         /**
383          * The active Category.
384          */
385         private MoneyWiseCashCategory theCategory;
386 
387         /**
388          * The active CashBucket.
389          */
390         private MoneyWiseXAnalysisCashBucket theCash;
391 
392         /**
393          * The dateRange.
394          */
395         private OceanusDateRange theDateRange;
396 
397         /**
398          * The active Filter.
399          */
400         private MoneyWiseXAnalysisCashFilter theFilter;
401 
402         /**
403          * Constructor.
404          */
405         private MoneyWiseCashState() {
406         }
407 
408         /**
409          * Constructor.
410          *
411          * @param pState state to copy from
412          */
413         private MoneyWiseCashState(final MoneyWiseCashState pState) {
414             /* Initialise state */
415             theCash = pState.getCash();
416             theCategory = pState.getCategory();
417             theDateRange = pState.getDateRange();
418             theFilter = pState.getFilter();
419         }
420 
421         /**
422          * Obtain the Cash Bucket.
423          *
424          * @return the Cash
425          */
426         private MoneyWiseXAnalysisCashBucket getCash() {
427             return theCash;
428         }
429 
430         /**
431          * Obtain the Category.
432          *
433          * @return the category
434          */
435         private MoneyWiseCashCategory getCategory() {
436             return theCategory;
437         }
438 
439         /**
440          * Obtain the dateRange.
441          *
442          * @return the dateRange
443          */
444         private OceanusDateRange getDateRange() {
445             return theDateRange;
446         }
447 
448         /**
449          * Obtain the Filter.
450          *
451          * @return the filter
452          */
453         private MoneyWiseXAnalysisCashFilter getFilter() {
454             return theFilter;
455         }
456 
457         /**
458          * Set new Cash Account.
459          *
460          * @param pCash the Cash Account
461          * @return true/false did a change occur
462          */
463         private boolean setCash(final MoneyWiseXAnalysisCashBucket pCash) {
464             /* Adjust the selected cash */
465             if (!MetisDataDifference.isEqual(pCash, theCash)) {
466                 /* Store the cash */
467                 setTheCash(pCash);
468                 return true;
469             }
470             return false;
471         }
472 
473         /**
474          * Set the Cash.
475          *
476          * @param pCash the Cash
477          */
478         private void setTheCash(final MoneyWiseXAnalysisCashBucket pCash) {
479             /* Access category for account */
480             final MoneyWiseCashCategory myCategory = pCash == null
481                     ? null
482                     : pCash.getCategory();
483             setTheCash(myCategory, pCash);
484         }
485 
486         /**
487          * Set the Cash.
488          *
489          * @param pCategory the category
490          * @param pCash     the Cash
491          */
492         private void setTheCash(final MoneyWiseCashCategory pCategory,
493                                 final MoneyWiseXAnalysisCashBucket pCash) {
494             /* Store the cash */
495             theCash = pCash;
496             theCategory = pCategory;
497 
498             /* Access filter */
499             if (theCash != null) {
500                 theFilter = new MoneyWiseXAnalysisCashFilter(theCash);
501                 theFilter.setDateRange(theDateRange);
502             } else {
503                 theFilter = null;
504             }
505         }
506 
507         /**
508          * Set new Category.
509          *
510          * @param pCategory the Category
511          * @return true/false did a change occur
512          */
513         private boolean setCategory(final MoneyWiseCashCategory pCategory) {
514             /* Adjust the selected category */
515             if (!MetisDataDifference.isEqual(pCategory, theCategory)) {
516                 setTheCash(pCategory, getDefaultCash(pCategory));
517                 return true;
518             }
519             return false;
520         }
521 
522         /**
523          * Set the dateRange.
524          *
525          * @param pRange the dateRange
526          */
527         private void setDateRange(final OceanusDateRange pRange) {
528             /* Store the dateRange */
529             theDateRange = pRange;
530             if (theFilter != null) {
531                 theFilter.setDateRange(theDateRange);
532             }
533         }
534 
535         /**
536          * Apply the State.
537          */
538         private void applyState() {
539             /* Adjust the lock-down */
540             setEnabled(true);
541             theCashButton.setValue(theCash);
542             theCatButton.setValue(theCategory);
543         }
544     }
545 }