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.ui.panel;
18  
19  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
20  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
21  import io.github.tonywasher.joceanus.oceanus.event.OceanusEvent;
22  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventManager;
23  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar;
24  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar.OceanusEventProvider;
25  import io.github.tonywasher.joceanus.oceanus.profile.OceanusProfile;
26  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
27  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
28  import io.github.tonywasher.joceanus.metis.ui.MetisAction;
29  import io.github.tonywasher.joceanus.metis.ui.MetisErrorPanel;
30  import io.github.tonywasher.joceanus.metis.ui.MetisIcon;
31  import io.github.tonywasher.joceanus.metis.viewer.MetisViewerEntry;
32  import io.github.tonywasher.joceanus.metis.viewer.MetisViewerManager;
33  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
34  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicResource;
35  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio;
36  import io.github.tonywasher.joceanus.moneywise.ui.MoneyWiseUIResource;
37  import io.github.tonywasher.joceanus.moneywise.ui.base.MoneyWiseBaseTable;
38  import io.github.tonywasher.joceanus.moneywise.ui.controls.MoneyWiseSpotPricesSelect;
39  import io.github.tonywasher.joceanus.moneywise.views.MoneyWiseSpotSecurityPrice;
40  import io.github.tonywasher.joceanus.moneywise.views.MoneyWiseSpotSecurityPrice.MoneyWiseSpotSecurityList;
41  import io.github.tonywasher.joceanus.moneywise.views.MoneyWiseView;
42  import io.github.tonywasher.joceanus.moneywise.views.MoneyWiseViewResource;
43  import io.github.tonywasher.joceanus.moneywise.views.MoneyWiseYQLDownloader;
44  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataResource;
45  import io.github.tonywasher.joceanus.prometheus.ui.PrometheusActionButtons;
46  import io.github.tonywasher.joceanus.prometheus.views.PrometheusDataEvent;
47  import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
48  import io.github.tonywasher.joceanus.prometheus.views.PrometheusUIEvent;
49  import io.github.tonywasher.joceanus.prometheus.views.PrometheusViewerEntryId;
50  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIComponent;
51  import io.github.tonywasher.joceanus.tethys.api.button.TethysUIButton;
52  import io.github.tonywasher.joceanus.tethys.api.control.TethysUIControl.TethysUIIconMapSet;
53  import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;
54  import io.github.tonywasher.joceanus.tethys.api.pane.TethysUIBorderPaneManager;
55  import io.github.tonywasher.joceanus.tethys.api.pane.TethysUIPaneFactory;
56  import io.github.tonywasher.joceanus.tethys.api.table.TethysUITableManager;
57  
58  /**
59   * MoneyWise SpotPrices Table.
60   */
61  public class MoneyWiseMarketPricesTable
62          extends MoneyWiseBaseTable<MoneyWiseSpotSecurityPrice> {
63      /**
64       * The SpotPrices selection panel.
65       */
66      private final MoneyWiseSpotPricesSelect theSelect;
67  
68      /**
69       * The account price list.
70       */
71      private MoneyWiseSpotSecurityList thePrices;
72  
73      /**
74       * The selected date.
75       */
76      private OceanusDate theDate;
77  
78      /**
79       * The Portfolio.
80       */
81      private MoneyWisePortfolio thePortfolio;
82  
83      /**
84       * Constructor.
85       *
86       * @param pView    the view
87       * @param pEditSet the editSet
88       * @param pError   the error panel
89       */
90      MoneyWiseMarketPricesTable(final MoneyWiseView pView,
91                                 final PrometheusEditSet pEditSet,
92                                 final MetisErrorPanel pError) {
93          /* Store parameters */
94          super(pView, pEditSet, pError, MoneyWiseBasicDataType.SECURITYPRICE);
95  
96          /* Access Gui factory */
97          final TethysUIFactory<?> myGuiFactory = pView.getGuiFactory();
98          final TethysUITableManager<MetisDataFieldId, MoneyWiseSpotSecurityPrice> myTable = getTable();
99  
100         /* Create new button */
101         final TethysUIButton myNewButton = myGuiFactory.buttonFactory().newButton();
102         MetisIcon.configureNewIconButton(myNewButton);
103 
104         /* Create a selection panel */
105         theSelect = new MoneyWiseSpotPricesSelect(myGuiFactory, pView);
106 
107         /* Set table configuration */
108         myTable.setDisabled(MoneyWiseSpotSecurityPrice::isDisabled)
109                 .setComparator(MoneyWiseSpotSecurityPrice::compareTo);
110 
111         /* Create the asset column */
112         myTable.declareStringColumn(MoneyWiseBasicDataType.SECURITY)
113                 .setCellValueFactory(MoneyWiseSpotSecurityPrice::getSecurityName)
114                 .setEditable(false)
115                 .setColumnWidth(WIDTH_NAME);
116 
117         /* Create the price column */
118         myTable.declarePriceColumn(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_PRICE)
119                 .setCellValueFactory(MoneyWiseSpotSecurityPrice::getPrice)
120                 .setEditable(true)
121                 .setCellEditable(r -> !r.isDisabled())
122                 .setColumnWidth(WIDTH_PRICE)
123                 .setOnCommit((r, v) -> updateField(MoneyWiseSpotSecurityPrice::setPrice, r, v));
124 
125         /* Create the previous price column */
126         myTable.declarePriceColumn(MoneyWiseViewResource.SPOTPRICE_PREVPRICE)
127                 .setCellValueFactory(MoneyWiseSpotSecurityPrice::getPrevPrice)
128                 .setName(MoneyWiseViewResource.SPOTPRICE_PREVPRICE.getValue())
129                 .setEditable(false)
130                 .setColumnWidth(WIDTH_PRICE);
131 
132         /* Create the previous date column */
133         myTable.declareDateColumn(MoneyWiseViewResource.SPOTEVENT_PREVDATE)
134                 .setCellValueFactory(MoneyWiseSpotSecurityPrice::getPrevDate)
135                 .setName(MoneyWiseViewResource.SPOTEVENT_PREVDATE.getValue())
136                 .setEditable(false)
137                 .setColumnWidth(WIDTH_DATE);
138 
139         /* Create the Active column */
140         final TethysUIIconMapSet<MetisAction> myActionMapSet = MetisIcon.configureStatusIconButton(myGuiFactory);
141         myTable.declareIconColumn(PrometheusDataResource.DATAITEM_TOUCH, MetisAction.class)
142                 .setIconMapSet(r -> myActionMapSet)
143                 .setCellValueFactory(r -> r.getPrice() != null && !r.isDisabled() ? MetisAction.DELETE : MetisAction.DO)
144                 .setName(MoneyWiseUIResource.STATICDATA_ACTIVE.getValue())
145                 .setEditable(true)
146                 .setCellEditable(r -> r.getPrice() != null && !r.isDisabled())
147                 .setColumnWidth(WIDTH_ICON)
148                 .setOnCommit((r, v) -> updateField(this::deleteRow, r, v));
149 
150         /* Add listeners */
151         theSelect.getEventRegistrar().addEventListener(PrometheusDataEvent.SELECTIONCHANGED, e -> handleNewSelection());
152         theSelect.getEventRegistrar().addEventListener(PrometheusDataEvent.DOWNLOAD, e -> downloadPrices());
153         pView.getEventRegistrar().addEventListener(e -> refreshData());
154         pEditSet.getEventRegistrar().addEventListener(e -> updateTableData());
155     }
156 
157     /**
158      * Obtain the selection panel.
159      *
160      * @return the select panel
161      */
162     MoneyWiseSpotPricesSelect getSelect() {
163         return theSelect;
164     }
165 
166     @Override
167     protected void refreshData() {
168         /* Obtain the active profile */
169         OceanusProfile myTask = getView().getActiveTask();
170         myTask = myTask.startTask("SpotPrices1");
171 
172         /* Refresh the data */
173         theSelect.refreshData();
174 
175         /* Access the selection details */
176         setSelection(theSelect.getPortfolio(), theSelect.getDate());
177 
178         /* Create SavePoint */
179         theSelect.createSavePoint();
180 
181         /* Complete the task */
182         myTask.end();
183     }
184 
185     /**
186      * Set Selection to the specified portfolio and date.
187      *
188      * @param pPortfolio the portfolio
189      * @param pDate      the Date for the extract
190      */
191     public void setSelection(final MoneyWisePortfolio pPortfolio,
192                              final OceanusDate pDate) {
193         /* Record selection */
194         theDate = pDate;
195         thePortfolio = pPortfolio;
196 
197         /* If selection is valid */
198         if (theDate != null
199                 && thePortfolio != null) {
200             /* Create the new list */
201             thePrices = new MoneyWiseSpotSecurityList(getView(), pPortfolio, pDate);
202 
203             /* Update Next/Previous values */
204             theSelect.setAdjacent(thePrices.getPrev(), thePrices.getNext());
205 
206             /* else invalid selection */
207         } else {
208             /* Set no selection */
209             thePrices = null;
210             theSelect.setAdjacent(null, null);
211         }
212 
213         /* Update other details */
214         getTable().setItems(thePrices == null ? null : thePrices.getUnderlyingList());
215         getEditEntry().setDataList(thePrices);
216         theSelect.setEnabled(true);
217     }
218 
219     /**
220      * handle new selection.
221      */
222     private void handleNewSelection() {
223         /* Set the deleted option */
224         setShowAll();
225 
226         /* Access selection */
227         final MoneyWisePortfolio myPortfolio = theSelect.getPortfolio();
228         final OceanusDate myDate = theSelect.getDate();
229 
230         /* If the selection differs */
231         if (!MetisDataDifference.isEqual(theDate, myDate)
232                 || !MetisDataDifference.isEqual(thePortfolio, myPortfolio)) {
233             /* Set selection */
234             setSelection(myPortfolio, myDate);
235 
236             /* Create SavePoint */
237             theSelect.createSavePoint();
238         }
239     }
240 
241     /**
242      * Set the showAll indicator.
243      */
244     private void setShowAll() {
245         cancelEditing();
246         getTable().setFilter(this::isFiltered);
247     }
248 
249     @Override
250     protected boolean isFiltered(final MoneyWiseSpotSecurityPrice pRow) {
251         /* Handle filter */
252         return theSelect.getShowClosed() || !pRow.isDisabled();
253     }
254 
255     @Override
256     protected void deleteRow(final MoneyWiseSpotSecurityPrice pRow,
257                              final Object pValue) throws OceanusException {
258         pRow.setPrice(null);
259     }
260 
261     /**
262      * Determine Focus.
263      */
264     public void determineFocus() {
265         /* Request the focus */
266         getTable().requestFocus();
267     }
268 
269     @Override
270     protected void notifyChanges() {
271         theSelect.setEnabled(!hasUpdates());
272         super.notifyChanges();
273     }
274 
275     /**
276      * Download prices.
277      */
278     private void downloadPrices() {
279         /* Cancel editing */
280         cancelEditing();
281 
282         /* Protect against exceptions */
283         try {
284             /* Download Prices */
285             if (MoneyWiseYQLDownloader.downloadPrices(thePrices)) {
286                 /* Increment data version */
287                 getEditSet().incrementVersion();
288 
289                 /* Update components to reflect changes */
290                 updateTableData();
291                 notifyChanges();
292             }
293         } catch (OceanusException e) {
294             /* Show the error */
295             setError(e);
296         }
297     }
298 
299     /**
300      * SpotPrices Panel.
301      */
302     public static class MoneyWiseSpotPricesPanel
303             implements TethysUIComponent, OceanusEventProvider<PrometheusDataEvent> {
304         /**
305          * Text for DataEntry Title.
306          */
307         private static final String NLS_DATAENTRY = MoneyWiseUIResource.PRICES_DATAENTRY.getValue();
308 
309         /**
310          * The Event Manager.
311          */
312         private final OceanusEventManager<PrometheusDataEvent> theEventManager;
313 
314         /**
315          * The updateSet.
316          */
317         private final PrometheusEditSet theEditSet;
318 
319         /**
320          * The error panel.
321          */
322         private final MetisErrorPanel theError;
323 
324         /**
325          * The action buttons.
326          */
327         private final PrometheusActionButtons theActionButtons;
328 
329         /**
330          * The table.
331          */
332         private final MoneyWiseMarketPricesTable theTable;
333 
334         /**
335          * The panel.
336          */
337         private final TethysUIBorderPaneManager thePanel;
338 
339         /**
340          * The viewer entry.
341          */
342         private final MetisViewerEntry theViewerPrice;
343 
344         /**
345          * Constructor.
346          *
347          * @param pView the data view
348          */
349         public MoneyWiseSpotPricesPanel(final MoneyWiseView pView) {
350             /* Build the Update set and entry */
351             theEditSet = new PrometheusEditSet(pView);
352 
353             /* Create the event manager */
354             theEventManager = new OceanusEventManager<>();
355 
356             /* Create the top level viewer entry for this view */
357             final MetisViewerEntry mySection = pView.getViewerEntry(PrometheusViewerEntryId.VIEW);
358             final MetisViewerManager myViewer = pView.getViewerManager();
359             theViewerPrice = myViewer.newEntry(mySection, NLS_DATAENTRY);
360             theViewerPrice.setTreeObject(theEditSet);
361 
362             /* Create the error panel for this view */
363             theError = pView.getToolkit().getToolkit().newErrorPanel(theViewerPrice);
364 
365             /* Create the table */
366             theTable = new MoneyWiseMarketPricesTable(pView, theEditSet, theError);
367 
368             /* Create the action buttons */
369             final TethysUIFactory<?> myGuiFactory = pView.getGuiFactory();
370             theActionButtons = new PrometheusActionButtons(myGuiFactory, theEditSet);
371             theActionButtons.setVisible(false);
372 
373             /* Create the header panel */
374             final TethysUIPaneFactory myPanes = myGuiFactory.paneFactory();
375             final TethysUIBorderPaneManager myHeader = myPanes.newBorderPane();
376             myHeader.setCentre(theTable.getSelect());
377             myHeader.setNorth(theError);
378             myHeader.setEast(theActionButtons);
379 
380             /* Create the panel */
381             thePanel = myPanes.newBorderPane();
382             thePanel.setNorth(myHeader);
383             thePanel.setCentre(theTable);
384 
385             /* Add listeners */
386             theError.getEventRegistrar().addEventListener(e -> handleErrorPane());
387             theActionButtons.getEventRegistrar().addEventListener(this::handleActionButtons);
388             theTable.getEventRegistrar().addEventListener(e -> notifyChanges());
389         }
390 
391         @Override
392         public TethysUIComponent getUnderlying() {
393             return thePanel;
394         }
395 
396         @Override
397         public OceanusEventRegistrar<PrometheusDataEvent> getEventRegistrar() {
398             return theEventManager.getEventRegistrar();
399         }
400 
401         /**
402          * handleErrorPane.
403          */
404         private void handleErrorPane() {
405             /* Determine whether we have an error */
406             final boolean isError = theError.hasError();
407 
408             /* Hide selection panel on error */
409             theTable.getSelect().setVisible(!isError);
410 
411             /* Lock scroll area */
412             theTable.setEnabled(!isError);
413 
414             /* Lock Action Buttons */
415             theActionButtons.setEnabled(!isError);
416         }
417 
418         /**
419          * handle Action Buttons.
420          *
421          * @param pEvent the event
422          */
423         private void handleActionButtons(final OceanusEvent<PrometheusUIEvent> pEvent) {
424             /* Cancel editing */
425             theTable.cancelEditing();
426 
427             /* Perform the command */
428             theEditSet.processCommand(pEvent.getEventId(), theError);
429 
430             /* Adjust for changes */
431             theTable.notifyChanges();
432         }
433 
434         /**
435          * Determine Focus.
436          */
437         public void determineFocus() {
438             /* Request the focus */
439             theTable.determineFocus();
440 
441             /* Focus on the Data entry */
442             theViewerPrice.setFocus();
443         }
444 
445         /**
446          * Call underlying controls to take notice of changes in view/selection.
447          */
448         private void notifyChanges() {
449             /* Determine whether we have updates */
450             final boolean hasUpdates = hasUpdates();
451 
452             /* Update the table buttons */
453             theActionButtons.setEnabled(true);
454             theActionButtons.setVisible(hasUpdates);
455 
456             /* Notify listeners */
457             theEventManager.fireEvent(PrometheusDataEvent.ADJUSTVISIBILITY);
458         }
459 
460         /**
461          * Does the panel have updates?
462          *
463          * @return true/false
464          */
465         public boolean hasUpdates() {
466             return theTable.hasUpdates();
467         }
468 
469         /**
470          * Does the panel have a session?
471          *
472          * @return true/false
473          */
474         public boolean hasSession() {
475             return theTable.hasUpdates();
476         }
477 
478         /**
479          * Does the panel have errors?
480          *
481          * @return true/false
482          */
483         public boolean hasErrors() {
484             return theTable.hasErrors();
485         }
486     }
487 }