1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
81
82 public class AppControl {
83
84
85
86 public static final String PROP_USERHOME = "user.home";
87
88
89
90
91 public static final String EXT_POOL_NAME = GuiResource.FILETYPE_NAME.getValue();
92
93
94
95
96 public static final String EXT_POOL = ".plxml";
97
98
99
100
101 public static final String EXT_POOL_ALL = "*" + EXT_POOL;
102
103
104
105
106 public static final String EXT_HTML_NAME = GuiResource.FILETYPE_NAME.getValue();
107
108
109
110
111 public static final String EXT_HTML = ".html";
112
113
114
115
116 public static final String EXT_HTML_ALL = "*" + EXT_HTML;
117
118
119
120
121 private static final int ROW_HEIGHT = 20;
122
123
124
125
126 private static final int NUM_STARTING_PLAYERS = 5;
127
128
129
130
131 private final Stage theStage;
132
133
134
135
136 private final DocumentBuilder theBuilder;
137
138
139
140
141 private final Transformer theXformer;
142
143
144
145
146 private MainTabView theTabs;
147
148
149
150
151 private final ReportPanel theReportPanel;
152
153
154
155
156 private PoolPreferences thePreferences;
157
158
159
160
161 private DataSet theDataSet;
162
163
164
165
166
167
168 public AppControl(final Stage pStage) throws PoolException {
169
170 try {
171
172 theStage = pStage;
173
174
175 theDataSet = new DataSet();
176
177
178 thePreferences = new PoolPreferences();
179
180
181 DocumentBuilderFactory myFactory = DocumentBuilderFactory.newInstance();
182 theBuilder = myFactory.newDocumentBuilder();
183
184
185 TransformerFactory myXformFactory = TransformerFactory.newInstance();
186 theXformer = myXformFactory.newTransformer();
187
188
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
198
199
200 protected Stage getStage() {
201 return theStage;
202 }
203
204
205
206
207
208 public DataSet getDataSet() {
209 return theDataSet;
210 }
211
212
213
214
215
216 protected void setTabs(final MainTabView pTabs) {
217 theTabs = pTabs;
218 }
219
220
221
222
223
224
225 public void writeDataSetToFile(final DataSet pData) throws PoolException {
226
227 File myFile = pData.getFileName();
228
229
230 try (FileOutputStream myOutFile = new FileOutputStream(myFile);
231 BufferedOutputStream myStream = new BufferedOutputStream(myOutFile)) {
232
233 Document myDocument = theBuilder.newDocument();
234
235
236 XmlBuilder myBuilder = new XmlBuilder(myDocument);
237 myBuilder.buildDataSet(pData);
238
239
240 theXformer.transform(new DOMSource(myDocument), new StreamResult(myStream));
241
242
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
253
254
255
256
257 public DataSet readDataSetFromFile(final File pFile) throws PoolException {
258
259 try (FileInputStream myInFile = new FileInputStream(pFile);
260 BufferedInputStream myStream = new BufferedInputStream(myInFile)) {
261
262 Document myDocument = theBuilder.parse(myStream);
263
264
265 XmlParser myParser = new XmlParser(myDocument);
266 DataSet myData = myParser.parseDataSet();
267
268
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
276 return myData;
277
278 } catch (IOException | SAXException e) {
279 throw new PoolException("Failed to parse XML", e);
280 }
281 }
282
283
284
285
286
287 protected void adjustStage(final WindowAdjustment pAdjust) {
288
289 double myStageWidth = theStage.getWidth();
290 myStageWidth += pAdjust.getWidth();
291 myStageWidth = Math.max(myStageWidth, theStage.getMinWidth());
292 theStage.setWidth(myStageWidth);
293
294
295 double myStageHeight = theStage.getHeight();
296 myStageHeight += pAdjust.getHeight();
297 myStageHeight = Math.max(myStageHeight, theStage.getMinHeight());
298 theStage.setHeight(myStageHeight);
299
300
301 theStage.centerOnScreen();
302 }
303
304
305
306
307
308 protected boolean checkDiscard() {
309
310 if ((theDataSet != null)
311 && theDataSet.isUpdated()) {
312
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
319 Optional<ButtonType> userChoice = myDialog.showAndWait();
320
321
322 return ButtonType.OK.equals(userChoice.get());
323 }
324
325
326 return true;
327 }
328
329
330
331
332 protected void chooseAndLoadFile() {
333
334 if (checkDiscard()) {
335
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
342 File myFile = theDataSet.getFileName();
343 if (myFile != null) {
344
345 if (!myFile.isDirectory()) {
346
347 myFile = myFile.getParentFile();
348 }
349
350
351 myChooser.setInitialDirectory(myFile);
352
353
354 } else {
355 myChooser.setInitialDirectory(new File(System.getProperty(PROP_USERHOME)));
356 }
357
358
359 myFile = myChooser.showOpenDialog(theStage);
360 if (myFile != null) {
361
362 loadDataFile(myFile);
363 }
364 }
365 }
366
367
368
369
370 protected void loadDefaultData() {
371
372 File myFile = thePreferences.getFileName();
373
374
375 if (myFile != null) {
376
377 loadDataFile(myFile);
378
379
380 } else {
381 createNewData();
382 }
383 }
384
385
386
387
388
389 protected void loadDataFile(final File pFile) {
390
391 try {
392
393 loadTheDataFile(pFile);
394
395
396 } catch (PoolException e) {
397
398 reportException("Failed to load data", e);
399 }
400 }
401
402
403
404
405
406
407 protected void loadTheDataFile(final File pFile) throws PoolException {
408
409 theDataSet = readDataSetFromFile(pFile);
410
411
412 theTabs.setData(theDataSet);
413
414
415 thePreferences.setFileName(pFile);
416 }
417
418
419
420
421 protected void createNewData() {
422
423 if (checkDiscard()) {
424
425 theDataSet = new DataSet();
426
427
428 PlayerList myPlayers = theDataSet.getPlayers();
429 for (int i = 0; i < NUM_STARTING_PLAYERS; i++) {
430 myPlayers.createUniquePlayer();
431 }
432
433
434 theTabs.setData(theDataSet);
435 }
436 }
437
438
439
440
441 protected void saveToFile() {
442
443 File myFile = theDataSet.getFileName();
444 if (myFile == null || myFile.isDirectory()) {
445
446 saveAsFile();
447 } else {
448
449 try {
450
451 writeDataSetToFile(theDataSet);
452
453
454 thePreferences.setFileName(myFile);
455
456
457 } catch (PoolException e) {
458
459 reportException("Failed to save data", e);
460 }
461 }
462 }
463
464
465
466
467 protected void saveAsFile() {
468
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
475 File myFile = theDataSet.getFileName();
476 if (myFile != null) {
477
478 if (myFile.isDirectory()) {
479
480 myChooser.setInitialDirectory(myFile);
481
482
483 } else {
484
485 File myDir = myFile.getParentFile();
486 myChooser.setInitialDirectory(myDir);
487
488
489 myChooser.setInitialFileName(myFile.getName());
490 }
491
492
493 } else {
494 myChooser.setInitialDirectory(new File(System.getProperty(PROP_USERHOME)));
495 }
496
497
498 myFile = myChooser.showSaveDialog(theStage);
499 if (myFile != null) {
500
501 theDataSet.setFileName(myFile);
502 theDataSet.setUpdated(Boolean.TRUE);
503 saveToFile();
504 }
505 }
506
507
508
509
510
511
512 protected void reportException(final String pMessage,
513 final PoolException pException) {
514
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
521 StringWriter mySWriter = new StringWriter();
522 PrintWriter myPWriter = new PrintWriter(mySWriter);
523 pException.printStackTrace(myPWriter);
524 String myExceptionText = mySWriter.toString();
525
526
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
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
545 myAlert.showAndWait();
546 }
547
548
549
550
551
552
553 protected void printNode(final Node pNode,
554 final ViewSelect pSelect) {
555
556 PageOrientation myOrientation = pSelect.getOrientation();
557
558
559 if (pNode instanceof TableSource) {
560
561 theReportPanel.printReport((TableSource) pNode, myOrientation);
562
563
564 } else {
565 printNode(pNode, myOrientation);
566 }
567 }
568
569
570
571
572
573
574 protected void sourceNode(final Node pNode,
575 final ViewSelect pSelect) {
576
577 if (pNode instanceof TableSource) {
578
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
585 myChooser.setInitialDirectory(new File(System.getProperty(PROP_USERHOME)));
586
587
588 myChooser.setInitialFileName(pSelect.toString() + EXT_HTML);
589
590
591 File myFile = myChooser.showSaveDialog(theStage);
592 if (myFile != null) {
593
594 theReportPanel.sourceReport((TableSource) pNode, myFile);
595 }
596 }
597 }
598
599
600
601
602
603
604 protected void createWeb(final CompetitionPanel pMaster,
605 final Competition pCompetition) {
606
607 theReportPanel.createWeb(pMaster, pCompetition);
608 }
609
610
611
612
613
614
615 protected void printNode(final Node pNode,
616 final PageOrientation pOrientation) {
617
618 PrinterJob job = PrinterJob.createPrinterJob();
619
620
621 if ((job != null)
622 && job.showPrintDialog(theStage)) {
623
624 pNode.setMouseTransparent(true);
625
626
627 Bounds myBounds = pNode.getBoundsInParent();
628 List<Transform> myTransforms = pNode.getTransforms();
629 List<Transform> oldTransforms = new ArrayList<Transform>(myTransforms);
630
631
632 Printer myPrinter = job.getPrinter();
633
634
635 PageLayout myLayout = myPrinter.createPageLayout(Paper.A4, pOrientation, Printer.MarginType.DEFAULT);
636
637
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
645 boolean success = job.printPage(myLayout, pNode);
646 if (success) {
647 job.endJob();
648 }
649
650
651 myTransforms.clear();
652 myTransforms.addAll(oldTransforms);
653
654
655 pNode.setMouseTransparent(false);
656 }
657 }
658
659
660
661
662 protected static final class WindowAdjustment {
663
664
665
666 private final double theWidth;
667
668
669
670
671 private final double theHeight;
672
673
674
675
676
677
678
679 protected <T> WindowAdjustment(final TableView<T> pTable,
680 final Node pCurrent) {
681
682 Bounds myBounds = pCurrent.layoutBoundsProperty().getValue();
683
684
685 double myTableWidth = calculateTableWidth(pTable);
686 theWidth = myTableWidth - myBounds.getMaxX();
687
688
689 double myTableHeight = calculateTableHeight(pTable);
690 theHeight = myTableHeight - myBounds.getMaxY();
691 }
692
693
694
695
696
697 public double getWidth() {
698 return theWidth;
699 }
700
701
702
703
704
705 public double getHeight() {
706 return theHeight;
707 }
708
709
710
711
712
713
714
715 private static <T> double calculateTableWidth(final TableView<T> pTable) {
716
717 double myWidth = 2;
718
719
720 Iterator<TableColumn<T, ?>> myIterator = pTable.getColumns().iterator();
721 while (myIterator.hasNext()) {
722 TableColumn<T, ?> myCol = myIterator.next();
723
724
725 myWidth += myCol.getMinWidth();
726 }
727
728
729 return myWidth;
730 }
731
732
733
734
735
736
737
738 private static <T> double calculateTableHeight(final TableView<T> pTable) {
739
740 int myNumRows = pTable.getItems().size() + 2;
741 int myRowHeight = ROW_HEIGHT;
742 return (double) (myRowHeight * myNumRows);
743 }
744 }
745 }