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.MoneyWiseXAnalysisPortfolioBucket;
26  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisPortfolioBucket.MoneyWiseXAnalysisPortfolioBucketList;
27  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisSecurityBucket;
28  import io.github.tonywasher.joceanus.moneywise.atlas.views.MoneyWiseXAnalysisFilter;
29  import io.github.tonywasher.joceanus.moneywise.atlas.views.MoneyWiseXAnalysisFilter.MoneyWiseXAnalysisSecurityFilter;
30  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
31  import io.github.tonywasher.joceanus.prometheus.views.PrometheusDataEvent;
32  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIComponent;
33  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIConstant;
34  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIEvent;
35  import io.github.tonywasher.joceanus.tethys.api.button.TethysUIButtonFactory;
36  import io.github.tonywasher.joceanus.tethys.api.button.TethysUIScrollButtonManager;
37  import io.github.tonywasher.joceanus.tethys.api.control.TethysUIControlFactory;
38  import io.github.tonywasher.joceanus.tethys.api.control.TethysUILabel;
39  import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;
40  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollItem;
41  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollMenu;
42  import io.github.tonywasher.joceanus.tethys.api.pane.TethysUIBoxPaneManager;
43  
44  import java.util.Iterator;
45  
46  /**
47   * Security Analysis Selection.
48   */
49  public class MoneyWiseXSecurityAnalysisSelect
50          implements MoneyWiseXAnalysisFilterSelection, OceanusEventProvider<PrometheusDataEvent> {
51      /**
52       * Text for Portfolio Label.
53       */
54      private static final String NLS_PORTFOLIO = MoneyWiseBasicDataType.PORTFOLIO.getItemName();
55  
56      /**
57       * Text for Security Label.
58       */
59      private static final String NLS_SECURITY = MoneyWiseBasicDataType.SECURITY.getItemName();
60  
61      /**
62       * The Event Manager.
63       */
64      private final OceanusEventManager<PrometheusDataEvent> theEventManager;
65  
66      /**
67       * The panel.
68       */
69      private final TethysUIBoxPaneManager thePanel;
70  
71      /**
72       * The security button.
73       */
74      private final TethysUIScrollButtonManager<MoneyWiseXAnalysisSecurityBucket> theSecButton;
75  
76      /**
77       * The portfolio button.
78       */
79      private final TethysUIScrollButtonManager<MoneyWiseXAnalysisPortfolioBucket> thePortButton;
80  
81      /**
82       * Portfolio menu.
83       */
84      private final TethysUIScrollMenu<MoneyWiseXAnalysisPortfolioBucket> thePortfolioMenu;
85  
86      /**
87       * Security menu.
88       */
89      private final TethysUIScrollMenu<MoneyWiseXAnalysisSecurityBucket> theSecurityMenu;
90  
91      /**
92       * The active portfolio bucket list.
93       */
94      private MoneyWiseXAnalysisPortfolioBucketList thePortfolios;
95  
96      /**
97       * The state.
98       */
99      private MoneyWiseSecurityState theState;
100 
101     /**
102      * The savePoint.
103      */
104     private MoneyWiseSecurityState theSavePoint;
105 
106     /**
107      * Constructor.
108      *
109      * @param pFactory the GUI factory
110      */
111     protected MoneyWiseXSecurityAnalysisSelect(final TethysUIFactory<?> pFactory) {
112         /* Create the security button */
113         final TethysUIButtonFactory<?> myButtons = pFactory.buttonFactory();
114         theSecButton = myButtons.newScrollButton(MoneyWiseXAnalysisSecurityBucket.class);
115 
116         /* Create the portfolio button */
117         thePortButton = myButtons.newScrollButton(MoneyWiseXAnalysisPortfolioBucket.class);
118 
119         /* Create Event Manager */
120         theEventManager = new OceanusEventManager<>();
121 
122         /* Create the labels */
123         final TethysUIControlFactory myControls = pFactory.controlFactory();
124         final TethysUILabel myPortLabel = myControls.newLabel(NLS_PORTFOLIO + TethysUIConstant.STR_COLON);
125         final TethysUILabel mySecLabel = myControls.newLabel(NLS_SECURITY + TethysUIConstant.STR_COLON);
126 
127         /* Define the layout */
128         thePanel = pFactory.paneFactory().newHBoxPane();
129         thePanel.addSpacer();
130         thePanel.addNode(myPortLabel);
131         thePanel.addNode(thePortButton);
132         thePanel.addStrut();
133         thePanel.addNode(mySecLabel);
134         thePanel.addNode(theSecButton);
135 
136         /* Create initial state */
137         theState = new MoneyWiseSecurityState();
138         theState.applyState();
139 
140         /* Access the menus */
141         thePortfolioMenu = thePortButton.getMenu();
142         theSecurityMenu = theSecButton.getMenu();
143 
144         /* Create the listener */
145         OceanusEventRegistrar<TethysUIEvent> myRegistrar = thePortButton.getEventRegistrar();
146         myRegistrar.addEventListener(TethysUIEvent.NEWVALUE, e -> handleNewPortfolio());
147         thePortButton.setMenuConfigurator(e -> buildPortfolioMenu());
148         myRegistrar = theSecButton.getEventRegistrar();
149         myRegistrar.addEventListener(TethysUIEvent.NEWVALUE, e -> handleNewSecurity());
150         theSecButton.setMenuConfigurator(e -> buildSecurityMenu());
151     }
152 
153     @Override
154     public TethysUIComponent getUnderlying() {
155         return thePanel;
156     }
157 
158     @Override
159     public OceanusEventRegistrar<PrometheusDataEvent> getEventRegistrar() {
160         return theEventManager.getEventRegistrar();
161     }
162 
163     @Override
164     public MoneyWiseXAnalysisSecurityFilter getFilter() {
165         return theState.getFilter();
166     }
167 
168     @Override
169     public boolean isAvailable() {
170         return thePortfolios != null
171                 && !thePortfolios.isEmpty();
172     }
173 
174     /**
175      * Create SavePoint.
176      */
177     public void createSavePoint() {
178         /* Create the savePoint */
179         theSavePoint = new MoneyWiseSecurityState(theState);
180     }
181 
182     /**
183      * Restore SavePoint.
184      */
185     public void restoreSavePoint() {
186         /* Restore the savePoint */
187         theState = new MoneyWiseSecurityState(theSavePoint);
188 
189         /* Apply the state */
190         theState.applyState();
191     }
192 
193     @Override
194     public void setEnabled(final boolean bEnabled) {
195         /* Determine whether there are any Securities to select */
196         final boolean secAvailable = bEnabled && isAvailable();
197 
198         /* Pass call on to buttons */
199         theSecButton.setEnabled(secAvailable);
200         thePortButton.setEnabled(secAvailable);
201     }
202 
203     @Override
204     public void setVisible(final boolean pVisible) {
205         thePanel.setVisible(pVisible);
206     }
207 
208     /**
209      * Set analysis.
210      *
211      * @param pAnalysis the analysis.
212      */
213     public void setAnalysis(final MoneyWiseXAnalysis pAnalysis) {
214         /* Access buckets */
215         thePortfolios = pAnalysis.getPortfolios();
216 
217         /* Obtain the current security */
218         MoneyWiseXAnalysisSecurityBucket mySecurity = theState.getSecurity();
219 
220         /* Switch to versions from the analysis */
221         mySecurity = mySecurity != null
222                 ? thePortfolios.getMatchingSecurityHolding(mySecurity.getSecurityHolding())
223                 : thePortfolios.getDefaultSecurityHolding();
224         final MoneyWiseXAnalysisPortfolioBucket myPortfolio = mySecurity != null
225                 ? thePortfolios.getMatchingPortfolio(mySecurity.getPortfolio())
226                 : thePortfolios.getDefaultPortfolio();
227 
228         /* Set the security */
229         theState.setTheSecurity(myPortfolio, mySecurity);
230         theState.setDateRange(pAnalysis.getDateRange());
231         theState.applyState();
232     }
233 
234     @Override
235     public void setFilter(final MoneyWiseXAnalysisFilter<?, ?> pFilter) {
236         /* If this is the correct filter type */
237         if (pFilter instanceof MoneyWiseXAnalysisSecurityFilter myFilter) {
238             /* Obtain the filter buckets */
239             MoneyWiseXAnalysisSecurityBucket mySecurity = myFilter.getBucket();
240 
241             /* Look for the equivalent buckets */
242             mySecurity = thePortfolios.getMatchingSecurityHolding(mySecurity.getSecurityHolding());
243 
244             /* Determine the matching portfolio bucket */
245             final MoneyWiseXAnalysisPortfolioBucket myPortfolio = thePortfolios.getMatchingPortfolio(mySecurity.getPortfolio());
246 
247             /* Set the security */
248             theState.setTheSecurity(myPortfolio, mySecurity);
249             theState.setDateRange(myFilter.getDateRange());
250             theState.applyState();
251         }
252     }
253 
254     /**
255      * Handle new Portfolio.
256      */
257     private void handleNewPortfolio() {
258         /* Select the new portfolio */
259         if (theState.setPortfolio(thePortButton.getValue())) {
260             theState.applyState();
261             theEventManager.fireEvent(PrometheusDataEvent.SELECTIONCHANGED);
262         }
263     }
264 
265     /**
266      * Handle new Security.
267      */
268     private void handleNewSecurity() {
269         /* Select the new security */
270         if (theState.setSecurity(theSecButton.getValue())) {
271             theState.applyState();
272             theEventManager.fireEvent(PrometheusDataEvent.SELECTIONCHANGED);
273         }
274     }
275 
276     /**
277      * Build Portfolio menu.
278      */
279     private void buildPortfolioMenu() {
280         /* Reset the popUp menu */
281         thePortfolioMenu.removeAllItems();
282 
283         /* Record active item */
284         TethysUIScrollItem<MoneyWiseXAnalysisPortfolioBucket> myActive = null;
285         final MoneyWiseXAnalysisPortfolioBucket myCurr = theState.getPortfolio();
286 
287         /* Loop through the available portfolio values */
288         final Iterator<MoneyWiseXAnalysisPortfolioBucket> myIterator = thePortfolios.iterator();
289         while (myIterator.hasNext()) {
290             final MoneyWiseXAnalysisPortfolioBucket myBucket = myIterator.next();
291 
292             /* Create a new MenuItem and add it to the popUp */
293             final TethysUIScrollItem<MoneyWiseXAnalysisPortfolioBucket> myItem = thePortfolioMenu.addItem(myBucket);
294 
295             /* If this is the active bucket */
296             if (myBucket.equals(myCurr)) {
297                 /* Record it */
298                 myActive = myItem;
299             }
300         }
301 
302         /* Ensure active item is visible */
303         if (myActive != null) {
304             myActive.scrollToItem();
305         }
306     }
307 
308     /**
309      * Build Security menu.
310      */
311     private void buildSecurityMenu() {
312         /* Reset the popUp menu */
313         theSecurityMenu.removeAllItems();
314 
315         /* Access current portfolio */
316         final MoneyWiseXAnalysisPortfolioBucket myPortfolio = theState.getPortfolio();
317         final MoneyWiseXAnalysisSecurityBucket myCurr = theState.getSecurity();
318         TethysUIScrollItem<MoneyWiseXAnalysisSecurityBucket> myActive = null;
319 
320         /* Loop through the available security values */
321         final Iterator<MoneyWiseXAnalysisSecurityBucket> myIterator = myPortfolio.securityIterator();
322         while (myIterator.hasNext()) {
323             final MoneyWiseXAnalysisSecurityBucket myBucket = myIterator.next();
324 
325             /* Create a new MenuItem and add it to the popUp */
326             final TethysUIScrollItem<MoneyWiseXAnalysisSecurityBucket> myItem = theSecurityMenu.addItem(myBucket, myBucket.getSecurityName());
327 
328             /* If this is the active bucket */
329             if (myBucket.equals(myCurr)) {
330                 /* Record it */
331                 myActive = myItem;
332             }
333         }
334 
335         /* Ensure active item is visible */
336         if (myActive != null) {
337             myActive.scrollToItem();
338         }
339     }
340 
341     /**
342      * SavePoint values.
343      */
344     private final class MoneyWiseSecurityState {
345         /**
346          * The active Portfolio.
347          */
348         private MoneyWiseXAnalysisPortfolioBucket thePortfolio;
349 
350         /**
351          * The active SecurityBucket.
352          */
353         private MoneyWiseXAnalysisSecurityBucket theSecurity;
354 
355         /**
356          * The dateRange.
357          */
358         private OceanusDateRange theDateRange;
359 
360         /**
361          * The filter.
362          */
363         private MoneyWiseXAnalysisSecurityFilter theFilter;
364 
365         /**
366          * Constructor.
367          */
368         private MoneyWiseSecurityState() {
369         }
370 
371         /**
372          * Constructor.
373          *
374          * @param pState state to copy from
375          */
376         private MoneyWiseSecurityState(final MoneyWiseSecurityState pState) {
377             /* Initialise state */
378             theSecurity = pState.getSecurity();
379             thePortfolio = pState.getPortfolio();
380             theDateRange = pState.getDateRange();
381             theFilter = pState.getFilter();
382         }
383 
384         /**
385          * Obtain the Security Bucket.
386          *
387          * @return the Security
388          */
389         private MoneyWiseXAnalysisSecurityBucket getSecurity() {
390             return theSecurity;
391         }
392 
393         /**
394          * Obtain the Portfolio.
395          *
396          * @return the portfolio
397          */
398         private MoneyWiseXAnalysisPortfolioBucket getPortfolio() {
399             return thePortfolio;
400         }
401 
402         /**
403          * Obtain the dateRange.
404          *
405          * @return the dateRange
406          */
407         private OceanusDateRange getDateRange() {
408             return theDateRange;
409         }
410 
411         /**
412          * Obtain the Filter.
413          *
414          * @return the filter
415          */
416         private MoneyWiseXAnalysisSecurityFilter getFilter() {
417             return theFilter;
418         }
419 
420         /**
421          * Set new Security.
422          *
423          * @param pSecurity the Security
424          * @return true/false did a change occur
425          */
426         private boolean setSecurity(final MoneyWiseXAnalysisSecurityBucket pSecurity) {
427             /* Adjust the selected security */
428             if (!MetisDataDifference.isEqual(pSecurity, theSecurity)) {
429                 /* Store the security */
430                 setTheSecurity(thePortfolio, pSecurity);
431                 return true;
432             }
433             return false;
434         }
435 
436         /**
437          * Set new Security.
438          *
439          * @param pPortfolio the Portfolio
440          * @param pSecurity  the Security
441          */
442         private void setTheSecurity(final MoneyWiseXAnalysisPortfolioBucket pPortfolio,
443                                     final MoneyWiseXAnalysisSecurityBucket pSecurity) {
444             /* Store the portfolio and security */
445             thePortfolio = pPortfolio;
446             theSecurity = pSecurity;
447             if (theSecurity != null) {
448                 theFilter = new MoneyWiseXAnalysisSecurityFilter(theSecurity);
449                 theFilter.setDateRange(theDateRange);
450             } else {
451                 theFilter = null;
452             }
453         }
454 
455         /**
456          * Set new Portfolio.
457          *
458          * @param pPortfolio the Portfolio
459          * @return true/false did a change occur
460          */
461         private boolean setPortfolio(final MoneyWiseXAnalysisPortfolioBucket pPortfolio) {
462             /* Adjust the selected portfolio */
463             if (!MetisDataDifference.isEqual(pPortfolio, thePortfolio)) {
464                 setTheSecurity(pPortfolio, getFirstSecurity(pPortfolio));
465                 return true;
466             }
467             return false;
468         }
469 
470         /**
471          * Obtain first security for portfolio.
472          *
473          * @param pPortfolio the portfolio
474          * @return the first security
475          */
476         private MoneyWiseXAnalysisSecurityBucket getFirstSecurity(final MoneyWiseXAnalysisPortfolioBucket pPortfolio) {
477             /* Loop through the available security values */
478             final Iterator<MoneyWiseXAnalysisSecurityBucket> myIterator = pPortfolio.securityIterator();
479             return myIterator.hasNext()
480                     ? myIterator.next()
481                     : null;
482         }
483 
484         /**
485          * Set the dateRange.
486          *
487          * @param pRange the dateRange
488          */
489         private void setDateRange(final OceanusDateRange pRange) {
490             /* Store the dateRange */
491             theDateRange = pRange;
492             if (theFilter != null) {
493                 theFilter.setDateRange(theDateRange);
494             }
495         }
496 
497         /**
498          * Apply the State.
499          */
500         private void applyState() {
501             /* Adjust the lock-down */
502             setEnabled(true);
503             theSecButton.setValue(theSecurity, theSecurity == null
504                     ? null
505                     : theSecurity.getSecurityName());
506             thePortButton.setValue(thePortfolio);
507         }
508     }
509 }