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.OceanusDatePeriod;
20  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateRange;
21  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventManager;
22  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar;
23  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar.OceanusEventProvider;
24  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
25  import io.github.tonywasher.joceanus.metis.ui.MetisIcon;
26  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysis;
27  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisPortfolioBucket;
28  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisPortfolioBucket.MoneyWiseAnalysisPortfolioBucketList;
29  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisSecurityBucket;
30  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisSecurityBucket.MoneyWiseAnalysisSecurityBucketList;
31  import io.github.tonywasher.joceanus.moneywise.lethe.reports.MoneyWiseReportType;
32  import io.github.tonywasher.joceanus.moneywise.ui.MoneyWiseUIResource;
33  import io.github.tonywasher.joceanus.prometheus.views.PrometheusDataEvent;
34  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIComponent;
35  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIEvent;
36  import io.github.tonywasher.joceanus.tethys.api.button.TethysUIButton;
37  import io.github.tonywasher.joceanus.tethys.api.button.TethysUIButtonFactory;
38  import io.github.tonywasher.joceanus.tethys.api.button.TethysUIDateRangeSelector;
39  import io.github.tonywasher.joceanus.tethys.api.button.TethysUIScrollButtonManager;
40  import io.github.tonywasher.joceanus.tethys.api.control.TethysUILabel;
41  import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;
42  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollItem;
43  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollMenu;
44  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollSubMenu;
45  import io.github.tonywasher.joceanus.tethys.api.pane.TethysUIBoxPaneManager;
46  
47  import java.util.Iterator;
48  
49  /**
50   * Report selection panel.
51   */
52  public class MoneyWiseReportSelect
53          implements OceanusEventProvider<PrometheusDataEvent>, TethysUIComponent {
54      /**
55       * Text for Report Label.
56       */
57      private static final String NLS_REPORT = MoneyWiseUIResource.REPORT_PROMPT.getValue();
58  
59      /**
60       * Text for Selection Title.
61       */
62      private static final String NLS_TITLE = MoneyWiseUIResource.REPORT_TITLE.getValue();
63  
64      /**
65       * The Event Manager.
66       */
67      private final OceanusEventManager<PrometheusDataEvent> theEventManager;
68  
69      /**
70       * The panel.
71       */
72      private final TethysUIBoxPaneManager thePanel;
73  
74      /**
75       * Reports scroll button.
76       */
77      private final TethysUIScrollButtonManager<MoneyWiseReportType> theReportButton;
78  
79      /**
80       * Holding scroll button.
81       */
82      private final TethysUIScrollButtonManager<MoneyWiseAnalysisSecurityBucket> theHoldingButton;
83  
84      /**
85       * Range select.
86       */
87      private final TethysUIDateRangeSelector theRangeSelect;
88  
89      /**
90       * Print button.
91       */
92      private final TethysUIButton thePrintButton;
93  
94      /**
95       * Save button.
96       */
97      private final TethysUIButton theSaveButton;
98  
99      /**
100      * Current state.
101      */
102     private MoneyWiseReportState theState;
103 
104     /**
105      * Saved state.
106      */
107     private MoneyWiseReportState theSavePoint;
108 
109     /**
110      * Active flag.
111      */
112     private boolean isActive;
113 
114     /**
115      * Constructor.
116      *
117      * @param pFactory the GUI factory
118      */
119     public MoneyWiseReportSelect(final TethysUIFactory<?> pFactory) {
120         /* Create the buttons */
121         final TethysUIButtonFactory<?> myButtons = pFactory.buttonFactory();
122         theReportButton = myButtons.newScrollButton(MoneyWiseReportType.class);
123         theHoldingButton = myButtons.newScrollButton(MoneyWiseAnalysisSecurityBucket.class);
124 
125         /* Create the Range Select and disable its border */
126         theRangeSelect = myButtons.newDateRangeSelector();
127 
128         /* Create the labels */
129         final TethysUILabel myRepLabel = pFactory.controlFactory().newLabel(NLS_REPORT);
130 
131         /* Create the print button */
132         thePrintButton = myButtons.newButton();
133         MetisIcon.configurePrintIconButton(thePrintButton);
134 
135         /* Create the save button */
136         theSaveButton = myButtons.newButton();
137         MetisIcon.configureSaveIconButton(theSaveButton);
138 
139         /* Create initial state */
140         theState = new MoneyWiseReportState();
141         theState.setRange(theRangeSelect);
142         theState.setType(MoneyWiseReportType.getDefault());
143 
144         /* Create Event Manager */
145         theEventManager = new OceanusEventManager<>();
146 
147         /* Create the selection panel */
148         thePanel = pFactory.paneFactory().newHBoxPane();
149         thePanel.setBorderTitle(NLS_TITLE);
150 
151         /* Define the layout */
152         thePanel.addNode(myRepLabel);
153         thePanel.addNode(theReportButton);
154         thePanel.addSpacer();
155         thePanel.addNode(theHoldingButton);
156         thePanel.addSpacer();
157         thePanel.addNode(theRangeSelect);
158         thePanel.addSpacer();
159         thePanel.addNode(thePrintButton);
160         thePanel.addNode(theSaveButton);
161 
162         /* Add the listeners */
163         theReportButton.getEventRegistrar().addEventListener(TethysUIEvent.NEWVALUE, e -> handleNewReport());
164         theReportButton.setMenuConfigurator(e -> buildReportMenu());
165         theHoldingButton.getEventRegistrar().addEventListener(TethysUIEvent.NEWVALUE, e -> handleNewSecurity());
166         theHoldingButton.setMenuConfigurator(e -> buildHoldingMenu());
167         thePrintButton.getEventRegistrar().addEventListener(e -> theEventManager.fireEvent(PrometheusDataEvent.PRINT));
168         theSaveButton.getEventRegistrar().addEventListener(e -> theEventManager.fireEvent(PrometheusDataEvent.SAVETOFILE));
169         theRangeSelect.getEventRegistrar().addEventListener(TethysUIEvent.NEWVALUE, e -> handleNewRange());
170     }
171 
172     @Override
173     public TethysUIComponent getUnderlying() {
174         return thePanel;
175     }
176 
177     @Override
178     public OceanusEventRegistrar<PrometheusDataEvent> getEventRegistrar() {
179         return theEventManager.getEventRegistrar();
180     }
181 
182     /**
183      * Obtain the report type.
184      *
185      * @return the report type
186      */
187     public MoneyWiseReportType getReportType() {
188         return theState.getType();
189     }
190 
191     /**
192      * Obtain the selected date range.
193      *
194      * @return the date range
195      */
196     public OceanusDateRange getDateRange() {
197         return theState.getRange();
198     }
199 
200     /**
201      * Obtain the securityBucket.
202      *
203      * @return the security
204      */
205     public MoneyWiseAnalysisSecurityBucket getSecurity() {
206         return theState.getSecurity();
207     }
208 
209     /**
210      * Obtain the date range selection control.
211      *
212      * @return the date range selection
213      */
214     public TethysUIDateRangeSelector getDateRangeSelector() {
215         return theRangeSelect;
216     }
217 
218     /**
219      * Build report menu.
220      */
221     private void buildReportMenu() {
222         /* Access builder */
223         final boolean hasSecurities = theState.hasSecurities();
224         final TethysUIScrollMenu<MoneyWiseReportType> myBuilder = theReportButton.getMenu();
225         myBuilder.removeAllItems();
226 
227         /* Loop through the reports */
228         for (MoneyWiseReportType myType : MoneyWiseReportType.values()) {
229             /* If we can produce the report */
230             if (hasSecurities
231                     || !myType.needSecurities()) {
232                 /* Create a new MenuItem for the report type */
233                 myBuilder.addItem(myType);
234             }
235         }
236     }
237 
238     /**
239      * Build holding menu.
240      */
241     private void buildHoldingMenu() {
242         /* Access state details */
243         final MoneyWiseAnalysis myAnalysis = theState.getAnalysis();
244         final MoneyWiseAnalysisSecurityBucket mySecurity = theState.getSecurity();
245         final MoneyWiseAnalysisPortfolioBucketList myPortfolios = myAnalysis.getPortfolios();
246 
247         /* Access builder */
248         final TethysUIScrollMenu<MoneyWiseAnalysisSecurityBucket> myBuilder = theHoldingButton.getMenu();
249         myBuilder.removeAllItems();
250         TethysUIScrollItem<MoneyWiseAnalysisSecurityBucket> myActive = null;
251 
252         /* Loop through the Portfolio Buckets */
253         final Iterator<MoneyWiseAnalysisPortfolioBucket> myPortIterator = myPortfolios.iterator();
254         while (myPortIterator.hasNext()) {
255             final MoneyWiseAnalysisPortfolioBucket myPortBucket = myPortIterator.next();
256 
257             /* Create subMenu */
258             final String myName = myPortBucket.getName();
259             final TethysUIScrollSubMenu<MoneyWiseAnalysisSecurityBucket> myMenu = myBuilder.addSubMenu(myName);
260 
261             /* Loop through the Security Buckets */
262             final MoneyWiseAnalysisSecurityBucketList mySecurities = myPortBucket.getSecurities();
263             final Iterator<MoneyWiseAnalysisSecurityBucket> myIterator = mySecurities.iterator();
264             while (myIterator.hasNext()) {
265                 final MoneyWiseAnalysisSecurityBucket myBucket = myIterator.next();
266 
267                 /* Add menuItem */
268                 final TethysUIScrollItem<MoneyWiseAnalysisSecurityBucket> myItem = myMenu.getSubMenu().addItem(myBucket, myBucket.getSecurityName());
269 
270                 /* Record active item */
271                 if (myBucket.equals(mySecurity)) {
272                     myActive = myItem;
273                 }
274             }
275         }
276 
277         /* Ensure that active item is displayed */
278         if (myActive != null) {
279             myActive.scrollToItem();
280         }
281     }
282 
283     /**
284      * Set the range for the date box.
285      *
286      * @param pRange the date range
287      */
288     public final void setRange(final OceanusDateRange pRange) {
289         /* Set up range */
290         theRangeSelect.setOverallRange(pRange);
291     }
292 
293     /**
294      * Set securities flag.
295      *
296      * @param pSecurities do we have securities?
297      */
298     public void setSecurities(final boolean pSecurities) {
299         theState.setSecurities(pSecurities);
300     }
301 
302     /**
303      * Set analysis.
304      *
305      * @param pAnalysis the analysis.
306      */
307     public void setAnalysis(final MoneyWiseAnalysis pAnalysis) {
308         /* Record the analysis */
309         theState.setAnalysis(pAnalysis);
310 
311         /* Access the portfolios */
312         final MoneyWiseAnalysisPortfolioBucketList myPortfolios = pAnalysis.getPortfolios();
313 
314         /* If we have an existing security bucket */
315         MoneyWiseAnalysisSecurityBucket mySecurity = theState.getSecurity();
316 
317         /* If we have a selected Security */
318         if (mySecurity != null) {
319             /* Look for the equivalent bucket */
320             mySecurity = myPortfolios.getMatchingSecurityHolding(mySecurity.getSecurityHolding());
321         }
322 
323         /* If we no longer have a selected security */
324         if (mySecurity == null) {
325             /* Obtain the default security holding */
326             mySecurity = myPortfolios.getDefaultSecurityHolding();
327         }
328 
329         /* Set the selected security */
330         theState.setSecurity(mySecurity);
331     }
332 
333     /**
334      * Set security.
335      *
336      * @param pSecurity the security.
337      */
338     public void setSecurity(final MoneyWiseAnalysisSecurityBucket pSecurity) {
339         /* Set the selected security */
340         theState.setSecurity(pSecurity);
341         theState.setType(MoneyWiseReportType.CAPITALGAINS);
342 
343         /* Notify that the state has changed */
344         theEventManager.fireEvent(PrometheusDataEvent.SELECTIONCHANGED);
345     }
346 
347     /**
348      * Create SavePoint.
349      */
350     public void createSavePoint() {
351         /* Create the savePoint */
352         theSavePoint = new MoneyWiseReportState(theState);
353     }
354 
355     /**
356      * Restore SavePoint.
357      */
358     public void restoreSavePoint() {
359         /* Restore the savePoint */
360         theState = new MoneyWiseReportState(theSavePoint);
361 
362         /* Apply the state */
363         theState.applyState();
364     }
365 
366     @Override
367     public void setEnabled(final boolean bEnable) {
368         theRangeSelect.setEnabled(bEnable);
369         theReportButton.setEnabled(bEnable);
370         theHoldingButton.setEnabled(bEnable);
371         thePrintButton.setEnabled(bEnable);
372         theSaveButton.setEnabled(bEnable);
373     }
374 
375     @Override
376     public void setVisible(final boolean pVisible) {
377         thePanel.setVisible(pVisible);
378     }
379 
380     /**
381      * Handle new report.
382      */
383     private void handleNewReport() {
384         /* Set active flag */
385         isActive = true;
386 
387         /* Look for a changed report type */
388         if (theState.setType(theReportButton.getValue())) {
389             /* Notify that the state has changed */
390             theEventManager.fireEvent(PrometheusDataEvent.SELECTIONCHANGED);
391         }
392 
393         /* Clear active flag */
394         isActive = false;
395     }
396 
397     /**
398      * Handle new holding.
399      */
400     private void handleNewSecurity() {
401         /* Set active flag */
402         isActive = true;
403 
404         /* Look for a changed report type */
405         if (theState.setSecurity(theHoldingButton.getValue())) {
406             /* Notify that the state has changed */
407             theEventManager.fireEvent(PrometheusDataEvent.SELECTIONCHANGED);
408         }
409 
410         /* Clear active flag */
411         isActive = false;
412     }
413 
414     /**
415      * Handle new range.
416      */
417     private void handleNewRange() {
418         /* if we have a changed range and are not changing report */
419         if (theState.setRange(theRangeSelect)
420                 && !isActive) {
421             /* Notify that the state has changed */
422             theEventManager.fireEvent(PrometheusDataEvent.SELECTIONCHANGED);
423         }
424     }
425 
426     /**
427      * SavePoint values.
428      */
429     private final class MoneyWiseReportState {
430         /**
431          * The analysis.
432          */
433         private MoneyWiseAnalysis theAnalysis;
434 
435         /**
436          * The selected range.
437          */
438         private OceanusDateRange theRange;
439 
440         /**
441          * Do we have securities?
442          */
443         private boolean hasSecurities;
444 
445         /**
446          * The securityBucket.
447          */
448         private MoneyWiseAnalysisSecurityBucket theSecurity;
449 
450         /**
451          * The selected report type.
452          */
453         private MoneyWiseReportType theType;
454 
455         /**
456          * Constructor.
457          */
458         private MoneyWiseReportState() {
459         }
460 
461         /**
462          * Constructor.
463          *
464          * @param pState state to copy from
465          */
466         private MoneyWiseReportState(final MoneyWiseReportState pState) {
467             theAnalysis = pState.getAnalysis();
468             theRange = pState.getRange();
469             hasSecurities = pState.hasSecurities();
470             theSecurity = pState.getSecurity();
471             theType = pState.getType();
472         }
473 
474         /**
475          * Obtain the analysis.
476          *
477          * @return the analysis
478          */
479         private MoneyWiseAnalysis getAnalysis() {
480             return theAnalysis;
481         }
482 
483         /**
484          * Obtain the selected range.
485          *
486          * @return the range
487          */
488         private OceanusDateRange getRange() {
489             return theRange;
490         }
491 
492         /**
493          * Do we have securities?
494          *
495          * @return true/false
496          */
497         private boolean hasSecurities() {
498             return hasSecurities;
499         }
500 
501         /**
502          * Obtain the security bucket.
503          *
504          * @return the bucket
505          */
506         private MoneyWiseAnalysisSecurityBucket getSecurity() {
507             return theSecurity;
508         }
509 
510         /**
511          * Obtain the selected report type.
512          *
513          * @return the report type
514          */
515         private MoneyWiseReportType getType() {
516             return theType;
517         }
518 
519         /**
520          * Set new Range.
521          *
522          * @param pSelect the Panel with the new range
523          * @return true/false did a change occur
524          */
525         private boolean setRange(final TethysUIDateRangeSelector pSelect) {
526             /* Adjust the date and build the new range */
527             final OceanusDateRange myRange = pSelect.getRange();
528             if (!MetisDataDifference.isEqual(myRange, theRange)) {
529                 theRange = new OceanusDateRange(myRange);
530                 return true;
531             }
532             return false;
533         }
534 
535         /**
536          * Set securities flag.
537          *
538          * @param pSecurities do we have securities?
539          */
540         private void setSecurities(final boolean pSecurities) {
541             /* Adjust the flag */
542             if (pSecurities != hasSecurities) {
543                 hasSecurities = pSecurities;
544                 if (!hasSecurities
545                         && theType.needSecurities()) {
546                     theType = MoneyWiseReportType.getDefault();
547                 }
548             }
549         }
550 
551         /**
552          * Set analysis.
553          *
554          * @param pAnalysis the analysis
555          */
556         private void setAnalysis(final MoneyWiseAnalysis pAnalysis) {
557             theAnalysis = pAnalysis;
558         }
559 
560         /**
561          * Set security.
562          *
563          * @param pSecurity the security
564          * @return true/false did a change occur
565          */
566         private boolean setSecurity(final MoneyWiseAnalysisSecurityBucket pSecurity) {
567             if (!pSecurity.equals(theSecurity)) {
568                 theSecurity = pSecurity;
569                 applyState();
570                 return true;
571             }
572             return false;
573         }
574 
575         /**
576          * Set new Report Type.
577          *
578          * @param pType the new type
579          * @return true/false did a change occur
580          */
581         private boolean setType(final MoneyWiseReportType pType) {
582             if (!pType.equals(theType)) {
583                 /* Are we currently point in time */
584                 final boolean isPointInTime = theType != null
585                         && theType.isPointInTime();
586 
587                 /* Store the new type */
588                 theType = pType;
589 
590                 /* If we need to switch point in time */
591                 if (theType.isPointInTime() != isPointInTime) {
592                     /* Switch it appropriately */
593                     theRangeSelect.setPeriod(isPointInTime
594                             ? OceanusDatePeriod.FISCALYEAR
595                             : OceanusDatePeriod.DATESUPTO);
596                     theRangeSelect.lockPeriod(!isPointInTime);
597 
598                     /* else if we are switching to tax calculation */
599                 } else if (theType == MoneyWiseReportType.TAXCALC) {
600                     /* Switch explicitly to Fiscal Year */
601                     theRangeSelect.setPeriod(OceanusDatePeriod.FISCALYEAR);
602                 }
603 
604                 /* Apply the state */
605                 applyState();
606                 return true;
607             }
608             return false;
609         }
610 
611         /**
612          * Apply the State.
613          */
614         private void applyState() {
615             /* Adjust the lock-down */
616             setEnabled(true);
617             theReportButton.setFixedText(theType == null
618                     ? null
619                     : theType.toString());
620             theHoldingButton.setValue(theSecurity);
621             theHoldingButton.setVisible(MoneyWiseReportType.CAPITALGAINS.equals(theType));
622         }
623     }
624 }