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