Java tutorial
/* * Copyright (c) 2016 Jess "baudlord" Vlez Palacios, Carlos "kauron" Santiago Galindo Jimnez * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * If we meet some day, and you think this stuff is worth it, you can buy me a beer in return. */ package es.baudlord.pcpartpicker.controller; import com.itextpdf.text.*; import com.itextpdf.text.Font; import com.itextpdf.text.pdf.PdfPCell; import com.itextpdf.text.pdf.PdfPTable; import com.itextpdf.text.pdf.PdfWriter; import es.baudlord.pcpartpicker.App; import es.baudlord.pcpartpicker.model.Build; import es.baudlord.pcpartpicker.model.Part; import es.baudlord.pcpartpicker.model.Wizard; import es.baudlord.pcpartpicker.util.Category; import es.baudlord.pcpartpicker.util.Coin; import es.upv.inf.Product; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.event.ActionEvent; import javafx.event.Event; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.control.Button; import javafx.scene.control.MenuItem; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.text.Text; import javafx.stage.FileChooser; import javafx.stage.Modality; import javafx.stage.Stage; import javax.xml.bind.JAXBException; import java.awt.*; import java.io.*; import java.net.URI; import java.net.URL; import java.nio.file.FileSystem; import java.nio.file.*; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.*; import java.util.List; import static es.baudlord.pcpartpicker.App.GENERAL_BUNDLE; import javafx.scene.image.Image; public class BuildViewController implements Initializable { @FXML public BorderPane root; @FXML public TableView<Part> table; @FXML public TableColumn<Part, String> amountCol, unitPriceCol, priceCol, descCol, categoryCol; @FXML public TableColumn<Part, ImageView> iconCol; @FXML public Text textTotal, textTax, textPvp; @FXML public MenuButton buttonMenuAdd; @FXML public SplitMenuButton buttonMenuLoad, buttonSave; @FXML public ToolBar toolBar; @FXML public Button buttonNew, buttonWizard, buttonEdit, buttonRemove, buttonBill; private Build build; private BooleanProperty saved, nonEditable; private File file; @Override public void initialize(URL url, ResourceBundle resourceBundle) { MenuItem randomItem = new MenuItem(GENERAL_BUNDLE.getString("build.random")); randomItem.setOnAction(event -> { setBuild(Build.generateRandom()); nonEditable.set(true); file = null; ((Stage) root.getScene().getWindow()) .setTitle(GENERAL_BUNDLE.getString("app.name") + " - " + GENERAL_BUNDLE.getString("unnamed")); }); buttonMenuLoad.getItems().add(randomItem); // Add files from examples folder // yeh there's probably a more efficient way of loading from jar files // feel free to improve try { URI uri = App.class.getResource("builds").toURI(); Path myPath; FileSystem fileSystem; boolean isJar = uri.getScheme().equals("jar"); if (isJar) { fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap()); myPath = fileSystem.getPath("es/baudlord/pcpartpicker/builds"); } else { myPath = Paths.get(uri); } for (Path entry : Files.newDirectoryStream(myPath)) { MenuItem item = new MenuItem(GENERAL_BUNDLE.getString(entry.getFileName().toString())); InputStream is; if (isJar) { is = App.class.getResourceAsStream(entry.toString()); } else { is = new FileInputStream(entry.toFile()); } item.setOnAction(e -> { try { setBuild(Build.loadFrom(is)); nonEditable.set(true); file = null; ((Stage) root.getScene().getWindow()).setTitle(GENERAL_BUNDLE.getString("app.name") + " - " + GENERAL_BUNDLE.getString(entry.getFileName().toString())); } catch (JAXBException jaxbe) { jaxbe.printStackTrace(); } }); buttonMenuLoad.getItems().add(item); } } catch (Exception e) { e.printStackTrace(); } // Generic columns for all the tables iconCol.setCellValueFactory( e -> new SimpleObjectProperty<>(Category.getGraphic(e.getValue().getCategory(), 24))); descCol.setCellValueFactory(e -> new SimpleStringProperty(e.getValue().getDescription())); categoryCol.setCellValueFactory(e -> new SimpleStringProperty( App.PART_NAMES_BUNDLE.getString(e.getValue().getCategory().toString()))); priceCol.setCellValueFactory(e -> new SimpleStringProperty(Coin.formatNet(e.getValue().getTotalPrice()))); unitPriceCol.setCellValueFactory(e -> new SimpleStringProperty(Coin.formatNet(e.getValue().getPrice()))); amountCol.setCellValueFactory(e -> { int amount = e.getValue().getAmount(); return new SimpleStringProperty(amount > 0 ? String.valueOf(amount) : ""); }); Wizard wizard = new Wizard(); while (wizard.hasNextStep()) { Product.Category category = wizard.currentCategory(); MenuItem item = new MenuItem(); item.setText(App.PART_NAMES_BUNDLE.getString(category.toString())); item.setGraphic(Category.getGraphic(category, 24)); item.addEventHandler(Event.ANY, event -> { try { Part part = pickNewPart(category); if (part != null) { build.add(part); saved.set(false); displayBuild(); } } catch (IOException e) { e.printStackTrace(); } }); buttonMenuAdd.getItems().add(item); wizard.nextStep(); } saved = new SimpleBooleanProperty(true); nonEditable = new SimpleBooleanProperty(false); Arrays.asList(buttonNew, buttonWizard, buttonSave, buttonEdit, buttonRemove, buttonBill, buttonMenuAdd, buttonMenuLoad).forEach(node -> { node.setGraphic(new ImageView( new Image(App.class.getResourceAsStream("img/toolbar/" + node.getId() + ".png")))); node.getGraphic().prefWidth(32); node.getGraphic().prefHeight(32); }); buttonWizard.disableProperty().bind(nonEditable); buttonMenuAdd.disableProperty().bind(nonEditable); buttonSave.disableProperty().bind(saved.or(nonEditable)); buttonEdit.disableProperty() .bind(table.getSelectionModel().selectedItemProperty().isNull().or(nonEditable)); buttonRemove.disableProperty() .bind(table.getSelectionModel().selectedItemProperty().isNull().or(nonEditable)); MenuItem item = new MenuItem(); item.setText(App.GENERAL_BUNDLE.getString("save.as")); item.addEventHandler(Event.ANY, e -> { file = null; onSaveAction((ActionEvent) e); }); buttonSave.getItems().add(item); } public Build getBuild() { return build; } public void setBuild(Build build) { this.build = build; saved.set(true); displayBuild(); } private void displayBuild() { table.getItems().clear(); // Generate a leaf for each category for (int i = 0; i < 15; i++) { Product.Category category = Category.valueOf(i); ArrayList<Part> parts = build.getParts(category); for (Part part : parts) { table.getItems().add(part); } } double totalPrice = build.getTotalPrice(); textTotal.setText(Coin.formatNet(totalPrice)); textTax.setText(Coin.formatTax(totalPrice)); textPvp.setText(Coin.formatGross(totalPrice)); table.autosize(); } @FXML public void onBillAction(ActionEvent event) { if (build.isComplete()) { try { FileChooser chooser = new FileChooser(); chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("PDF", "*.pdf")); File file = chooser.showSaveDialog(((Node) event.getSource()).getScene().getWindow()); if (file == null) return; Document document = new Document(); PdfWriter.getInstance(document, new FileOutputStream(file)); document.open(); // Begin header generation PdfPTable table = new PdfPTable(2); table.setWidths(new int[] { 3, 7 }); com.itextpdf.text.Image image = com.itextpdf.text.Image .getInstance(App.class.getResource("img/logo.png")); image.scaleToFit(100f, 100f); PdfPCell cell = new PdfPCell(image); cell.setBorder(0); PdfPCell cell2 = new PdfPCell( new Paragraph("Hipster PC Store", new Font(Font.FontFamily.HELVETICA, 30))); cell2.setBorder(0); cell2.setVerticalAlignment(Element.ALIGN_MIDDLE); table.addCell(cell); table.addCell(cell2); document.add(table); // End header generation document.add(build.createPdfPTable(new Font(Font.FontFamily.COURIER, 7))); // Begin generate date warning Calendar cal = Calendar.getInstance(); Instant now = cal.toInstant(); cal.add(Calendar.DAY_OF_MONTH, 7); Instant end = cal.toInstant(); DateTimeFormatter localFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT) .withZone(ZoneId.systemDefault()); Paragraph p1 = new Paragraph(GENERAL_BUNDLE.getString("bill.date.warning"), new Font(Font.FontFamily.COURIER, 10, Font.BOLD)); Paragraph p2 = new Paragraph( GENERAL_BUNDLE.getString("bill.date.emitted") + " " + localFormat.format(now) + "\n" + GENERAL_BUNDLE.getString("bill.date.valid") + " " + localFormat.format(end), new Font(Font.FontFamily.COURIER, 10)); p1.setAlignment(Element.ALIGN_CENTER); p2.setAlignment(Element.ALIGN_CENTER); document.add(p1); document.add(p2); // End generate date warning document.close(); Alert dialog = new Alert(Alert.AlertType.CONFIRMATION); dialog.setTitle(GENERAL_BUNDLE.getString("bill.generated")); // TODO: translate dialog.setHeaderText(GENERAL_BUNDLE.getString("bill.validity")); dialog.getButtonTypes().setAll(ButtonType.CLOSE, new ButtonType(GENERAL_BUNDLE.getString("button.Print"), ButtonBar.ButtonData.FINISH), new ButtonType(GENERAL_BUNDLE.getString("button.ShowPDF"), ButtonBar.ButtonData.OK_DONE)); dialog.showAndWait(); if (dialog.getResult() != null) { ButtonBar.ButtonData result = dialog.getResult().getButtonData(); if (result.equals(ButtonBar.ButtonData.FINISH)) { Desktop.getDesktop().print(file); } else if (result.equals(ButtonBar.ButtonData.OK_DONE)) { Desktop.getDesktop().open(file); } } } catch (DocumentException | IOException e) { e.printStackTrace(); } } else { Alert dialog = new Alert(Alert.AlertType.WARNING); dialog.getButtonTypes().setAll( new ButtonType(GENERAL_BUNDLE.getString("button.tryAgain"), ButtonBar.ButtonData.OK_DONE), ButtonType.CANCEL); dialog.setTitle(GENERAL_BUNDLE.getString("dialog.build.incomplete")); List<Product.Category> missing = build.getMissingMandatoryParts(); dialog.setHeaderText(GENERAL_BUNDLE.getString("dialog.build.needed")); final HBox content = new HBox(10); missing.forEach(category -> { Button button = new Button(App.PART_NAMES_BUNDLE.getString(category.toString())); button.setGraphic(Category.getGraphic(category, 30)); button.setContentDisplay(ContentDisplay.TOP); button.setId(category.toString()); button.setOnAction(actionEvent -> { try { Part part = pickNewPart(category); if (part != null) { saved.set(false); build.add(part); displayBuild(); if (Category.isStorage(category)) { content.getChildren() .removeIf(node -> node.getId().equals(Product.Category.HDD_SSD.toString()) || node.getId().equals(Product.Category.HDD.toString())); content.autosize(); } else { content.getChildren().remove(button); } dialog.getDialogPane().getScene().getWindow().sizeToScene(); if (content.getChildren().isEmpty()) { buttonBill.fire(); dialog.close(); } } } catch (IOException e) { e.printStackTrace(); } }); content.getChildren().add(button); }); dialog.getDialogPane().setContent(content); dialog.showAndWait(); ButtonType result = dialog.getResult(); if (result != null && result.getButtonData().equals(ButtonBar.ButtonData.OK_DONE)) { dialog.hide(); onBillAction(event); } } } @FXML public void onSaveAction(ActionEvent event) { try { if (this.file == null) { FileChooser chooser = new FileChooser(); chooser.getExtensionFilters().add(App.EXTENSION_FILTER); File file = chooser.showSaveDialog(((Node) event.getSource()).getScene().getWindow()); if (file == null) return; this.file = file; } Build.saveTo(this.file, build); ((Stage) root.getScene().getWindow()) .setTitle(GENERAL_BUNDLE.getString("app.name") + " - " + file.getName()); saved.set(true); } catch (JAXBException e) { e.printStackTrace(); Alert alert = new Alert(Alert.AlertType.ERROR); alert.setTitle(GENERAL_BUNDLE.getString("dialog.error")); alert.setHeaderText(GENERAL_BUNDLE.getString("dialog.save.failure")); alert.showAndWait(); } } @FXML public void onLoadAction(ActionEvent event) { if (unsavedChangesDialog(event)) { try { FileChooser chooser = new FileChooser(); chooser.getExtensionFilters().add(App.EXTENSION_FILTER); File file = chooser.showOpenDialog(((Node) event.getSource()).getScene().getWindow()); if (file == null) return; setBuild(Build.loadFrom(file)); this.file = file; ((Stage) root.getScene().getWindow()) .setTitle(GENERAL_BUNDLE.getString("app.name") + " - " + file.getName()); nonEditable.set(false); saved.set(true); } catch (JAXBException e) { Alert alert = new Alert(Alert.AlertType.ERROR); alert.setTitle(GENERAL_BUNDLE.getString("dialog.error")); alert.setHeaderText(GENERAL_BUNDLE.getString("dialog.load.failure")); alert.showAndWait(); } } } @FXML public void onOpenWizardAction(ActionEvent event) { try { FXMLLoader loader = new FXMLLoader(App.class.getResource("fxml/Wizard.fxml"), GENERAL_BUNDLE); Parent parent = loader.load(); loader.<WizardController>getController().setParent(this); Stage stage = new Stage(); stage.getIcons().add(((Stage) root.getScene().getWindow()).getIcons().get(0)); stage.setScene(new Scene(parent)); stage.setTitle(App.GENERAL_BUNDLE.getString("title.Wizard")); stage.initOwner(((Node) event.getSource()).getScene().getWindow()); stage.initModality(Modality.WINDOW_MODAL); stage.showAndWait(); saved.set(false); } catch (IOException e) { e.printStackTrace(); } } @FXML public void onNewAction(ActionEvent event) { if (unsavedChangesDialog(event)) { setBuild(new Build()); saved.set(true); file = null; ((Stage) root.getScene().getWindow()) .setTitle(GENERAL_BUNDLE.getString("app.name") + " - " + GENERAL_BUNDLE.getString("unnamed")); nonEditable.set(false); } } @FXML public void onChangeAmountAction(ActionEvent event) { Part part = table.getSelectionModel().getSelectedItem(); if (part == null) return; Alert alert = new Alert(Alert.AlertType.CONFIRMATION); alert.setTitle(App.GENERAL_BUNDLE.getString("amount.name")); alert.setHeaderText(App.GENERAL_BUNDLE.getString("amount.header")); Spinner<Integer> spinner = new Spinner<>(); spinner.setValueFactory( new SpinnerValueFactory.IntegerSpinnerValueFactory(1, part.getStock(), part.getAmount())); alert.getDialogPane().setContent(spinner); alert.showAndWait(); if (alert.getResult().getButtonData().equals(ButtonBar.ButtonData.OK_DONE) && spinner.getValue() != part.getAmount()) { part.setAmount(spinner.getValue()); saved.set(false); displayBuild(); } } @FXML public void onRemoveAction(ActionEvent event) { Part part = table.getSelectionModel().getSelectedItem(); if (part != null) { if (part.getAmount() > 1) { Alert alert = new Alert(Alert.AlertType.WARNING); alert.getButtonTypes().setAll(ButtonType.APPLY, ButtonType.CANCEL); alert.setTitle(App.GENERAL_BUNDLE.getString("remove.name")); alert.setHeaderText(App.GENERAL_BUNDLE.getString("remove.header")); Spinner<Integer> amount = new Spinner<>(); amount.setEditable(true); amount.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, part.getAmount(), 1)); alert.getDialogPane().setContent(amount); alert.showAndWait(); if (alert.getResult() != null && alert.getResult().getButtonData().equals(ButtonBar.ButtonData.APPLY)) { if (amount.getValue() == part.getAmount()) build.remove(part); else part.removeAmount(amount.getValue()); saved.set(false); displayBuild(); } } else { Alert alert = new Alert(Alert.AlertType.WARNING); alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO); alert.setTitle(GENERAL_BUNDLE.getString("dialog.remove.warning")); alert.setHeaderText(GENERAL_BUNDLE.getString("dialog.remove.continue")); alert.showAndWait(); if (alert.getResult() != null && alert.getResult().getButtonData().equals(ButtonBar.ButtonData.YES)) { saved.set(false); build.remove(table.getSelectionModel().getSelectedItem()); displayBuild(); } } } } /** * Checks if there are unsaved changes and presents a dialog to the user if necessary * @param event From which a node can be extracted * @return Whether the calling method should continue (saved or changes discarded) * or not (the user clicked cancel) */ private boolean unsavedChangesDialog(ActionEvent event) { if (!saved.get()) { Alert dialog = new Alert(Alert.AlertType.WARNING); dialog.getButtonTypes().setAll( new ButtonType(GENERAL_BUNDLE.getString("button.Save"), ButtonBar.ButtonData.YES), new ButtonType(GENERAL_BUNDLE.getString("button.dontSave"), ButtonBar.ButtonData.NO), ButtonType.CANCEL); dialog.setTitle(GENERAL_BUNDLE.getString("dialog.unsaved.Title")); dialog.setHeaderText(GENERAL_BUNDLE.getString("dialog.unsaved.Content")); dialog.showAndWait(); ButtonType result = dialog.getResult(); if (result.getButtonData() == ButtonBar.ButtonData.YES) { onSaveAction(event); } else if (result == ButtonType.CANCEL) return false; } return true; } private Part pickNewPart(Product.Category category) throws IOException { FXMLLoader loader = new FXMLLoader(App.class.getResource("fxml/ProductList.fxml"), GENERAL_BUNDLE); Parent parent = loader.load(); loader.<ProductListController>getController().setCategory(category); Stage stage = new Stage(); stage.getIcons().add(new Image(App.class.getResource("img/icon.png").toString())); stage.initModality(Modality.WINDOW_MODAL); stage.initOwner(root.getScene().getWindow()); stage.setTitle(GENERAL_BUNDLE.getString("title.productChooser")); stage.setScene(new Scene(parent)); stage.setResizable(false); stage.showAndWait(); return loader.<ProductListController>getController().getPart(); } public void spawnLanding() throws IOException { FXMLLoader loader = new FXMLLoader(App.class.getResource("fxml/Landing.fxml"), GENERAL_BUNDLE); Parent parent = loader.load(); Stage stage = new Stage(); stage.initOwner(root.getScene().getWindow()); stage.initModality(Modality.WINDOW_MODAL); stage.setResizable(false); stage.setTitle("PCPartPicker"); stage.setScene(new Scene(parent)); stage.show(); } }