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