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