jobhunter.gui.FXMLController.java Source code

Java tutorial

Introduction

Here is the source code for jobhunter.gui.FXMLController.java

Source

/*
 * Copyright (C) 2014 Alejandro Ayuso
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package jobhunter.gui;

import java.io.File;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;

import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.Menu;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebView;
import javafx.stage.Window;
import jobhunter.controllers.PreferencesController;
import jobhunter.controllers.SubscriptionController;
import jobhunter.gui.dialog.BugReportDialog;
import jobhunter.gui.dialog.ConcurrentFileModificationDialog;
import jobhunter.gui.dialog.DebugDialog;
import jobhunter.gui.dialog.DialogFactory;
import jobhunter.gui.dialog.PreferencesDialog;
import jobhunter.gui.job.JobFormController;
import jobhunter.models.Job;
import jobhunter.models.Subscription;
import jobhunter.models.SubscriptionItem;
import jobhunter.persistence.Persistence;
import jobhunter.persistence.ProfileRepository;
import jobhunter.persistence.SubscriptionRepository;
import jobhunter.plugins.PlugInLoader;
import jobhunter.utils.ApplicationState;
import jobhunter.utils.FreeMarkerRenderer;
import jobhunter.utils.JavaFXUtils;
import jobhunter.utils.Random;
import jobhunter.utils.WebViewRenderer;

import org.apache.commons.lang3.StringUtils;
import org.controlsfx.control.action.Action;
import org.controlsfx.dialog.Dialog;
import org.controlsfx.dialog.Dialogs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Our main controller. Called by the MainApp.
 */
public class FXMLController implements Initializable, Localizable {

    private static final Logger l = LoggerFactory.getLogger(FXMLController.class);

    @FXML
    private ListView<Job> jobsList;

    @FXML
    private ListView<Subscription> subscriptionsList;

    @FXML
    private VBox feedsTableViewContainer;

    @FXML
    private RadioMenuItem deletedMenuItem;

    @FXML
    private RadioMenuItem orderByDateMenuItem;

    @FXML
    private RadioMenuItem orderByRatingMenuItem;

    @FXML
    private RadioMenuItem orderByActivityMenuItem;

    @FXML
    private RadioMenuItem orderByStatusMenuItem;

    @FXML
    private VBox mainContainer;

    @FXML
    private WebView mainWebView;

    @FXML
    private Menu developmentMenu;

    @FXML
    private Menu importMenu;

    @FXML
    private Label statusLabel;

    @FXML
    private TableView<SubscriptionItem> subscriptionItemsTable;

    @FXML
    private TableColumn<SubscriptionItem, LocalDateTime> dateColumn;

    @FXML
    private TableColumn<SubscriptionItem, String> positionColumn;

    /**
     * Renders the HTML for the mainWebView
     */
    private WebViewRenderer webViewRenderer;

    /**
     * This label is used to display an error when a Subscription is
     * in failed state. Check initilize for more info
     */
    private Label feedErrorLabel = new Label();

    private Window parent;

    private ResourceBundle bundle;

    private final SubscriptionController subscriptionController;

    private Integer lastSelectedJobIndex = Integer.MAX_VALUE;

    public FXMLController() {
        this.subscriptionController = new SubscriptionController(ApplicationState.getBundle());
    }

    @FXML
    void newFile(ActionEvent e) {
        if (ApplicationState.changesPending()) {
            Action response = DialogFactory.pendingChanges();

            if (response.equals(Dialog.Actions.YES)) {
                save(e);
            } else if (response.equals(Dialog.Actions.CANCEL)) {
                return;
            }
        }

        PreferencesController.setLastFilePath("");
        ProfileRepository.clear();
        SubscriptionRepository.clear();
        refresh();
    }

    @FXML
    void openFile(ActionEvent event) {
        DialogFactory.open(parent).ifPresent(fopen -> {
            if (DialogFactory.openFile(fopen.getName())) {
                ProfileRepository.load(fopen);
                SubscriptionRepository.load(fopen);
                PreferencesController.setLastFilePath(fopen);
                refresh();
            }
        });
    }

    @FXML
    void save(ActionEvent event) {
        if (PreferencesController.isLastFilePathSet()) {
            final File fout = new File(PreferencesController.getLastFilePath());
            try {
                Persistence.save(fout);
                JavaFXUtils.toast(statusLabel, getTranslation("message.changes.saved"));
            } catch (ConcurrentModificationException e) {
                handleConcurrentFileModification(fout);
            }

        } else {
            saveAs(event);
        }
    }

    @FXML
    void saveAs(ActionEvent event) {
        DialogFactory.saveAs(parent).ifPresent(fopen -> {
            Persistence.rewrite(fopen);
            PreferencesController.setLastFilePath(fopen);
            JavaFXUtils.toast(statusLabel, getTranslation("message.changes.saved"));
        });
    }

    @FXML
    void exportHTML(ActionEvent event) {
        DialogFactory.exportHTML(parent).ifPresent(fopen -> {
            if (FreeMarkerRenderer.create().export(fopen, ProfileRepository.getProfile())) {
                JavaFXUtils.toast(statusLabel, getTranslation("message.exported.html"));
            } else {
                DialogFactory.error("message.failed.export.html");
            }
        });
    }

    @FXML
    void quit(ActionEvent event) {
        if (ApplicationState.changesPending()) {
            Action response = DialogFactory.quit(this.parent);

            // Cancel so no action is done
            if (response.equals(Dialog.Actions.CANCEL))
                return;

            // Don't save changes, then quit
            if (response.equals(Dialog.Actions.NO)) {
                Platform.exit();
                return;
            }

            // Lets save and then quit
            save(null);
            Platform.exit();
        } else {
            Platform.exit();
        }

    }

    @FXML
    void refresh(ActionEvent event) {
        l.debug("onActionDeletedMenuItemHandler");
        refresh();
    }

    @FXML
    void showAboutDialog(ActionEvent event) {
        DialogFactory.about(this.parent);
    }

    @FXML
    void jobsListClick(MouseEvent e) {
        Job selectedJob = jobsList.getSelectionModel().getSelectedItem();
        Integer currentlySelected = jobsList.getSelectionModel().getSelectedIndex();
        if (selectedJob != null) {
            if (JavaFXUtils.isDoubleClick(e)) {
                openJobForm(Optional.of(selectedJob));
            } else if (currentlySelected != lastSelectedJobIndex) {
                webViewRenderer.render(selectedJob);
            }
        }
        lastSelectedJobIndex = currentlySelected;
    }

    @FXML
    void subscriptionsListClick(MouseEvent e) {
        if (!JavaFXUtils.isLeftButton(e))
            return;
        showSubscriptionItems();
    }

    void showSubscriptionItems() {
        Subscription selected = subscriptionsList.getSelectionModel().getSelectedItem();
        if (selected != null) {
            SubscriptionRepository.findById(selected.getId()).ifPresent(sub -> {
                if (sub.getFailed()) {
                    if (!feedsTableViewContainer.getChildren().contains(feedErrorLabel))
                        feedsTableViewContainer.getChildren().add(feedErrorLabel);
                } else {
                    feedsTableViewContainer.getChildren().remove(feedErrorLabel);
                }
                subscriptionItemsTable.setItems(FXCollections.observableArrayList(sub.getSortedItems()));

                subscriptionItemsTable.getSelectionModel().clearSelection();
                subscriptionItemsTable.requestFocus();
                subscriptionItemsTable.getSelectionModel().selectFirst();
                subscriptionItemsTable.getFocusModel().focus(0);
                subscriptionItemsTable.scrollTo(0);
                subscriptionTableClick(null);

            });
        } else {
            subscriptionItemsTable.setItems(null);
        }
    }

    @FXML
    void subscriptionTableClick(MouseEvent e) {
        SubscriptionItem item = subscriptionItemsTable.getSelectionModel().getSelectedItem();
        if (item != null) {
            webViewRenderer.render(item);
            item.setActive(false);
            UpdateableListViewSkin.cast(subscriptionsList.getSkin()).refresh();
        }
    }

    @FXML
    void deleteItems(ActionEvent e) {
        subscriptionController.deleteItems(subscriptionItemsTable.getSelectionModel().getSelectedItems());
        showSubscriptionItems();
        autosave();

    }

    @FXML
    void subscriptionTableKey(KeyEvent e) {
        if (e.getCode() == KeyCode.UP || e.getCode() == KeyCode.DOWN)
            subscriptionTableClick(null);
    }

    @FXML
    void jobsListKey(KeyEvent e) {
        if (e.getCode() == KeyCode.UP || e.getCode() == KeyCode.DOWN)
            jobsListClick(null);
    }

    @FXML
    void addJob(ActionEvent e) {
        openJobForm(Optional.empty());
    }

    @FXML
    void addFeed(ActionEvent e) {
        subscriptionController.addFeed();
    }

    @FXML
    void deleteFeed(ActionEvent e) {
        l.debug("Show delete dialog");
        subscriptionController
                .deleteFeed(Optional.ofNullable(subscriptionsList.getSelectionModel().getSelectedItem()));
    }

    @FXML
    void updateFeeds(ActionEvent e) {
        subscriptionController.updateFeeds();
    }

    @FXML
    void readAllFeeds(ActionEvent e) {
        subscriptionController.readAll();
    }

    @FXML
    void readAllItems(ActionEvent e) {
        subscriptionController
                .readAll(Optional.ofNullable(subscriptionsList.getSelectionModel().getSelectedItem()));
        showSubscriptionItems();
        autosave();
    }

    @FXML
    void editFeed(ActionEvent e) {
        subscriptionController
                .editFeed(Optional.ofNullable(subscriptionsList.getSelectionModel().getSelectedItem()));
    }

    @FXML
    void showPreferencesDialog(ActionEvent e) {
        PreferencesDialog.create(getBundle()).show();
    }

    @FXML
    void showBugReportDialog(ActionEvent event) {
        BugReportDialog.create().setBundle(bundle).show();
    }

    @FXML
    void clearData(ActionEvent e) {
        File dataFile = new File(PreferencesController.getLastFilePath());
        dataFile.delete();
        PreferencesController.setLastFilePath("");
    }

    @FXML
    void insertRandomJob(ActionEvent e) {
        ProfileRepository.getProfile().addJob(Random.Job());
        refresh();
    }

    @FXML
    void insertRandomSubscription(ActionEvent e) {
        SubscriptionRepository.add(Random.Subscription());
        refresh();
    }

    @FXML
    void showJobs(ActionEvent e) {
        DebugDialog.create(getBundle()).setText(Persistence.debugProfile()).show();
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        this.bundle = rb;

        UpdateableListViewSkin<Subscription> skin = new UpdateableListViewSkin<>(this.subscriptionsList);
        this.subscriptionsList.setSkin(skin);

        feedErrorLabel.setText(getTranslation("message.feed.failed"));
        feedErrorLabel.getStyleClass().add("error-label");

        developmentMenu.setVisible(ApplicationState.isDebug());

        if (PreferencesController.isLastFilePathSet()) {
            final File fout = new File(PreferencesController.getLastFilePath());
            ProfileRepository.load(fout);
            SubscriptionRepository.load(fout);
        } else {
            ProfileRepository.getProfile();
        }

        ProfileRepository.setListener(() -> {
            // Listen for change events in the repo and react
            refresh();
            autosave();
        });

        subscriptionController.setOnUpdate(e -> {
            l.debug("Subscription Controller onUpdate");
            JavaFXUtils.toast(statusLabel, getTranslation("message.all.feeds.updated"));
            UpdateableListViewSkin.cast(subscriptionsList.getSkin()).refresh();
            autosave();
        });

        subscriptionController.setOnAdd(e -> {
            l.debug("Subscription Controller onAdd");
            refresh();
            autosave();
        });

        jobsList.setCellFactory(new JobCell.JobCellCallback());
        subscriptionsList.setCellFactory(new SubscriptionListCell.CellCallback());

        // Initialize the table
        dateColumn.setCellValueFactory(SubscriptionRow.DATE_VALUE);
        dateColumn.setCellFactory(SubscriptionRow::getCellFactory);
        positionColumn.setCellValueFactory(SubscriptionRow.POSITION_VALUE);
        subscriptionItemsTable.setRowFactory(SubscriptionRow::create);
        subscriptionItemsTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

        this.webViewRenderer = new WebViewRenderer(mainWebView);
        WebViewOnClickListener.set(mainWebView);

        // Load Plugins
        loadPlugIns(null);

        refresh();
    }

    private void openJobForm(final Optional<Job> job) {
        JobFormController.create(bundle).setJob(job.orElse(null)).show();
    }

    private void refresh() {
        l.debug("Refreshing View");
        this.lastSelectedJobIndex = Integer.MAX_VALUE;
        mainWebView.getEngine().loadContent("");
        refreshJobsListView();
        refreshSubscriptionsListView();
    }

    private void autosave() {
        l.debug("Autosaving");
        if (!PreferencesController.isAutosave())
            return;
        if (!ApplicationState.changesPending())
            return;
        if (StringUtils.isEmpty(PreferencesController.getLastFilePath()))
            return;
        save(null);
    }

    @Override
    public ResourceBundle getBundle() {
        return this.bundle;
    }

    private void refreshJobsListView() {
        List<Job> jobs;
        if (orderByRatingMenuItem.isSelected()) {
            jobs = ProfileRepository.getJobsByRating(deletedMenuItem.isSelected());
        } else if (orderByActivityMenuItem.isSelected()) {
            jobs = ProfileRepository.getJobsByActivity(deletedMenuItem.isSelected());
        } else if (orderByStatusMenuItem.isSelected()) {
            jobs = ProfileRepository.getJobsByStatus(deletedMenuItem.isSelected());
        } else {
            orderByDateMenuItem.setSelected(true);
            jobs = ProfileRepository.getJobsByDate(deletedMenuItem.isSelected());
        }

        jobsList.getSelectionModel().clearSelection();
        jobsList.getItems().clear();
        jobsList.setItems(FXCollections.observableArrayList(jobs));
    }

    private void refreshSubscriptionsListView() {
        subscriptionsList.getItems().clear();
        subscriptionsList.setItems(FXCollections.observableArrayList(SubscriptionRepository.getSubscriptions()));
        subscriptionsListClick(null);
    }

    private void loadPlugIns(ActionEvent e) {
        l.debug("Loading plugins");

        PlugInLoader pl = new PlugInLoader();

        Dialogs.create().message(getTranslation("message.loading.plugins")).showWorkerProgress(pl);

        pl.start(importMenu);
    }

    private void handleConcurrentFileModification(final File file) {
        ConcurrentFileModificationDialog.Actions act = ConcurrentFileModificationDialog.create()
                .setBundle(getBundle()).show(file);

        switch (act) {
        case SAVE:
            saveAs(null);
            break;
        case OVERWRITE:
            Persistence.rewrite(file);
            JavaFXUtils.toast(statusLabel, getTranslation("message.changes.saved"));
            break;
        case RELOAD:
            ProfileRepository.clear();
            SubscriptionRepository.clear();
            ProfileRepository.load(file);
            SubscriptionRepository.load(file);
            break;
        default:
            break;
        }

    }

    public void setParent(Window parent) {
        this.parent = parent;
        this.parent.setOnCloseRequest(e -> {
            this.quit(null);
        });
    }

}