es.baudlord.pcpartpicker.controller.BuildViewController.java Source code

Java tutorial

Introduction

Here is the source code for es.baudlord.pcpartpicker.controller.BuildViewController.java

Source

/*
 * 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();
    }
}