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