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.MoneyWiseLoanCategory;
26  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseLoanCategoryClass;
27  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysis;
28  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisLoanBucket;
29  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisLoanBucket.MoneyWiseAnalysisLoanBucketList;
30  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisLoanCategoryBucket;
31  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisLoanCategoryBucket.MoneyWiseAnalysisLoanCategoryBucketList;
32  import io.github.tonywasher.joceanus.moneywise.lethe.views.MoneyWiseAnalysisFilter;
33  import io.github.tonywasher.joceanus.moneywise.lethe.views.MoneyWiseAnalysisFilter.MoneyWiseAnalysisLoanFilter;
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   * Loan Analysis Selection.
54   */
55  public class MoneyWiseLoanAnalysisSelect
56          implements MoneyWiseAnalysisFilterSelection, OceanusEventProvider<PrometheusDataEvent> {
57      /**
58       * Text for Category Label.
59       */
60      private static final String NLS_CATEGORY = MoneyWiseBasicDataType.LOANCATEGORY.getItemName();
61  
62      /**
63       * Text for Loan Label.
64       */
65      private static final String NLS_LOAN = MoneyWiseBasicDataType.LOAN.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 loan button.
79       */
80      private final TethysUIScrollButtonManager<MoneyWiseAnalysisLoanBucket> theLoanButton;
81  
82      /**
83       * The category button.
84       */
85      private final TethysUIScrollButtonManager<MoneyWiseLoanCategory> theCatButton;
86  
87      /**
88       * Category menu.
89       */
90      private final TethysUIScrollMenu<MoneyWiseLoanCategory> theCategoryMenu;
91  
92      /**
93       * Loan menu.
94       */
95      private final TethysUIScrollMenu<MoneyWiseAnalysisLoanBucket> theLoanMenu;
96  
97      /**
98       * The active category bucket list.
99       */
100     private MoneyWiseAnalysisLoanCategoryBucketList theCategories;
101 
102     /**
103      * The active loan bucket list.
104      */
105     private MoneyWiseAnalysisLoanBucketList theLoans;
106 
107     /**
108      * The state.
109      */
110     private MoneyWiseLoanState theState;
111 
112     /**
113      * The savePoint.
114      */
115     private MoneyWiseLoanState theSavePoint;
116 
117     /**
118      * Constructor.
119      *
120      * @param pFactory the GUI factory
121      */
122     protected MoneyWiseLoanAnalysisSelect(final TethysUIFactory<?> pFactory) {
123         /* Create the loan button */
124         final TethysUIButtonFactory<?> myButtons = pFactory.buttonFactory();
125         theLoanButton = myButtons.newScrollButton(MoneyWiseAnalysisLoanBucket.class);
126 
127         /* Create the category button */
128         theCatButton = myButtons.newScrollButton(MoneyWiseLoanCategory.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 myLoanLabel = myControls.newLabel(NLS_LOAN + 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(myLoanLabel);
145         thePanel.addNode(theLoanButton);
146 
147         /* Create initial state */
148         theState = new MoneyWiseLoanState();
149         theState.applyState();
150 
151         /* Access the menus */
152         theCategoryMenu = theCatButton.getMenu();
153         theLoanMenu = theLoanButton.getMenu();
154 
155         /* Create the listener */
156         OceanusEventRegistrar<TethysUIEvent> myRegistrar = theCatButton.getEventRegistrar();
157         myRegistrar.addEventListener(TethysUIEvent.NEWVALUE, e -> handleNewCategory());
158         theCatButton.setMenuConfigurator(e -> buildCategoryMenu());
159         myRegistrar = theLoanButton.getEventRegistrar();
160         myRegistrar.addEventListener(TethysUIEvent.NEWVALUE, e -> handleNewLoan());
161         theLoanButton.setMenuConfigurator(e -> buildLoanMenu());
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 MoneyWiseAnalysisLoanFilter getFilter() {
176         return theState.getFilter();
177     }
178 
179     @Override
180     public boolean isAvailable() {
181         return theLoans != null
182                 && !theLoans.isEmpty();
183     }
184 
185     /**
186      * Create SavePoint.
187      */
188     protected void createSavePoint() {
189         /* Create the savePoint */
190         theSavePoint = new MoneyWiseLoanState(theState);
191     }
192 
193     /**
194      * Restore SavePoint.
195      */
196     protected void restoreSavePoint() {
197         /* Restore the savePoint */
198         theState = new MoneyWiseLoanState(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 Loans to select */
207         final boolean lnAvailable = bEnabled && isAvailable();
208 
209         /* Pass call on to buttons */
210         theLoanButton.setEnabled(lnAvailable);
211         theCatButton.setEnabled(lnAvailable);
212     }
213 
214     /**
215      * Set analysis.
216      *
217      * @param pAnalysis the analysis.
218      */
219     public void setAnalysis(final MoneyWiseAnalysis pAnalysis) {
220         /* Access buckets */
221         theCategories = pAnalysis.getLoanCategories();
222         theLoans = pAnalysis.getLoans();
223 
224         /* Obtain the current account */
225         MoneyWiseAnalysisLoanBucket myLoan = theState.getLoan();
226 
227         /* Switch to versions from the analysis */
228         myLoan = myLoan != null
229                 ? theLoans.getMatchingLoan(myLoan.getAccount())
230                 : theLoans.getDefaultLoan();
231 
232         /* Set the loan */
233         theState.setTheLoan(myLoan);
234         theState.setDateRange(pAnalysis.getDateRange());
235         theState.applyState();
236     }
237 
238     @Override
239     public void setFilter(final MoneyWiseAnalysisFilter<?, ?> pFilter) {
240         /* If this is the correct filter type */
241         if (pFilter instanceof MoneyWiseAnalysisLoanFilter) {
242             /* Access filter */
243             final MoneyWiseAnalysisLoanFilter myFilter = (MoneyWiseAnalysisLoanFilter) pFilter;
244 
245             /* Obtain the filter bucket */
246             MoneyWiseAnalysisLoanBucket myLoan = myFilter.getBucket();
247 
248             /* Obtain equivalent bucket */
249             myLoan = theLoans.getMatchingLoan(myLoan.getAccount());
250 
251             /* Set the loan */
252             theState.setTheLoan(myLoan);
253             theState.setDateRange(myFilter.getDateRange());
254             theState.applyState();
255         }
256     }
257 
258     /**
259      * Obtain the default Loan for the category.
260      *
261      * @param pCategory the category
262      * @return the bucket
263      */
264     protected MoneyWiseAnalysisLoanBucket getDefaultLoan(final MoneyWiseLoanCategory pCategory) {
265         return theLoans.getDefaultLoan(pCategory);
266     }
267 
268     /**
269      * Handle new Category.
270      */
271     private void handleNewCategory() {
272         /* Select the new category */
273         if (theState.setCategory(theCatButton.getValue())) {
274             theState.applyState();
275             theEventManager.fireEvent(PrometheusDataEvent.SELECTIONCHANGED);
276         }
277     }
278 
279     /**
280      * Handle new Loan.
281      */
282     private void handleNewLoan() {
283         /* Select the new loan */
284         if (theState.setLoan(theLoanButton.getValue())) {
285             theState.applyState();
286             theEventManager.fireEvent(PrometheusDataEvent.SELECTIONCHANGED);
287         }
288     }
289 
290     /**
291      * Build Category menu.
292      */
293     private void buildCategoryMenu() {
294         /* Reset the popUp menu */
295         theCategoryMenu.removeAllItems();
296 
297         /* Create a simple map for top-level categories */
298         final Map<String, TethysUIScrollSubMenu<MoneyWiseLoanCategory>> myMap = new HashMap<>();
299 
300         /* Record active item */
301         final MoneyWiseLoanCategory myCurrent = theState.getCategory();
302         TethysUIScrollItem<MoneyWiseLoanCategory> myActive = null;
303 
304         /* Re-Loop through the available category values */
305         final Iterator<MoneyWiseAnalysisLoanCategoryBucket> myIterator = theCategories.iterator();
306         while (myIterator.hasNext()) {
307             final MoneyWiseAnalysisLoanCategoryBucket myBucket = myIterator.next();
308 
309             /* Only process low-level items */
310             if (myBucket.getAccountCategory().isCategoryClass(MoneyWiseLoanCategoryClass.PARENT)) {
311                 continue;
312             }
313 
314             /* Determine menu to add to */
315             final MoneyWiseLoanCategory myParent = myBucket.getAccountCategory().getParentCategory();
316             final String myParentName = myParent.getName();
317             TethysUIScrollSubMenu<MoneyWiseLoanCategory> myMenu = myMap.get(myParent.getName());
318 
319             /* If this is a new menu */
320             if (myMenu == null) {
321                 /* Create a new JMenu and add it to the popUp */
322                 myMenu = theCategoryMenu.addSubMenu(myParentName);
323                 myMap.put(myParentName, myMenu);
324             }
325 
326             /* Create a new JMenuItem and add it to the popUp */
327             final MoneyWiseLoanCategory myCategory = myBucket.getAccountCategory();
328             final TethysUIScrollItem<MoneyWiseLoanCategory> myItem = myMenu.getSubMenu().addItem(myCategory, myCategory.getSubCategory());
329 
330             /* If this is the active category */
331             if (myCategory.equals(myCurrent)) {
332                 /* Record it */
333                 myActive = myItem;
334             }
335         }
336 
337         /* Ensure active item is visible */
338         if (myActive != null) {
339             myActive.scrollToItem();
340         }
341     }
342 
343     /**
344      * Build Loan menu.
345      */
346     private void buildLoanMenu() {
347         /* Reset the popUp menu */
348         theLoanMenu.removeAllItems();
349 
350         /* Access current category and Loan */
351         final MoneyWiseLoanCategory myCategory = theState.getCategory();
352         final MoneyWiseAnalysisLoanBucket myLoan = theState.getLoan();
353 
354         /* Record active item */
355         TethysUIScrollItem<MoneyWiseAnalysisLoanBucket> myActive = null;
356 
357         /* Loop through the available account values */
358         final Iterator<MoneyWiseAnalysisLoanBucket> myIterator = theLoans.iterator();
359         while (myIterator.hasNext()) {
360             final MoneyWiseAnalysisLoanBucket myBucket = myIterator.next();
361 
362             /* Ignore if not the correct category */
363             if (!MetisDataDifference.isEqual(myCategory, myBucket.getCategory())) {
364                 continue;
365             }
366 
367             /* Create a new MenuItem and add it to the popUp */
368             final TethysUIScrollItem<MoneyWiseAnalysisLoanBucket> myItem = theLoanMenu.addItem(myBucket);
369 
370             /* If this is the active loan */
371             if (myBucket.equals(myLoan)) {
372                 /* Record it */
373                 myActive = myItem;
374             }
375         }
376 
377         /* Ensure active item is visible */
378         if (myActive != null) {
379             myActive.scrollToItem();
380         }
381     }
382 
383     /**
384      * SavePoint values.
385      */
386     private final class MoneyWiseLoanState {
387         /**
388          * The active Category.
389          */
390         private MoneyWiseLoanCategory theCategory;
391 
392         /**
393          * The active LoanBucket.
394          */
395         private MoneyWiseAnalysisLoanBucket theLoan;
396 
397         /**
398          * The dateRange.
399          */
400         private OceanusDateRange theDateRange;
401 
402         /**
403          * The active Filter.
404          */
405         private MoneyWiseAnalysisLoanFilter theFilter;
406 
407         /**
408          * Constructor.
409          */
410         private MoneyWiseLoanState() {
411         }
412 
413         /**
414          * Constructor.
415          *
416          * @param pState state to copy from
417          */
418         private MoneyWiseLoanState(final MoneyWiseLoanState pState) {
419             /* Initialise state */
420             theLoan = pState.getLoan();
421             theCategory = pState.getCategory();
422             theDateRange = pState.getDateRange();
423             theFilter = pState.getFilter();
424         }
425 
426         /**
427          * Obtain the Loan Bucket.
428          *
429          * @return the Loan
430          */
431         private MoneyWiseAnalysisLoanBucket getLoan() {
432             return theLoan;
433         }
434 
435         /**
436          * Obtain the Category.
437          *
438          * @return the category
439          */
440         private MoneyWiseLoanCategory getCategory() {
441             return theCategory;
442         }
443 
444         /**
445          * Obtain the dateRange.
446          *
447          * @return the dateRange
448          */
449         private OceanusDateRange getDateRange() {
450             return theDateRange;
451         }
452 
453         /**
454          * Obtain the Filter.
455          *
456          * @return the filter
457          */
458         private MoneyWiseAnalysisLoanFilter getFilter() {
459             return theFilter;
460         }
461 
462         /**
463          * Set new Loan Account.
464          *
465          * @param pLoan the Loan Account
466          * @return true/false did a change occur
467          */
468         private boolean setLoan(final MoneyWiseAnalysisLoanBucket pLoan) {
469             /* Adjust the selected loan */
470             if (!MetisDataDifference.isEqual(pLoan, theLoan)) {
471                 /* Set the Loan */
472                 setTheLoan(pLoan);
473                 return true;
474             }
475             return false;
476         }
477 
478         /**
479          * Set the Loan.
480          *
481          * @param pLoan the Loan
482          */
483         private void setTheLoan(final MoneyWiseAnalysisLoanBucket pLoan) {
484             /* Access category for account */
485             final MoneyWiseLoanCategory myCategory = pLoan == null
486                     ? null
487                     : pLoan.getCategory();
488             setTheLoan(myCategory, pLoan);
489         }
490 
491         /**
492          * Set the Loan.
493          *
494          * @param pCategory the category
495          * @param pLoan     the Loan
496          */
497         private void setTheLoan(final MoneyWiseLoanCategory pCategory,
498                                 final MoneyWiseAnalysisLoanBucket pLoan) {
499             /* Store the loan */
500             theLoan = pLoan;
501             theCategory = pCategory;
502 
503             /* Access filter */
504             if (theLoan != null) {
505                 theFilter = new MoneyWiseAnalysisLoanFilter(theLoan);
506                 theFilter.setDateRange(theDateRange);
507             } else {
508                 theFilter = null;
509             }
510         }
511 
512         /**
513          * Set new Category.
514          *
515          * @param pCategory the Category
516          * @return true/false did a change occur
517          */
518         private boolean setCategory(final MoneyWiseLoanCategory pCategory) {
519             /* Adjust the selected category */
520             if (!MetisDataDifference.isEqual(pCategory, theCategory)) {
521                 setTheLoan(pCategory, getDefaultLoan(pCategory));
522                 return true;
523             }
524             return false;
525         }
526 
527         /**
528          * Set the dateRange.
529          *
530          * @param pRange the dateRange
531          */
532         private void setDateRange(final OceanusDateRange pRange) {
533             /* Store the dateRange */
534             theDateRange = pRange;
535             if (theFilter != null) {
536                 theFilter.setDateRange(theDateRange);
537             }
538         }
539 
540         /**
541          * Apply the State.
542          */
543         private void applyState() {
544             /* Adjust the lock-down */
545             setEnabled(true);
546             theLoanButton.setValue(theLoan);
547             theCatButton.setValue(theCategory);
548         }
549     }
550 }