@Entity
@Table(name = "display", schema = "public")
public class Display {

    private final IntegerProperty id;
    private StringProperty name;
    private StringProperty groupName;
    private BooleanProperty initialDisplay;
    private ObjectProperty<StageType> stageType;
    private ObjectProperty<Parent> fxNode;

    public Display() {
        this.id = new SimpleIntegerProperty(this, "display_id");
        this.name = new SimpleStringProperty(this, "name", "");
        this.groupName = new SimpleStringProperty(this, "name", "");
        this.initialDisplay = new SimpleBooleanProperty(this, "initial_display", false);
        this.stageType = new SimpleObjectProperty<>(this, "stageType", StageType.REPLACE);
        this.fxNode = new SimpleObjectProperty<>(this, "fxNode", null);
    }

    @Id
    @Column(name = "display_id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    public Integer getId() {
        return id.get();
    }

    public IntegerProperty idProperty() {
        return id;
    }

    public void setId(Integer id) {
        this.id.set(id);
    }

    @Column(name = "name")
    public String getName() {
        return name.get();
    }

    public StringProperty nameProperty() {
        return name;
    }

    public void setName(String name) {
        this.name.set(name);
    }

    @Column(name = "group_name")
    public String getGroupName() {
        return groupName.get();
    }

    public StringProperty groupNameProperty() {
        return groupName;
    }

    public void setGroupName(String groupName) {
        this.groupName.set(groupName);
    }

    @Column(name = "initial_display")
    public boolean isInitialDisplay() {
        return initialDisplay.get();
    }

    public BooleanProperty initialDisplayProperty() {
        return initialDisplay;
    }

    public void setInitialDisplay(boolean initialDisplay) {
        this.initialDisplay.set(initialDisplay);
    }

    @Transient
    public StageType getStageType() {
        return stageType.get();
    }

    public ObjectProperty<StageType> stageTypeProperty() {
        return stageType;
    }

    public void setStageType(StageType stageType) {
        this.stageType.set(stageType);
    }

    @Column(name = "stage_type")
    public String getStageTypeName() {
        return getStageType() == null ? "" : getStageType().name();
    }

    public void setStageTypeName(String name) {
        setStageType(name == null || name.isEmpty() ? StageType.REPLACE : StageType.valueOf(name));
    }

    @Transient
    public Parent getFxNode() {
        return fxNode.get();
    }

    public ObjectProperty<Parent> fxNodeProperty() {
        return fxNode;
    }

    public void setFxNode(Parent fxNode) {
        this.fxNode.set(fxNode);
    }

    public enum StageType {REPLACE, NEW}

    @Override
    public String toString() {
        return getGroupName()+":"+getName();
    }
} @Data
@Entity
@AllArgsConstructor
@Table(name = "project", schema = "public")
public class Project {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Integer id;
    String name;
    @LazyCollection(LazyCollectionOption.FALSE)
    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
    EruGroup group;
    @LazyCollection(LazyCollectionOption.FALSE)
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    List<Device> devices;
    @LazyCollection(LazyCollectionOption.FALSE)
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    List<Connection> connections;
    @LazyCollection(LazyCollectionOption.FALSE)
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    List<Tag> tags;
    @LazyCollection(LazyCollectionOption.FALSE)
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    List<User> users;
    @LazyCollection(LazyCollectionOption.FALSE)
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    List<Display> displays;

    public Project() {
        this.id = null;
        this.name = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
        this.group = new EruGroup();
        this.devices = new ArrayList<>();
        this.connections = new ArrayList<>();
        this.tags = new ArrayList<>();
        this.users = new ArrayList<>();
        this.displays = new ArrayList<>();
    }

} public class EruException extends RuntimeException{
    public EruException(String s, IOException e) {
        super(s, e);
    }
} public class FxmlFileReadException extends RuntimeException {
    public FxmlFileReadException(Throwable throwable) {
        super(throwable);
    }
} @Slf4j
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class EruSceneBuilder extends VBox {

    private final SceneFxmlManager sceneFxmlManager;
    private final CustomLibraryLoader customLibraryLoader;
    private File sceneFxmlFile;
    private EditorController editorController;
    private ChangeListener<Number> updateListener;

    public EruSceneBuilder(CustomLibraryLoader customLibraryLoader, SceneFxmlManager sceneFxmlManager) {
        this.customLibraryLoader = customLibraryLoader;
        this.sceneFxmlManager = sceneFxmlManager;

        AnchorPane.setTopAnchor(this, 0.0);
        AnchorPane.setBottomAnchor(this, 0.0);
        AnchorPane.setRightAnchor(this, 0.0);
        AnchorPane.setLeftAnchor(this, 0.0);
        setFillWidth(true);
    }

    public void init(Display display) {
        log.debug("Instantiating Eru Scene Builder for {}", display.getName());
        sceneFxmlFile = sceneFxmlManager.createSceneFxmlFile(display);
        editorController = new EditorController();
        editorController.setLibrary(customLibraryLoader.getLibrary());

        HierarchyTreeViewController componentTree = new HierarchyTreeViewController(editorController);
        ContentPanelController canvas = new ContentPanelController(editorController);
        InspectorPanelController propertyTable = new InspectorPanelController(editorController);
        LibraryPanelController palette = new LibraryPanelController(editorController);

        setFxmlTextAndLocation();

        SplitPane leftPane = new SplitPane();
        leftPane.setOrientation(Orientation.VERTICAL);
        leftPane.getItems().addAll(palette.getPanelRoot(), componentTree.getPanelRoot());
        leftPane.setDividerPositions(0.5, 0.5);

        SplitPane.setResizableWithParent(leftPane, Boolean.FALSE);
        SplitPane.setResizableWithParent(propertyTable.getPanelRoot(), Boolean.FALSE);

        SplitPane content = new SplitPane();
        content.getItems().addAll(leftPane, canvas.getPanelRoot(), propertyTable.getPanelRoot());
        content.setDividerPositions(0.2, 0.8);
        SplitPane.setResizableWithParent(content, Boolean.TRUE);

        getChildren().add(content);
        startChangeListener();
    }

    private void setFxmlTextAndLocation() {
        try {
            URL fxmlFileUrl = sceneFxmlFile.toURI().toURL();
            String fxmlText = FXOMDocument.readContentFromURL(fxmlFileUrl);
            editorController.setFxmlTextAndLocation(fxmlText, fxmlFileUrl);
        } catch (IOException e) {
            log.error("Error trying to read '{}'", sceneFxmlFile.getAbsoluteFile());
            throw new FxmlFileReadException(e);
        }
    }

    private void startChangeListener() {
        updateListener = (observable, oldValue, newValue) -> {
            sceneFxmlManager.updateContent(sceneFxmlFile, editorController.getFxmlText());
        };

        editorController.getJobManager().revisionProperty().addListener(updateListener);
    }
} @Getter
@Component
public class ProjectModel {

    private ObjectProperty<Project> project;
    private IntegerProperty id;
    private StringProperty name;
    private ObjectProperty<EruGroup> group;
    private ObservableList<Device> devices;
    private ObservableList<Connection> connections;
    private ObservableList<Tag> tags;
    private ObservableList<User> users;
    private ObservableList<Display> displays;

    public void load(Project project){
        this.project = new SimpleObjectProperty<>(project);
        this.id = new SimpleIntegerProperty(project.getId());
        this.name = new SimpleStringProperty(project.getName());
        this.group = new SimpleObjectProperty<>(project.getGroup());
        this.devices = FXCollections.observableList(project.getDevices());
        this.connections = FXCollections.observableList(project.getConnections());
        this.tags = FXCollections.observableList(project.getTags());
        this.users = FXCollections.observableList(project.getUsers());
        this.displays = FXCollections.observableList(project.getDisplays());
    }

} @Slf4j
@Component
@RequiredArgsConstructor
public class SceneFxmlManager {

    private final EruPreferences eruPreferences;
    private static final String NEW_FXML_FILE_CONTENT = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
            "\n" +
            "<?import javafx.scene.layout.Pane?>\n" +
            "\n" +
            "\n" +
            "<Pane maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"400.0\" prefWidth=\"600.0\" xmlns=\"http://javafx.com/javafx/8.0.151\" xmlns:fx=\"http://javafx.com/fxml/1\" />";
    private static final String CHARSET_NAME = "utf-8";

    public File createSceneFxmlFile(Display display) {
        String fxmlFilesDirectoryPath = eruPreferences.getApplicationDirectory().getValue() + separator + "displays";

        String formattedSceneName = formatName(display);
        File fxmlFilesDirectory = new File(fxmlFilesDirectoryPath);
        File fxmlFile = new File(fxmlFilesDirectory.getAbsolutePath() + separator + formattedSceneName + ".fxml");
        if (!fxmlFile.exists()) {
            log.debug("Creating fxml file for display '{}'", display.getName());
            try {
                if (!fxmlFilesDirectory.exists()) {
                    fxmlFilesDirectory.mkdirs();
                }
                if (!fxmlFile.exists() && fxmlFile.createNewFile()) {
                    updateContent(fxmlFile, NEW_FXML_FILE_CONTENT);
                }

            } catch (IOException e) {
                log.error("Error creating fxml file for display '{}'", display.getName());
                throw new FxmlFileWriteException(e);
            }
        }
        log.debug("fxml file for display '{}' was created successfully in '{}'",
                display.getName(), fxmlFile.getAbsolutePath());
        return fxmlFile;
    }

    public void updateContent(File fxmlFile, String content) {
        log.debug("Updating '{}'", fxmlFile.getAbsolutePath());
        try (Writer writer = new BufferedWriter(new OutputStreamWriter(
                new FileOutputStream(fxmlFile), CHARSET_NAME))) {
            writer.write(content);
        } catch (IOException e) {
            log.error("Error updating fxml file '{}'", fxmlFile.getAbsolutePath());
            throw new FxmlFileWriteException(e);
        }
        log.debug("File '{}' updated successfully", fxmlFile.getAbsolutePath());
    }

    private String formatName(Display display) {
        return display.getName().replaceAll("\\s", "-").toLowerCase();
    }

} @Slf4j
public class LabelAppender extends AppenderBase<ILoggingEvent> {

    public static volatile StringProperty lastLog = new SimpleStringProperty();

    @Override
    protected void append(ILoggingEvent eventObject) {
        final String message = eventObject.getFormattedMessage();
        try {
            Platform.runLater(() -> {
                try {
                    if (lastLog != null) {
                        lastLog.setValue(LocalTime.now() + ": " + message);
                    }
                } catch (final Throwable t) {
                    log.error("LabelAppender error:", t);
                }
            });
        } catch (final IllegalStateException ignore) {
        }
    }
}
 @Repository
public interface ProjectRepository extends JpaRepository<Project, Integer> {
} @Slf4j
@Data
@Component
public class EruPreferences {

    private final Preferences appPreferences;
    private final EruPreference<Application.Theme> theme;
    private final EruPreference<String> eruDatabaseURL;
    private final EruPreference<String> eruDatabaseUsername;
    private final EruPreference<String> eruDatabasePassword;
    private final EruPreference<Boolean> animationEnabled;
    private final EruPreference<Integer> animationDuration;
    private final EruPreference<Integer> modbusBlockMaxLimit;
    private final EruPreference<Boolean> historianEnabled;
    private final EruPreference<Integer> historianLimit;
    private final EruPreference<Integer> historianSamplingTime;
    private final EruPreference<Boolean> alarmingEnabled;
    private final EruPreference<Integer> alarmingLimit;
    private final EruPreference<Integer> alarmingSamplingTime;
    private final EruPreference<Boolean> alarmingHornEnabled;
    private final EruPreference<String> applicationDirectory;
    private final EruPreference<Boolean> applicationConfigured;

    public EruPreferences() {
        appPreferences = Preferences.userNodeForPackage(Application.class);

        eruDatabaseURL = new EruPreference<>(this, "eru.database.url", "jdbc:mysql://localhost:3306/eru?useSSL=false");
        eruDatabaseURL.setValue(appPreferences.get(eruDatabaseURL.getName(), eruDatabaseURL.getDefaultValue()));
        eruDatabaseURL.addListener(observable -> save(eruDatabaseURL.getName(), eruDatabaseURL.getValue()));

        eruDatabaseUsername = new EruPreference<>(this, "eru.database.username", "eru");
        eruDatabaseUsername.setValue(appPreferences.get(eruDatabaseUsername.getName(), eruDatabaseUsername.getDefaultValue()));
        eruDatabaseUsername.addListener(observable -> save(eruDatabaseUsername.getName(), eruDatabaseUsername.getValue()));

        eruDatabasePassword = new EruPreference<>(this, "eru.database.password", "951753");
        eruDatabasePassword.setValue(appPreferences.get(eruDatabasePassword.getName(), eruDatabasePassword.getDefaultValue()));
        eruDatabasePassword.addListener(observable -> save(eruDatabasePassword.getName(), eruDatabasePassword.getValue()));

        theme = new EruPreference<>(this, "theme", Application.Theme.DEFAULT);
        theme.setValue(Application.Theme.valueOf(appPreferences.get(theme.getName(), theme.getDefaultValue().name())));
        theme.addListener(observable -> save(theme.getName(), theme.getValue()));

        animationEnabled = new EruPreference<>(this, "animations.enabled", true);
        animationEnabled.setValue(appPreferences.getBoolean(animationEnabled.getName(), animationEnabled.getDefaultValue()));
        animationEnabled.addListener((observable, oldValue, newValue) -> save(animationEnabled.getName(), newValue));

        animationDuration = new EruPreference<>(this, "animations.duration", 5000);
        animationDuration.setValue(appPreferences.getInt(animationDuration.getName(), animationDuration.getDefaultValue()));
        animationDuration.addListener((observable, oldValue, newValue) -> save(animationDuration.getName(), newValue));

        modbusBlockMaxLimit = new EruPreference<>(this, "modbus.block.maxLimit", 120);
        modbusBlockMaxLimit.setValue(appPreferences.getInt(modbusBlockMaxLimit.getName(), modbusBlockMaxLimit.getDefaultValue()));
        modbusBlockMaxLimit.addListener((observable, oldValue, newValue) -> save(modbusBlockMaxLimit.getName(), newValue));

        historianEnabled = new EruPreference<>(this, "historian.enabled", true);
        historianEnabled.setValue(appPreferences.getBoolean(historianEnabled.getName(), historianEnabled.getDefaultValue()));
        historianEnabled.addListener((observable, oldValue, newValue) -> save(historianEnabled.getName(), newValue));

        historianLimit = new EruPreference<>(this, "historian.limit", 1000);
        historianLimit.setValue(appPreferences.getInt(historianLimit.getName(), historianLimit.getDefaultValue()));
        historianLimit.addListener((observable, oldValue, newValue) -> save(historianLimit.getName(), newValue));

        historianSamplingTime = new EruPreference<>(this, "historian.samplingTime", 600_000);
        historianSamplingTime.setValue(appPreferences.getInt(historianSamplingTime.getName(), historianSamplingTime.getDefaultValue()));
        historianSamplingTime.addListener((observable, oldValue, newValue) -> save(historianSamplingTime.getName(), newValue));

        alarmingEnabled = new EruPreference<>(this, "alarming.enabled", true);
        alarmingEnabled.setValue(appPreferences.getBoolean(alarmingEnabled.getName(), alarmingEnabled.getDefaultValue()));
        alarmingEnabled.addListener((observable, oldValue, newValue) -> save(alarmingEnabled.getName(), newValue));

        alarmingLimit = new EruPreference<>(this, "alarming.limit", 1000);
        alarmingLimit.setValue(appPreferences.getInt(alarmingLimit.getName(), alarmingLimit.getDefaultValue()));
        alarmingLimit.addListener((observable, oldValue, newValue) -> save(alarmingLimit.getName(), newValue));

        alarmingSamplingTime = new EruPreference<>(this, "alarming.samplingTime", 500);
        alarmingSamplingTime.setValue(appPreferences.getInt(alarmingSamplingTime.getName(), alarmingSamplingTime.getDefaultValue()));
        alarmingSamplingTime.addListener((observable, oldValue, newValue) -> save(alarmingSamplingTime.getName(), newValue));

        alarmingHornEnabled = new EruPreference<>(this, "alarming.horn.enabled", true);
        alarmingHornEnabled.setValue(appPreferences.getBoolean(alarmingHornEnabled.getName(), alarmingHornEnabled.getDefaultValue()));
        alarmingHornEnabled.addListener((observable, oldValue, newValue) -> save(alarmingHornEnabled.getName(), newValue));

        applicationDirectory = new EruPreference<>(this, "app.directory", System.getProperty("user.home") + "/.eru/");
        applicationDirectory.setValue(appPreferences.get(applicationDirectory.getName(), applicationDirectory.getDefaultValue()));
        applicationDirectory.addListener((observable, oldValue, newValue) -> save(applicationDirectory.getName(), newValue));

        applicationConfigured = new EruPreference<>(this, "app.configured", false);
        applicationConfigured.setValue(appPreferences.getBoolean(applicationConfigured.getName(), applicationConfigured.getDefaultValue()));
        applicationConfigured.addListener((observable, oldValue, newValue) -> save(applicationConfigured.getName(), newValue));
    }

    private void save(String name, Object value){
        log.info("Saving preferences...");
        if (value instanceof String){
            appPreferences.put(name, (String) value);
        } else if (value instanceof Integer){
            appPreferences.putInt(name, (Integer) value);
        } else if (value instanceof Boolean){
            appPreferences.putBoolean(name, (Boolean) value);
        } else if (value instanceof Application.Theme){
            appPreferences.put(name, ((Application.Theme) value).name());
        }
    }

} @Slf4j
@Component
@Data
public class ProjectListener {

    private final ProjectActivities projectActivities;
    private ProjectModel projectModel;

    public void listen(){
        listenDevicesChanges();
        listenConnectionsChanges();
        listenTagsChanges();
        listenUsersChanges();
        listenDisplaysChanges();
    }

    private void listenDevicesChanges(){
        // TODO
    }

    private void listenConnectionsChanges(){
        // Current Connections
        this.projectModel.getConnections().forEach(this::installLink);
        // Future Connections
        this.projectModel.getConnections().addListener((ListChangeListener<Connection>) c -> {
            while (c.next()) {
                for (Connection newConnection : c.getAddedSubList()) {
                    installLink(newConnection);
                }
            }
        });
    }

    private void listenTagsChanges(){
        // TODO
    }

    private void listenUsersChanges(){
        // TODO
    }

    private void listenDisplaysChanges(){
        // Current Displays
        this.projectModel.getDisplays().forEach(this::installLink);
        // Future Displays
        this.projectModel.getDisplays().addListener((ListChangeListener<Display>) c -> {
            while (c.next()) {
                for (Display newDisplay : c.getAddedSubList()) {
                    installLink(newDisplay);
                }
            }
        });
    }

    private void installLink(Display display){
        display.fxNodeProperty().addListener((o1, oldNode, newNode) -> {
            if(newNode != null){
                DynamoExtractor extractor = new DynamoExtractor();
                List<Dynamo> dynamos = extractor.extractFrom(newNode);
                projectModel.getTags().forEach(tag ->
                        dynamos.stream()
                                .filter(dynamo -> dynamo instanceof ValuableDynamo)
                                .filter(dynamo  -> ((ValuableDynamo) dynamo).getCurrentValueTagName().equals(tag.getName()))
                                .forEach(dynamo -> tag.valueProperty().addListener((obj, old, newValue) -> ((ValuableDynamo) dynamo).setCurrentTagValue(newValue)))
                );
                projectModel.getDisplays().forEach(displayInProjectModel ->
                        dynamos.stream()
                                .filter(dynamo -> dynamo instanceof DisplayNavigator)
                                .filter(dynamo  -> ((DisplayNavigator) dynamo).getDisplayName().equals(displayInProjectModel.getName()))
                                .forEach(dynamo -> ((DisplayNavigator) dynamo).setDisplay(displayInProjectModel)));
            }
        });
    }

    private void installLink(Connection connection){
        connection.connectedProperty().addListener((o, wasConnected, isConnected) -> {
            if (isConnected){
                projectModel.getDevices()
                        .stream()
                        .filter(device -> device.getConnection() instanceof Modbus)
                        .filter(Device::getEnabled)
                        .filter(device -> device.getConnection().equals(connection))
                        .forEach(projectActivities::startModbusDeviceReading);
                projectModel.getTags()
                        .stream()
                        .filter(tag -> tag.getType() == Tag.Type.INPUT)
                        .filter(Tag::getEnabled)
                        .filter(tag -> tag.getLinkedAddress() != null)
                        .filter(tag -> tag.getLinkedAddress().getOwner().getConnection().equals(connection))
                        .forEach(projectActivities::startLinkedAddressListening);
            } else {
                projectModel.getDevices()
                        .stream()
                        .filter(device -> device.getConnection() instanceof Modbus)
                        .filter(Device::getEnabled)
                        .filter(device -> device.getConnection().equals(connection))
                        .forEach(projectActivities::stopTasks);
                projectModel.getTags()
                        .stream()
                        .filter(tag -> tag.getType() == Tag.Type.INPUT)
                        .filter(Tag::getEnabled)
                        .filter(tag -> tag.getLinkedAddress() != null)
                        .filter(tag -> tag.getLinkedAddress().getOwner().getConnection().equals(connection))
                        .forEach(projectActivities::stopTasks);
            }
        });
    }
}

import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.assemblits.eru.entities.Display;
import org.assemblits.eru.entities.Project;
import org.assemblits.eru.exception.EruException;
import org.assemblits.eru.exception.FxmlFileReadException;
import org.assemblits.eru.gui.component.EruSceneBuilder;
import org.assemblits.eru.gui.model.ProjectModel;
import org.assemblits.eru.jfx.scenebuilder.SceneFxmlManager;
import org.assemblits.eru.logger.LabelAppender;
import org.assemblits.eru.persistence.ProjectRepository;
import org.assemblits.eru.preferences.EruPreferences;
import org.assemblits.eru.project.ProjectListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.stream.Collectors;
/******************************************************************************
 * Copyright (c) 2017 Assemblits contributors                                 *
 *                                                                            *
 * This file is part of Eru The open JavaFX SCADA by Assemblits Organization. *
 *                                                                            *
 * Eru The open JavaFX SCADA 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, or (at your option) any later version.                    *
 *                                                                            *
 * Eru 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 Foobar.  If not, see <http://www.gnu.org/licenses/>.            *
 ******************************************************************************/
package org.assemblits.eru.gui.controller;



@Slf4j
@Component
@RequiredArgsConstructor
public class EruController {

    private final ConfigurableApplicationContext applicationContext;
    private final CenterPaneController centerPaneController;
    private final ProjectTreeController projectTreeController;
    private final ProjectListener projectListener;
    private final LogLabelController logLabelController;
    private final ProjectRepository projectRepository;
    private final ProjectModel projectModel;
    private final SceneFxmlManager sceneFxmlManager;
    private final EruPreferences eruPreferences;

    public void startEru(Stage stage) {
        log.info("Starting Eru");
        setStage(stage);
        populateControllers();
        configureAutoSave();
        stage.show();
    }

    private void setStage(Stage stage) {
        try {
            FXMLLoader fxmlLoader = createFxmlLoader();
            fxmlLoader.setLocation(getClass().getResource("/views/Main.fxml"));
            Parent appRootNode = fxmlLoader.load();

            Scene eruScene = new Scene(appRootNode);
            eruScene.getStylesheets().add(eruPreferences.getTheme().getValue().getStyleSheetURL());
            eruPreferences.getTheme().addListener((observable, oldTheme, newTheme) ->  {
                log.info("Updating App Theme...");
                eruScene.getStylesheets().clear();
                eruScene.getStylesheets().add(newTheme.getStyleSheetURL());
            });
            stage.setMaximized(true);
            stage.setTitle("Eru - The open JavaFX SCADA");
            stage.getIcons().add(new Image(getClass().getResource("/images/Logo256x256.png").toExternalForm()));
            stage.setScene(eruScene);
        } catch (IOException e) {
            log.error("Error loading eru main screen");
            throw new EruException("Error loading eru main screen", e);
        }
    }

    private void populateControllers(){
        centerPaneController.getUsersTableView().setUsers(projectModel.getUsers());
        centerPaneController.getConnectionsTableView().setConnections(projectModel.getConnections());
        centerPaneController.getDevicesTableView().setDevicesAndConnections(projectModel.getDevices(), projectModel.getConnections());
        centerPaneController.getTagsTableView().setTagsAndDevices(projectModel.getTags(), projectModel.getDevices());
        centerPaneController.getDisplayTableView().setDisplays(projectModel.getDisplays());
        centerPaneController.getDisplayTableView().setOnDisplayPreview(this::launchDisplayPreview);
        centerPaneController.getDisplayTableView().setOnDisplayEdit(this::launchDisplayEditor);

        projectTreeController.setRoot(projectModel.getGroup().getValue());
        projectTreeController.setOnSelectedItem(centerPaneController::setVisibleTable);

        projectListener.setProjectModel(projectModel);
        projectListener.listen();

        logLabelController.getLogeLabel().textProperty().bind(LabelAppender.lastLog);
    }

    private void configureAutoSave() {
        centerPaneController.getUsersTableView().addActionOnEditCommit(this::saveProject);
        centerPaneController.getConnectionsTableView().addActionOnEditCommit(this::saveProject);
        centerPaneController.getDevicesTableView().addActionOnEditCommit(this::saveProject);
        centerPaneController.getTagsTableView().addActionOnEditCommit(this::saveProject);
        centerPaneController.getDisplayTableView().addActionOnEditCommit(this::saveProject);
    }

    void saveProject(){
        log.info("Saving...");
        Project lastProject = projectRepository.findOne(1);