View Javadoc
1   /*******************************************************************************
2    * jhunters: Pool League
3    * Copyright 2015 Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy 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,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   ********************************************************************************/
17  package net.sourceforge.jhunters.pool.ui;
18  
19  import java.io.BufferedInputStream;
20  import java.io.BufferedOutputStream;
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.io.PrintWriter;
26  import java.io.StringWriter;
27  import java.time.Instant;
28  import java.time.LocalDate;
29  import java.time.LocalDateTime;
30  import java.time.ZoneId;
31  import java.util.ArrayList;
32  import java.util.Iterator;
33  import java.util.List;
34  import java.util.Optional;
35  
36  import javax.xml.parsers.DocumentBuilder;
37  import javax.xml.parsers.DocumentBuilderFactory;
38  import javax.xml.parsers.ParserConfigurationException;
39  import javax.xml.transform.Transformer;
40  import javax.xml.transform.TransformerConfigurationException;
41  import javax.xml.transform.TransformerException;
42  import javax.xml.transform.TransformerFactory;
43  import javax.xml.transform.dom.DOMSource;
44  import javax.xml.transform.stream.StreamResult;
45  
46  import org.w3c.dom.Document;
47  import org.xml.sax.SAXException;
48  
49  import javafx.geometry.Bounds;
50  import javafx.print.PageLayout;
51  import javafx.print.PageOrientation;
52  import javafx.print.Paper;
53  import javafx.print.Printer;
54  import javafx.print.PrinterJob;
55  import javafx.scene.Node;
56  import javafx.scene.control.Alert;
57  import javafx.scene.control.Alert.AlertType;
58  import javafx.scene.control.ButtonType;
59  import javafx.scene.control.Label;
60  import javafx.scene.control.TableColumn;
61  import javafx.scene.control.TableView;
62  import javafx.scene.control.TextArea;
63  import javafx.scene.layout.GridPane;
64  import javafx.scene.layout.Priority;
65  import javafx.scene.transform.Scale;
66  import javafx.scene.transform.Transform;
67  import javafx.stage.FileChooser;
68  import javafx.stage.FileChooser.ExtensionFilter;
69  import javafx.stage.Stage;
70  import net.sourceforge.jhunters.pool.PoolException;
71  import net.sourceforge.jhunters.pool.data.Competition;
72  import net.sourceforge.jhunters.pool.data.DataSet;
73  import net.sourceforge.jhunters.pool.data.PlayerList;
74  import net.sourceforge.jhunters.pool.data.XmlBuilder;
75  import net.sourceforge.jhunters.pool.data.XmlParser;
76  import net.sourceforge.jhunters.pool.ui.CompetitionPanel.ViewSelect;
77  import net.sourceforge.jhunters.pool.ui.ReportPanel.TableSource;
78  
79  /**
80   * Application Control.
81   */
82  public class AppControl {
83      /**
84       * The userHome property.
85       */
86      public static final String PROP_USERHOME = "user.home";
87  
88      /**
89       * File Types Name.
90       */
91      public static final String EXT_POOL_NAME = GuiResource.FILETYPE_NAME.getValue();
92  
93      /**
94       * File Types.
95       */
96      public static final String EXT_POOL = ".plxml";
97  
98      /**
99       * File Types.
100      */
101     public static final String EXT_POOL_ALL = "*" + EXT_POOL;
102 
103     /**
104      * File Types Name.
105      */
106     public static final String EXT_HTML_NAME = GuiResource.FILETYPE_NAME.getValue();
107 
108     /**
109      * File Types.
110      */
111     public static final String EXT_HTML = ".html";
112 
113     /**
114      * File Types.
115      */
116     public static final String EXT_HTML_ALL = "*" + EXT_HTML;
117 
118     /**
119      * The row height.
120      */
121     private static final int ROW_HEIGHT = 20;
122 
123     /**
124      * The number of initial players.
125      */
126     private static final int NUM_STARTING_PLAYERS = 5;
127 
128     /**
129      * The application stage.
130      */
131     private final Stage theStage;
132 
133     /**
134      * DocumentBuilder.
135      */
136     private final DocumentBuilder theBuilder;
137 
138     /**
139      * Transformer.
140      */
141     private final Transformer theXformer;
142 
143     /**
144      * The Tabs.
145      */
146     private MainTabView theTabs;
147 
148     /**
149      * Report Panel.
150      */
151     private final ReportPanel theReportPanel;
152 
153     /**
154      * Pool Preferences.
155      */
156     private PoolPreferences thePreferences;
157 
158     /**
159      * Current dataSet.
160      */
161     private DataSet theDataSet;
162 
163     /**
164      * Constructor.
165      * @param pStage the stage
166      * @throws PoolException on error
167      */
168     public AppControl(final Stage pStage) throws PoolException {
169         /* protect against exceptions */
170         try {
171             /* Store the stage */
172             theStage = pStage;
173 
174             /* Create an empty dataSet */
175             theDataSet = new DataSet();
176 
177             /* Access preferences */
178             thePreferences = new PoolPreferences();
179 
180             /* Create a Document builder */
181             DocumentBuilderFactory myFactory = DocumentBuilderFactory.newInstance();
182             theBuilder = myFactory.newDocumentBuilder();
183 
184             /* Create the transformer */
185             TransformerFactory myXformFactory = TransformerFactory.newInstance();
186             theXformer = myXformFactory.newTransformer();
187 
188             /* Create the reportPanel */
189             theReportPanel = new ReportPanel(this);
190 
191         } catch (ParserConfigurationException | TransformerConfigurationException e) {
192             throw new PoolException("Failed to initialise control", e);
193         }
194     }
195 
196     /**
197      * Obtain the stage.
198      * @return the stage
199      */
200     protected Stage getStage() {
201         return theStage;
202     }
203 
204     /**
205      * Obtain the dataSet.
206      * @return the dataSet
207      */
208     public DataSet getDataSet() {
209         return theDataSet;
210     }
211 
212     /**
213      * Declare the tabs.
214      * @param pTabs the tabs
215      */
216     protected void setTabs(final MainTabView pTabs) {
217         theTabs = pTabs;
218     }
219 
220     /**
221      * Write DataSet to file.
222      * @param pData the dataSet
223      * @throws PoolException on error
224      */
225     public void writeDataSetToFile(final DataSet pData) throws PoolException {
226         /* Access file name */
227         File myFile = pData.getFileName();
228 
229         /* Protect the workbook access */
230         try (FileOutputStream myOutFile = new FileOutputStream(myFile);
231              BufferedOutputStream myStream = new BufferedOutputStream(myOutFile)) {
232             /* Create a new document */
233             Document myDocument = theBuilder.newDocument();
234 
235             /* Build the dataSet document */
236             XmlBuilder myBuilder = new XmlBuilder(myDocument);
237             myBuilder.buildDataSet(pData);
238 
239             /* Format the XML and write to stream */
240             theXformer.transform(new DOMSource(myDocument), new StreamResult(myStream));
241 
242             /* Note that the data is now clean */
243             pData.setUpdated(Boolean.FALSE);
244             pData.setLastChange(LocalDate.now());
245 
246         } catch (TransformerException | IOException e) {
247             throw new PoolException("Failed to transform XML", e);
248         }
249     }
250 
251     /**
252      * Read DataSet from file.
253      * @param pFile the input File
254      * @return the dataSet
255      * @throws PoolException on error
256      */
257     public DataSet readDataSetFromFile(final File pFile) throws PoolException {
258         /* Protect the workbook access */
259         try (FileInputStream myInFile = new FileInputStream(pFile);
260              BufferedInputStream myStream = new BufferedInputStream(myInFile)) {
261             /* Read the document from the stream and parse it */
262             Document myDocument = theBuilder.parse(myStream);
263 
264             /* populate the list from the document */
265             XmlParser myParser = new XmlParser(myDocument);
266             DataSet myData = myParser.parseDataSet();
267 
268             /* Determine the update date of the file */
269             long myModified = pFile.lastModified();
270             Instant myf = Instant.ofEpochMilli(myModified);
271             LocalDateTime myDateTime = LocalDateTime.ofInstant(myf, ZoneId.systemDefault());
272             myData.setLastChange(myDateTime.toLocalDate());
273             myData.setFileName(pFile);
274 
275             /* Return the data */
276             return myData;
277 
278         } catch (IOException | SAXException e) {
279             throw new PoolException("Failed to parse XML", e);
280         }
281     }
282 
283     /**
284      * Adjust the stage.
285      * @param pAdjust the adjustment
286      */
287     protected void adjustStage(final WindowAdjustment pAdjust) {
288         /* Check the change in width */
289         double myStageWidth = theStage.getWidth();
290         myStageWidth += pAdjust.getWidth();
291         myStageWidth = Math.max(myStageWidth, theStage.getMinWidth());
292         theStage.setWidth(myStageWidth);
293 
294         /* Check the change in height */
295         double myStageHeight = theStage.getHeight();
296         myStageHeight += pAdjust.getHeight();
297         myStageHeight = Math.max(myStageHeight, theStage.getMinHeight());
298         theStage.setHeight(myStageHeight);
299 
300         /* Centre the application on the screen */
301         theStage.centerOnScreen();
302     }
303 
304     /**
305      * Check for discard.
306      * @return true/false
307      */
308     protected boolean checkDiscard() {
309         /* If we have unsaved changes */
310         if ((theDataSet != null)
311             && theDataSet.isUpdated()) {
312             /* Create a new alert */
313             Alert myDialog = new Alert(AlertType.CONFIRMATION);
314             myDialog.setTitle(GuiResource.DISCARD_DIALOG_TITLE.getValue());
315             myDialog.setHeaderText(GuiResource.DISCARD_DIALOG_HEADING.getValue());
316             myDialog.setContentText(GuiResource.DISCARD_DIALOG_MESSAGE.getValue());
317 
318             /* Show it */
319             Optional<ButtonType> userChoice = myDialog.showAndWait();
320 
321             /* Signal whether to proceed with shutdown */
322             return ButtonType.OK.equals(userChoice.get());
323         }
324 
325         /* Allow shutdown */
326         return true;
327     }
328 
329     /**
330      * Choose and load a file.
331      */
332     protected void chooseAndLoadFile() {
333         /* check whether we should proceed */
334         if (checkDiscard()) {
335             /* Create the chooser */
336             FileChooser myChooser = new FileChooser();
337             myChooser.setTitle(GuiResource.SELECTFILE_DIALOG_TITLE.getValue());
338             ExtensionFilter myFilter = new ExtensionFilter(EXT_POOL_NAME, EXT_POOL_ALL);
339             myChooser.getExtensionFilters().add(myFilter);
340 
341             /* If we have an existing dataSet */
342             File myFile = theDataSet.getFileName();
343             if (myFile != null) {
344                 /* If the file is not a directory */
345                 if (!myFile.isDirectory()) {
346                     /* Set as initial directory */
347                     myFile = myFile.getParentFile();
348                 }
349 
350                 /* Set the correct directory */
351                 myChooser.setInitialDirectory(myFile);
352 
353                 /* Else just start in the home directory */
354             } else {
355                 myChooser.setInitialDirectory(new File(System.getProperty(PROP_USERHOME)));
356             }
357 
358             /* Allow user to choose item */
359             myFile = myChooser.showOpenDialog(theStage);
360             if (myFile != null) {
361                 /* Load dataFile */
362                 loadDataFile(myFile);
363             }
364         }
365     }
366 
367     /**
368      * Load DefaultData.
369      */
370     protected void loadDefaultData() {
371         /* Access file name */
372         File myFile = thePreferences.getFileName();
373 
374         /* If we have a fileName */
375         if (myFile != null) {
376             /* Load the data */
377             loadDataFile(myFile);
378 
379             /* else no data */
380         } else {
381             createNewData();
382         }
383     }
384 
385     /**
386      * Load Data from named file.
387      * @param pFile the dataFile to load
388      */
389     protected void loadDataFile(final File pFile) {
390         /* Protect against exceptions */
391         try {
392             /* Load the data from the file */
393             loadTheDataFile(pFile);
394 
395             /* Handle exceptions */
396         } catch (PoolException e) {
397             /* Report exception to user */
398             reportException("Failed to load data", e);
399         }
400     }
401 
402     /**
403      * Load Data from named file.
404      * @param pFile the dataFile to load
405      * @throws PoolException on error
406      */
407     protected void loadTheDataFile(final File pFile) throws PoolException {
408         /* Load the data from the file */
409         theDataSet = readDataSetFromFile(pFile);
410 
411         /* Declare the dataSet */
412         theTabs.setData(theDataSet);
413 
414         /* Note the name in preferences */
415         thePreferences.setFileName(pFile);
416     }
417 
418     /**
419      * Create new dataSet.
420      */
421     protected void createNewData() {
422         /* check whether we should proceed */
423         if (checkDiscard()) {
424             /* Create new dataFile */
425             theDataSet = new DataSet();
426 
427             /* Create sample players */
428             PlayerList myPlayers = theDataSet.getPlayers();
429             for (int i = 0; i < NUM_STARTING_PLAYERS; i++) {
430                 myPlayers.createUniquePlayer();
431             }
432 
433             /* Set the data */
434             theTabs.setData(theDataSet);
435         }
436     }
437 
438     /**
439      * Save the data to the named file.
440      */
441     protected void saveToFile() {
442         /* If the dataSet does not have a linked file */
443         File myFile = theDataSet.getFileName();
444         if (myFile == null || myFile.isDirectory()) {
445             /* Handle as saveAsFile() */
446             saveAsFile();
447         } else {
448             /* Protect against exceptions */
449             try {
450                 /* Write the data to File */
451                 writeDataSetToFile(theDataSet);
452 
453                 /* Note the name in preferences */
454                 thePreferences.setFileName(myFile);
455 
456                 /* Handle exceptions */
457             } catch (PoolException e) {
458                 /* Report exception to user */
459                 reportException("Failed to save data", e);
460             }
461         }
462     }
463 
464     /**
465      * Save the data to a userSpecified file.
466      */
467     protected void saveAsFile() {
468         /* Create the chooser */
469         FileChooser myChooser = new FileChooser();
470         myChooser.setTitle(GuiResource.SAVEASFILE_DIALOG_TITLE.getValue());
471         ExtensionFilter myFilter = new ExtensionFilter(EXT_POOL_NAME, EXT_POOL_ALL);
472         myChooser.getExtensionFilters().add(myFilter);
473 
474         /* If the dataSet has a linked file */
475         File myFile = theDataSet.getFileName();
476         if (myFile != null) {
477             /* If the file is a directory */
478             if (myFile.isDirectory()) {
479                 /* Set as initial directory */
480                 myChooser.setInitialDirectory(myFile);
481 
482                 /* else split into directory and file */
483             } else {
484                 /* Set the correct directory */
485                 File myDir = myFile.getParentFile();
486                 myChooser.setInitialDirectory(myDir);
487 
488                 /* Set the name as the initial name */
489                 myChooser.setInitialFileName(myFile.getName());
490             }
491 
492             /* Else just start in the home directory */
493         } else {
494             myChooser.setInitialDirectory(new File(System.getProperty(PROP_USERHOME)));
495         }
496 
497         /* Show the dialog */
498         myFile = myChooser.showSaveDialog(theStage);
499         if (myFile != null) {
500             /* Save the file to the name */
501             theDataSet.setFileName(myFile);
502             theDataSet.setUpdated(Boolean.TRUE);
503             saveToFile();
504         }
505     }
506 
507     /**
508      * Report exception.
509      * @param pMessage the message
510      * @param pException the exception
511      */
512     protected void reportException(final String pMessage,
513                                    final PoolException pException) {
514         /* Create the alert dialog */
515         Alert myAlert = new Alert(AlertType.ERROR);
516         myAlert.setTitle(GuiResource.ERROR_DIALOG_TITLE.getValue());
517         myAlert.setHeaderText(null);
518         myAlert.setContentText(pMessage);
519 
520         /* Obtain print-out of Exception */
521         StringWriter mySWriter = new StringWriter();
522         PrintWriter myPWriter = new PrintWriter(mySWriter);
523         pException.printStackTrace(myPWriter);
524         String myExceptionText = mySWriter.toString();
525 
526         /* Create Exception pane */
527         Label myLabel = new Label(GuiResource.ERROR_DIALOG_STACK.getValue());
528         TextArea myTextArea = new TextArea(myExceptionText);
529         myTextArea.setEditable(false);
530         myTextArea.setWrapText(true);
531 
532         myTextArea.setMaxWidth(Double.MAX_VALUE);
533         myTextArea.setMaxHeight(Double.MAX_VALUE);
534         GridPane.setVgrow(myTextArea, Priority.ALWAYS);
535         GridPane.setHgrow(myTextArea, Priority.ALWAYS);
536 
537         /* Add the Exception pane as expandable content */
538         GridPane myXtraContent = new GridPane();
539         myXtraContent.setMaxWidth(Double.MAX_VALUE);
540         myXtraContent.add(myLabel, 0, 0);
541         myXtraContent.add(myTextArea, 0, 1);
542         myAlert.getDialogPane().setExpandableContent(myXtraContent);
543 
544         /* Display the alert */
545         myAlert.showAndWait();
546     }
547 
548     /**
549      * Print the relevant node.
550      * @param pNode the node to print
551      * @param pSelect the view selection
552      */
553     protected void printNode(final Node pNode,
554                              final ViewSelect pSelect) {
555         /* Determine page orientation */
556         PageOrientation myOrientation = pSelect.getOrientation();
557 
558         /* If the node is a table source */
559         if (pNode instanceof TableSource) {
560             /* Create and print report */
561             theReportPanel.printReport((TableSource) pNode, myOrientation);
562 
563             /* Print out as a resized node */
564         } else {
565             printNode(pNode, myOrientation);
566         }
567     }
568 
569     /**
570      * Source the relevant node.
571      * @param pNode the node to print
572      * @param pSelect the view selection
573      */
574     protected void sourceNode(final Node pNode,
575                               final ViewSelect pSelect) {
576         /* If the node is a table source */
577         if (pNode instanceof TableSource) {
578             /* Create the chooser */
579             FileChooser myChooser = new FileChooser();
580             myChooser.setTitle(GuiResource.SAVEASFILE_DIALOG_TITLE.getValue());
581             ExtensionFilter myFilter = new ExtensionFilter(EXT_HTML_NAME, EXT_HTML_ALL);
582             myChooser.getExtensionFilters().add(myFilter);
583 
584             /* Write to home directory */
585             myChooser.setInitialDirectory(new File(System.getProperty(PROP_USERHOME)));
586 
587             /* Set the name as the initial name */
588             myChooser.setInitialFileName(pSelect.toString() + EXT_HTML);
589 
590             /* Show the dialog */
591             File myFile = myChooser.showSaveDialog(theStage);
592             if (myFile != null) {
593                 /* Create and output source report */
594                 theReportPanel.sourceReport((TableSource) pNode, myFile);
595             }
596         }
597     }
598 
599     /**
600      * Create the WebSite.
601      * @param pMaster the master competition panel
602      * @param pCompetition the competition
603      */
604     protected void createWeb(final CompetitionPanel pMaster,
605                              final Competition pCompetition) {
606         /* Create and output webSite */
607         theReportPanel.createWeb(pMaster, pCompetition);
608     }
609 
610     /**
611      * Print the relevant node.
612      * @param pNode the node to print
613      * @param pOrientation the report orientation
614      */
615     protected void printNode(final Node pNode,
616                              final PageOrientation pOrientation) {
617         /* Create a new printer job */
618         PrinterJob job = PrinterJob.createPrinterJob();
619 
620         /* Select printer, allowing for cancel */
621         if ((job != null)
622             && job.showPrintDialog(theStage)) {
623             /* Stop the mouse affecting the node */
624             pNode.setMouseTransparent(true);
625 
626             /* Save existing transforms */
627             Bounds myBounds = pNode.getBoundsInParent();
628             List<Transform> myTransforms = pNode.getTransforms();
629             List<Transform> oldTransforms = new ArrayList<Transform>(myTransforms);
630 
631             /* Access printer and determine orientation */
632             Printer myPrinter = job.getPrinter();
633 
634             /* Create page layout of correct type */
635             PageLayout myLayout = myPrinter.createPageLayout(Paper.A4, pOrientation, Printer.MarginType.DEFAULT);
636 
637             /* Scale the node to fit the page */
638             double scaleX = myLayout.getPrintableWidth() / myBounds.getWidth();
639             double scaleY = PageOrientation.LANDSCAPE.equals(pOrientation)
640                                                                            ? myLayout.getPrintableHeight() / myBounds.getHeight()
641                                                                            : 1.0f;
642             myTransforms.add(new Scale(scaleX, scaleY));
643 
644             /* Print the page */
645             boolean success = job.printPage(myLayout, pNode);
646             if (success) {
647                 job.endJob();
648             }
649 
650             /* restore original transformations */
651             myTransforms.clear();
652             myTransforms.addAll(oldTransforms);
653 
654             /* Re-enable the mouse */
655             pNode.setMouseTransparent(false);
656         }
657     }
658 
659     /**
660      * Window adjustment.
661      */
662     protected static final class WindowAdjustment {
663         /**
664          * Width.
665          */
666         private final double theWidth;
667 
668         /**
669          * Height.
670          */
671         private final double theHeight;
672 
673         /**
674          * Constructor.
675          * @param <T> the table data type
676          * @param pTable the new table
677          * @param pCurrent the current node
678          */
679         protected <T> WindowAdjustment(final TableView<T> pTable,
680                                        final Node pCurrent) {
681             /* Access current bounds */
682             Bounds myBounds = pCurrent.layoutBoundsProperty().getValue();
683 
684             /* Determine width adjustment */
685             double myTableWidth = calculateTableWidth(pTable);
686             theWidth = myTableWidth - myBounds.getMaxX();
687 
688             /* Determine height adjustment */
689             double myTableHeight = calculateTableHeight(pTable);
690             theHeight = myTableHeight - myBounds.getMaxY();
691         }
692 
693         /**
694          * Obtain width.
695          * @return the width adjustment
696          */
697         public double getWidth() {
698             return theWidth;
699         }
700 
701         /**
702          * Obtain height.
703          * @return the height adjustment
704          */
705         public double getHeight() {
706             return theHeight;
707         }
708 
709         /**
710          * Calculate the full table width.
711          * @param pTable the table to calculate
712          * @param <T> the data type of the table
713          * @return the full width
714          */
715         private static <T> double calculateTableWidth(final TableView<T> pTable) {
716             /* Define width variable */
717             double myWidth = 2;
718 
719             /* Loop through the columns */
720             Iterator<TableColumn<T, ?>> myIterator = pTable.getColumns().iterator();
721             while (myIterator.hasNext()) {
722                 TableColumn<T, ?> myCol = myIterator.next();
723 
724                 /* Add column width */
725                 myWidth += myCol.getMinWidth();
726             }
727 
728             /* return the width */
729             return myWidth;
730         }
731 
732         /**
733          * Determine the full table height.
734          * @param pTable the table to calculate
735          * @param <T> the data type of the table
736          * @return the full height
737          */
738         private static <T> double calculateTableHeight(final TableView<T> pTable) {
739             /* Height = row height * (#rows +2) */
740             int myNumRows = pTable.getItems().size() + 2;
741             int myRowHeight = ROW_HEIGHT;
742             return (double) (myRowHeight * myNumRows);
743         }
744     }
745 }