Added profiles selector and logic for changing the selected profile.
This commit is contained in:
		
							parent
							
								
									7d7f80676a
								
							
						
					
					
						commit
						755dc87aec
					
				| 
						 | 
				
			
			@ -35,7 +35,7 @@ public class PerfinApp extends Application {
 | 
			
		|||
                PerfinApp::defineRoutes,
 | 
			
		||||
                PerfinApp::initAppDir,
 | 
			
		||||
                c -> initMainScreen(stage, c),
 | 
			
		||||
                PerfinApp::loadProfile
 | 
			
		||||
                PerfinApp::loadLastUsedProfile
 | 
			
		||||
        ));
 | 
			
		||||
        splashScreen.showAndWait();
 | 
			
		||||
        if (splashScreen.isStartupSuccessful()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +43,7 @@ public class PerfinApp extends Application {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initMainScreen(Stage stage, Consumer<String> msgConsumer) throws Exception {
 | 
			
		||||
    private void initMainScreen(Stage stage, Consumer<String> msgConsumer) {
 | 
			
		||||
        msgConsumer.accept("Initializing main screen.");
 | 
			
		||||
        Platform.runLater(() -> {
 | 
			
		||||
            stage.hide();
 | 
			
		||||
| 
						 | 
				
			
			@ -57,7 +57,7 @@ public class PerfinApp extends Application {
 | 
			
		|||
        router.map(route, PerfinApp.class.getResource(resource));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void defineRoutes(Consumer<String> msgConsumer) throws Exception {
 | 
			
		||||
    private static void defineRoutes(Consumer<String> msgConsumer) {
 | 
			
		||||
        msgConsumer.accept("Initializing application views.");
 | 
			
		||||
        Platform.runLater(() -> {
 | 
			
		||||
            mapResourceRoute("accounts", "/accounts-view.fxml");
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +80,7 @@ public class PerfinApp extends Application {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void loadProfile(Consumer<String> msgConsumer) throws Exception {
 | 
			
		||||
    private static void loadLastUsedProfile(Consumer<String> msgConsumer) throws Exception {
 | 
			
		||||
        msgConsumer.accept("Loading the most recent profile.");
 | 
			
		||||
        Profile.loadLast();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@ package com.andrewlalis.perfin.control;
 | 
			
		|||
 | 
			
		||||
import com.andrewlalis.javafx_scene_router.AnchorPaneRouterView;
 | 
			
		||||
import com.andrewlalis.perfin.view.BindingUtil;
 | 
			
		||||
import com.andrewlalis.perfin.view.ProfilesStage;
 | 
			
		||||
import javafx.fxml.FXML;
 | 
			
		||||
import javafx.scene.control.Label;
 | 
			
		||||
import javafx.scene.layout.BorderPane;
 | 
			
		||||
| 
						 | 
				
			
			@ -10,13 +11,10 @@ import javafx.scene.layout.HBox;
 | 
			
		|||
import static com.andrewlalis.perfin.PerfinApp.router;
 | 
			
		||||
 | 
			
		||||
public class MainViewController {
 | 
			
		||||
    @FXML
 | 
			
		||||
    public BorderPane mainContainer;
 | 
			
		||||
    @FXML
 | 
			
		||||
    public HBox breadcrumbHBox;
 | 
			
		||||
    @FXML public BorderPane mainContainer;
 | 
			
		||||
    @FXML public HBox breadcrumbHBox;
 | 
			
		||||
 | 
			
		||||
    @FXML
 | 
			
		||||
    public void initialize() {
 | 
			
		||||
    @FXML public void initialize() {
 | 
			
		||||
        AnchorPaneRouterView routerView = (AnchorPaneRouterView) router.getView();
 | 
			
		||||
        mainContainer.setCenter(routerView.getAnchorPane());
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -36,25 +34,25 @@ public class MainViewController {
 | 
			
		|||
        router.navigate("accounts");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @FXML
 | 
			
		||||
    public void goBack() {
 | 
			
		||||
    @FXML public void goBack() {
 | 
			
		||||
        router.navigateBack();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @FXML
 | 
			
		||||
    public void goForward() {
 | 
			
		||||
    @FXML public void goForward() {
 | 
			
		||||
        router.navigateForward();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @FXML
 | 
			
		||||
    public void goToAccounts() {
 | 
			
		||||
    @FXML public void goToAccounts() {
 | 
			
		||||
        router.getHistory().clear();
 | 
			
		||||
        router.navigate("accounts");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @FXML
 | 
			
		||||
    public void goToTransactions() {
 | 
			
		||||
    @FXML public void goToTransactions() {
 | 
			
		||||
        router.getHistory().clear();
 | 
			
		||||
        router.navigate("transactions");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @FXML public void viewProfiles() {
 | 
			
		||||
        ProfilesStage.open(mainContainer.getScene().getWindow());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,4 +11,17 @@ public class Popups {
 | 
			
		|||
        var result = alert.showAndWait();
 | 
			
		||||
        return result.isPresent() && result.get() == ButtonType.OK;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void message(String text) {
 | 
			
		||||
        Alert alert = new Alert(Alert.AlertType.NONE, text);
 | 
			
		||||
        alert.initModality(Modality.APPLICATION_MODAL);
 | 
			
		||||
        alert.getButtonTypes().setAll(ButtonType.OK);
 | 
			
		||||
        alert.showAndWait();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void error(String text) {
 | 
			
		||||
        Alert alert = new Alert(Alert.AlertType.WARNING, text);
 | 
			
		||||
        alert.initModality(Modality.APPLICATION_MODAL);
 | 
			
		||||
        alert.showAndWait();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,147 @@
 | 
			
		|||
package com.andrewlalis.perfin.control;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.data.FileUtil;
 | 
			
		||||
import com.andrewlalis.perfin.model.Profile;
 | 
			
		||||
import com.andrewlalis.perfin.view.ProfilesStage;
 | 
			
		||||
import javafx.beans.binding.BooleanExpression;
 | 
			
		||||
import javafx.beans.property.BooleanProperty;
 | 
			
		||||
import javafx.fxml.FXML;
 | 
			
		||||
import javafx.scene.Node;
 | 
			
		||||
import javafx.scene.control.Button;
 | 
			
		||||
import javafx.scene.control.Label;
 | 
			
		||||
import javafx.scene.control.TextField;
 | 
			
		||||
import javafx.scene.layout.AnchorPane;
 | 
			
		||||
import javafx.scene.layout.HBox;
 | 
			
		||||
import javafx.scene.layout.VBox;
 | 
			
		||||
import javafx.scene.text.Text;
 | 
			
		||||
import javafx.scene.text.TextFlow;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import static com.andrewlalis.perfin.PerfinApp.router;
 | 
			
		||||
 | 
			
		||||
public class ProfilesViewController {
 | 
			
		||||
    @FXML public VBox profilesVBox;
 | 
			
		||||
    @FXML public TextField newProfileNameField;
 | 
			
		||||
    @FXML public Text newProfileNameErrorLabel;
 | 
			
		||||
    @FXML public Button addProfileButton;
 | 
			
		||||
 | 
			
		||||
    @FXML public void initialize() {
 | 
			
		||||
        BooleanExpression newProfileNameValid = BooleanProperty.booleanExpression(newProfileNameField.textProperty()
 | 
			
		||||
                .map(text -> (
 | 
			
		||||
                        text != null &&
 | 
			
		||||
                        !text.isBlank() &&
 | 
			
		||||
                        Profile.validateName(text) &&
 | 
			
		||||
                        !Profile.getAvailableProfiles().contains(text)
 | 
			
		||||
                )));
 | 
			
		||||
        newProfileNameErrorLabel.managedProperty().bind(newProfileNameErrorLabel.visibleProperty());
 | 
			
		||||
        newProfileNameErrorLabel.visibleProperty().bind(newProfileNameValid.not().and(newProfileNameField.textProperty().isNotEmpty()));
 | 
			
		||||
        newProfileNameErrorLabel.wrappingWidthProperty().bind(newProfileNameField.widthProperty());
 | 
			
		||||
        addProfileButton.disableProperty().bind(newProfileNameValid.not());
 | 
			
		||||
 | 
			
		||||
        refreshAvailableProfiles();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @FXML public void addProfile() {
 | 
			
		||||
        String name = newProfileNameField.getText();
 | 
			
		||||
        boolean valid = Profile.validateName(name);
 | 
			
		||||
        if (valid && !Profile.getAvailableProfiles().contains(name)) {
 | 
			
		||||
            boolean confirm = Popups.confirm("Are you sure you want to add a new profile named \"" + name + "\"?");
 | 
			
		||||
            if (confirm) {
 | 
			
		||||
                if (openProfile(name, false)) {
 | 
			
		||||
                    Popups.message("Created new profile \"" + name + "\" and loaded it.");
 | 
			
		||||
                }
 | 
			
		||||
                newProfileNameField.clear();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void refreshAvailableProfiles() {
 | 
			
		||||
        List<String> profileNames = Profile.getAvailableProfiles();
 | 
			
		||||
        String currentProfile = Profile.getCurrent() == null ? null : Profile.getCurrent().getName();
 | 
			
		||||
        List<Node> nodes = new ArrayList<>(profileNames.size());
 | 
			
		||||
        for (String profileName : profileNames) {
 | 
			
		||||
            boolean isCurrent = profileName.equals(currentProfile);
 | 
			
		||||
            AnchorPane profilePane = new AnchorPane();
 | 
			
		||||
            profilePane.setStyle("""
 | 
			
		||||
                    -fx-border-color: lightgray;
 | 
			
		||||
                    -fx-border-radius: 5px;
 | 
			
		||||
                    -fx-padding: 5px;
 | 
			
		||||
                    """);
 | 
			
		||||
 | 
			
		||||
            Text nameTextElement = new Text(profileName);
 | 
			
		||||
            nameTextElement.setStyle("-fx-font-size: large;");
 | 
			
		||||
            TextFlow nameLabel = new TextFlow(nameTextElement);
 | 
			
		||||
            if (isCurrent) {
 | 
			
		||||
                nameTextElement.setStyle("-fx-font-size: large; -fx-font-weight: bold;");
 | 
			
		||||
                Text currentProfileIndicator = new Text(" Currently Selected Profile");
 | 
			
		||||
                currentProfileIndicator.setStyle("""
 | 
			
		||||
                        -fx-font-size: small;
 | 
			
		||||
                        -fx-fill: grey;
 | 
			
		||||
                        """);
 | 
			
		||||
                nameLabel.getChildren().add(currentProfileIndicator);
 | 
			
		||||
            }
 | 
			
		||||
            AnchorPane.setLeftAnchor(nameLabel, 0.0);
 | 
			
		||||
            AnchorPane.setTopAnchor(nameLabel, 0.0);
 | 
			
		||||
            AnchorPane.setBottomAnchor(nameLabel, 0.0);
 | 
			
		||||
 | 
			
		||||
            HBox buttonBox = new HBox();
 | 
			
		||||
            AnchorPane.setRightAnchor(buttonBox, 0.0);
 | 
			
		||||
            AnchorPane.setTopAnchor(buttonBox, 0.0);
 | 
			
		||||
            AnchorPane.setBottomAnchor(buttonBox, 0.0);
 | 
			
		||||
            buttonBox.getStyleClass().addAll("std-spacing");
 | 
			
		||||
            Button openButton = new Button("Open");
 | 
			
		||||
            openButton.setOnAction(event -> openProfile(profileName, false));
 | 
			
		||||
            openButton.setDisable(isCurrent);
 | 
			
		||||
            buttonBox.getChildren().add(openButton);
 | 
			
		||||
            Button deleteButton = new Button("Delete");
 | 
			
		||||
            deleteButton.setOnAction(event -> deleteProfile(profileName));
 | 
			
		||||
            buttonBox.getChildren().add(deleteButton);
 | 
			
		||||
 | 
			
		||||
            profilePane.getChildren().setAll(nameLabel, buttonBox);
 | 
			
		||||
            nodes.add(profilePane);
 | 
			
		||||
        }
 | 
			
		||||
        profilesVBox.getChildren().setAll(nodes);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean openProfile(String name, boolean showPopup) {
 | 
			
		||||
        System.out.println("Opening profile: " + name);
 | 
			
		||||
        try {
 | 
			
		||||
            Profile.load(name);
 | 
			
		||||
            ProfilesStage.closeView();
 | 
			
		||||
            router.getHistory().clear();
 | 
			
		||||
            router.navigate("accounts");
 | 
			
		||||
            if (showPopup) Popups.message("The profile \"" + name + "\" has been loaded.");
 | 
			
		||||
            return true;
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            e.printStackTrace(System.err);
 | 
			
		||||
            Popups.error("Failed to load profile: " + e.getMessage());
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void deleteProfile(String name) {
 | 
			
		||||
        boolean confirmA = Popups.confirm("Are you sure you want to delete the profile \"" + name + "\"? This will permanently delete ALL accounts, transactions, files, and other data for this profile, and it cannot be recovered.");
 | 
			
		||||
        if (confirmA) {
 | 
			
		||||
            boolean confirmB = Popups.confirm("Press \"OK\" to confirm that you really want to delete the profile \"" + name + "\". There's no going back.");
 | 
			
		||||
            if (confirmB) {
 | 
			
		||||
                try {
 | 
			
		||||
                    FileUtil.deleteDirRecursive(Profile.getDir(name));
 | 
			
		||||
                    // Reset the app's "last profile" to the default if it was the deleted profile.
 | 
			
		||||
                    if (Profile.getLastProfile().equals(name)) {
 | 
			
		||||
                        Profile.saveLastProfile("default");
 | 
			
		||||
                    }
 | 
			
		||||
                    // If the current profile was deleted, switch to the default.
 | 
			
		||||
                    if (Profile.getCurrent() != null && Profile.getCurrent().getName().equals(name)) {
 | 
			
		||||
                        openProfile("default", true);
 | 
			
		||||
                    }
 | 
			
		||||
                    refreshAvailableProfiles();
 | 
			
		||||
                } catch (IOException e) {
 | 
			
		||||
                    throw new RuntimeException(e);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,11 @@
 | 
			
		|||
package com.andrewlalis.perfin.data;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.nio.file.FileVisitResult;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.nio.file.SimpleFileVisitor;
 | 
			
		||||
import java.nio.file.attribute.BasicFileAttributes;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -22,4 +28,20 @@ public class FileUtil {
 | 
			
		|||
        MIMETYPES.put(".bmp", "image/bmp");
 | 
			
		||||
        MIMETYPES.put(".tiff", "image/tiff");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void deleteDirRecursive(Path startDir) throws IOException {
 | 
			
		||||
        Files.walkFileTree(startDir, new SimpleFileVisitor<>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
 | 
			
		||||
                Files.delete(file);
 | 
			
		||||
                return FileVisitResult.CONTINUE;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
 | 
			
		||||
                Files.delete(dir);
 | 
			
		||||
                return FileVisitResult.CONTINUE;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,10 +9,7 @@ import java.nio.charset.StandardCharsets;
 | 
			
		|||
import java.nio.file.Files;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.sql.SQLException;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Properties;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -86,6 +83,16 @@ public class Profile {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static List<String> getAvailableProfiles() {
 | 
			
		||||
        try (var files = Files.list(PerfinApp.APP_DIR)) {
 | 
			
		||||
            return files.filter(Files::isDirectory)
 | 
			
		||||
                    .map(path -> path.getFileName().toString())
 | 
			
		||||
                    .sorted().toList();
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            return Collections.emptyList();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String getLastProfile() {
 | 
			
		||||
        Path lastProfileFile = PerfinApp.APP_DIR.resolve("last-profile.txt");
 | 
			
		||||
        if (Files.exists(lastProfileFile)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -127,7 +134,6 @@ public class Profile {
 | 
			
		|||
        for (var c : profileLoadListeners) {
 | 
			
		||||
            c.accept(current);
 | 
			
		||||
        }
 | 
			
		||||
        profileLoadListeners.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void initProfileDir(String name) throws IOException {
 | 
			
		||||
| 
						 | 
				
			
			@ -181,6 +187,13 @@ public class Profile {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean validateName(String name) {
 | 
			
		||||
        return name.matches("\\w+");
 | 
			
		||||
        return name != null &&
 | 
			
		||||
                name.matches("\\w+") &&
 | 
			
		||||
                name.toLowerCase().equals(name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return name;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,40 @@
 | 
			
		|||
package com.andrewlalis.perfin.view;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.SceneUtil;
 | 
			
		||||
import javafx.stage.Modality;
 | 
			
		||||
import javafx.stage.Stage;
 | 
			
		||||
import javafx.stage.Window;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A stage that shows a popup for interacting with Perfin's collection of
 | 
			
		||||
 * profiles.
 | 
			
		||||
 */
 | 
			
		||||
public class ProfilesStage extends Stage {
 | 
			
		||||
    private static ProfilesStage instance;
 | 
			
		||||
 | 
			
		||||
    public ProfilesStage() {
 | 
			
		||||
        setTitle("Profiles");
 | 
			
		||||
        setAlwaysOnTop(false);
 | 
			
		||||
        initModality(Modality.APPLICATION_MODAL);
 | 
			
		||||
        setScene(SceneUtil.load("/profiles-view.fxml"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void open(Window owner) {
 | 
			
		||||
        if (instance == null) {
 | 
			
		||||
            instance = new ProfilesStage();
 | 
			
		||||
            instance.initOwner(owner);
 | 
			
		||||
            instance.show();
 | 
			
		||||
            instance.setOnCloseRequest(event -> instance = null);
 | 
			
		||||
        } else {
 | 
			
		||||
            instance.requestFocus();
 | 
			
		||||
            instance.toFront();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void closeView() {
 | 
			
		||||
        if (instance != null) {
 | 
			
		||||
            instance.close();
 | 
			
		||||
            instance = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +16,7 @@
 | 
			
		|||
                <Button text="Forward" onAction="#goForward"/>
 | 
			
		||||
                <Button text="Accounts" onAction="#goToAccounts"/>
 | 
			
		||||
                <Button text="Transactions" onAction="#goToTransactions"/>
 | 
			
		||||
                <Button text="Profiles" onAction="#viewProfiles"/>
 | 
			
		||||
            </HBox>
 | 
			
		||||
            <HBox fx:id="breadcrumbHBox" styleClass="std-spacing,small-text"/>
 | 
			
		||||
        </VBox>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,57 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
 | 
			
		||||
<?import javafx.scene.control.Button?>
 | 
			
		||||
<?import javafx.scene.control.Label?>
 | 
			
		||||
<?import javafx.scene.control.ScrollPane?>
 | 
			
		||||
<?import javafx.scene.control.TextField?>
 | 
			
		||||
<?import javafx.scene.layout.*?>
 | 
			
		||||
<?import javafx.scene.text.*?>
 | 
			
		||||
<BorderPane xmlns="http://javafx.com/javafx"
 | 
			
		||||
            xmlns:fx="http://javafx.com/fxml"
 | 
			
		||||
            fx:controller="com.andrewlalis.perfin.control.ProfilesViewController"
 | 
			
		||||
            stylesheets="@style/base.css"
 | 
			
		||||
            prefWidth="500"
 | 
			
		||||
            prefHeight="400"
 | 
			
		||||
>
 | 
			
		||||
    <top>
 | 
			
		||||
        <VBox styleClass="std-padding,std-spacing">
 | 
			
		||||
            <TextFlow>
 | 
			
		||||
                <Text text="In Perfin, all your accounts, transactions, files, and other financial data are stored in a single "/>
 | 
			
		||||
                <Text text="profile" styleClass="bold-text"/>
 | 
			
		||||
                <Text text=". By default, Perfin uses the "/>
 | 
			
		||||
                <Text text="default" style="-fx-font-style: italic;"/>
 | 
			
		||||
                <Text text=" profile, and this should be sufficient for most users, but you can also add new profiles if you'd like to track some finances separately."/>
 | 
			
		||||
            </TextFlow>
 | 
			
		||||
        </VBox>
 | 
			
		||||
    </top>
 | 
			
		||||
    <center>
 | 
			
		||||
        <ScrollPane fitToWidth="true" fitToHeight="true">
 | 
			
		||||
            <VBox fx:id="profilesVBox" styleClass="std-padding,spacing-extra"/>
 | 
			
		||||
        </ScrollPane>
 | 
			
		||||
    </center>
 | 
			
		||||
    <bottom>
 | 
			
		||||
        <BorderPane>
 | 
			
		||||
            <left>
 | 
			
		||||
                <VBox styleClass="std-padding">
 | 
			
		||||
                    <Label text="Add New Profile"/>
 | 
			
		||||
                </VBox>
 | 
			
		||||
            </left>
 | 
			
		||||
            <center>
 | 
			
		||||
                <VBox styleClass="std-padding">
 | 
			
		||||
                    <TextField fx:id="newProfileNameField" style="-fx-min-width: 50px; -fx-pref-width: 50px;"/>
 | 
			
		||||
                    <Text
 | 
			
		||||
                            fx:id="newProfileNameErrorLabel"
 | 
			
		||||
                            styleClass="error-text"
 | 
			
		||||
                            style="-fx-fill: red;"
 | 
			
		||||
                            text="Invalid profile name. Profile names must only contain lowercase text."
 | 
			
		||||
                    />
 | 
			
		||||
                </VBox>
 | 
			
		||||
            </center>
 | 
			
		||||
            <right>
 | 
			
		||||
                <VBox styleClass="std-padding">
 | 
			
		||||
                    <Button text="Add" onAction="#addProfile" fx:id="addProfileButton"/>
 | 
			
		||||
                </VBox>
 | 
			
		||||
            </right>
 | 
			
		||||
        </BorderPane>
 | 
			
		||||
    </bottom>
 | 
			
		||||
</BorderPane>
 | 
			
		||||
		Loading…
	
		Reference in New Issue