Updated popups to include owner.
This commit is contained in:
		
							parent
							
								
									4951b8720d
								
							
						
					
					
						commit
						da589807ef
					
				| 
						 | 
				
			
			@ -89,6 +89,7 @@ public class AccountViewController implements RouteSelectionListener {
 | 
			
		|||
    @FXML
 | 
			
		||||
    public void archiveAccount() {
 | 
			
		||||
        boolean confirmResult = Popups.confirm(
 | 
			
		||||
                titleLabel,
 | 
			
		||||
                "Are you sure you want to archive this account? It will no " +
 | 
			
		||||
                        "longer show up in the app normally, and you won't be " +
 | 
			
		||||
                        "able to add new transactions to it. You'll still be " +
 | 
			
		||||
| 
						 | 
				
			
			@ -103,6 +104,7 @@ public class AccountViewController implements RouteSelectionListener {
 | 
			
		|||
 | 
			
		||||
    @FXML public void unarchiveAccount() {
 | 
			
		||||
        boolean confirm = Popups.confirm(
 | 
			
		||||
                titleLabel,
 | 
			
		||||
                "Are you sure you want to restore this account from its archived " +
 | 
			
		||||
                        "status?"
 | 
			
		||||
        );
 | 
			
		||||
| 
						 | 
				
			
			@ -115,6 +117,7 @@ public class AccountViewController implements RouteSelectionListener {
 | 
			
		|||
    @FXML
 | 
			
		||||
    public void deleteAccount() {
 | 
			
		||||
        boolean confirm = Popups.confirm(
 | 
			
		||||
                titleLabel,
 | 
			
		||||
                "Are you sure you want to permanently delete this account and " +
 | 
			
		||||
                        "all data directly associated with it? This cannot be " +
 | 
			
		||||
                        "undone; deleted accounts are not recoverable at all. " +
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -48,7 +48,10 @@ public class BalanceRecordViewController implements RouteSelectionListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @FXML public void delete() {
 | 
			
		||||
        boolean confirm = Popups.confirm("Are you sure you want to delete this balance record? This may have an effect on the derived balance of your account, as shown in Perfin.");
 | 
			
		||||
        boolean confirm = Popups.confirm(
 | 
			
		||||
                titleLabel,
 | 
			
		||||
                "Are you sure you want to delete this balance record? This may have an effect on the derived balance of your account, as shown in Perfin."
 | 
			
		||||
        );
 | 
			
		||||
        if (confirm) {
 | 
			
		||||
            Profile.getCurrent().dataSource().useRepo(BalanceRecordRepository.class, repo -> repo.deleteById(balanceRecord.id));
 | 
			
		||||
            router.navigateBackAndClear();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -89,7 +89,7 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
 | 
			
		|||
        LocalDateTime localTimestamp = LocalDateTime.parse(timestampField.getText(), DateUtil.DEFAULT_DATETIME_FORMAT);
 | 
			
		||||
        BigDecimal reportedBalance = new BigDecimal(balanceField.getText());
 | 
			
		||||
 | 
			
		||||
        boolean confirm = Popups.confirm("Are you sure that you want to record the balance of account\n%s\nas %s,\nas of %s?".formatted(
 | 
			
		||||
        boolean confirm = Popups.confirm(timestampField, "Are you sure that you want to record the balance of account\n%s\nas %s,\nas of %s?".formatted(
 | 
			
		||||
                account.getShortName(),
 | 
			
		||||
                CurrencyUtil.formatMoneyWithCurrencyPrefix(new MoneyValue(reportedBalance, account.getCurrency())),
 | 
			
		||||
                localTimestamp.atZone(ZoneId.systemDefault()).format(DateUtil.DEFAULT_DATETIME_FORMAT_WITH_ZONE)
 | 
			
		||||
| 
						 | 
				
			
			@ -122,7 +122,7 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
 | 
			
		|||
                    CurrencyUtil.formatMoney(new MoneyValue(reportedBalance, account.getCurrency())),
 | 
			
		||||
                    CurrencyUtil.formatMoney(new MoneyValue(currentDerivedBalance, account.getCurrency()))
 | 
			
		||||
            );
 | 
			
		||||
            return Popups.confirm(msg);
 | 
			
		||||
            return Popups.confirm(timestampField, msg);
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -117,7 +117,7 @@ public class EditAccountController implements RouteSelectionListener {
 | 
			
		|||
                BigDecimal initialBalance = new BigDecimal(initialBalanceField.getText().strip());
 | 
			
		||||
                List<Path> attachments = Collections.emptyList();
 | 
			
		||||
 | 
			
		||||
                boolean success = Popups.confirm("Are you sure you want to create this account?");
 | 
			
		||||
                boolean success = Popups.confirm(accountNameField, "Are you sure you want to create this account?");
 | 
			
		||||
                if (success) {
 | 
			
		||||
                    long id = accountRepo.insert(type, number, name, currency);
 | 
			
		||||
                    balanceRepo.insert(LocalDateTime.now(ZoneOffset.UTC), id, initialBalance, currency, attachments);
 | 
			
		||||
| 
						 | 
				
			
			@ -138,7 +138,7 @@ public class EditAccountController implements RouteSelectionListener {
 | 
			
		|||
            }
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.error("Failed to save (or update) account " + account.id, e);
 | 
			
		||||
            Popups.error("Failed to save the account: " + e.getMessage());
 | 
			
		||||
            Popups.error(accountNameField, "Failed to save the account: " + e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -203,7 +203,7 @@ public class EditTransactionController implements RouteSelectionListener {
 | 
			
		|||
                });
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                log.error("Failed to get repositories.", e);
 | 
			
		||||
                Popups.error("Failed to fetch account-specific data: " + e.getMessage());
 | 
			
		||||
                Popups.error(container, "Failed to fetch account-specific data: " + e.getMessage());
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,30 +1,65 @@
 | 
			
		|||
package com.andrewlalis.perfin.control;
 | 
			
		||||
 | 
			
		||||
import javafx.scene.Node;
 | 
			
		||||
import javafx.scene.Scene;
 | 
			
		||||
import javafx.scene.control.Alert;
 | 
			
		||||
import javafx.scene.control.ButtonType;
 | 
			
		||||
import javafx.stage.Modality;
 | 
			
		||||
import javafx.stage.Window;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper class for standardized popups and confirmation dialogs for the app.
 | 
			
		||||
 */
 | 
			
		||||
public class Popups {
 | 
			
		||||
    public static boolean confirm(String text) {
 | 
			
		||||
    public static boolean confirm(Window owner, String text) {
 | 
			
		||||
        Alert alert = new Alert(Alert.AlertType.CONFIRMATION, text);
 | 
			
		||||
        alert.initOwner(owner);
 | 
			
		||||
        alert.initModality(Modality.APPLICATION_MODAL);
 | 
			
		||||
        var result = alert.showAndWait();
 | 
			
		||||
        return result.isPresent() && result.get() == ButtonType.OK;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void message(String text) {
 | 
			
		||||
    public static boolean confirm(Node node, String text) {
 | 
			
		||||
        return confirm(getWindowFromNode(node), text);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void message(Window owner, String text) {
 | 
			
		||||
        Alert alert = new Alert(Alert.AlertType.NONE, text);
 | 
			
		||||
        alert.initOwner(owner);
 | 
			
		||||
        alert.initModality(Modality.APPLICATION_MODAL);
 | 
			
		||||
        alert.getButtonTypes().setAll(ButtonType.OK);
 | 
			
		||||
        alert.showAndWait();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void error(String text) {
 | 
			
		||||
    public static void message(Node node, String text) {
 | 
			
		||||
        message(getWindowFromNode(node), text);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void error(Window owner, String text) {
 | 
			
		||||
        Alert alert = new Alert(Alert.AlertType.WARNING, text);
 | 
			
		||||
        alert.initOwner(owner);
 | 
			
		||||
        alert.initModality(Modality.APPLICATION_MODAL);
 | 
			
		||||
        alert.showAndWait();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void error(Node node, String text) {
 | 
			
		||||
        error(getWindowFromNode(node), text);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void error(Window owner, Exception e) {
 | 
			
		||||
        error(owner, "An " + e.getClass().getSimpleName() + " occurred: " + e.getMessage());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void error(Node node, Exception e) {
 | 
			
		||||
        error(getWindowFromNode(node), e);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Window getWindowFromNode(Node n) {
 | 
			
		||||
        Window owner = null;
 | 
			
		||||
        Scene scene = n.getScene();
 | 
			
		||||
        if (scene != null) {
 | 
			
		||||
            owner = scene.getWindow();
 | 
			
		||||
        }
 | 
			
		||||
        return owner;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,10 +46,10 @@ public class ProfilesViewController {
 | 
			
		|||
        String name = newProfileNameField.getText();
 | 
			
		||||
        boolean valid = Profile.validateName(name);
 | 
			
		||||
        if (valid && !ProfileLoader.getAvailableProfiles().contains(name)) {
 | 
			
		||||
            boolean confirm = Popups.confirm("Are you sure you want to add a new profile named \"" + name + "\"?");
 | 
			
		||||
            boolean confirm = Popups.confirm(profilesVBox, "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.");
 | 
			
		||||
                    Popups.message(profilesVBox, "Created new profile \"" + name + "\" and loaded it.");
 | 
			
		||||
                }
 | 
			
		||||
                newProfileNameField.clear();
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -108,18 +108,18 @@ public class ProfilesViewController {
 | 
			
		|||
            PerfinApp.profileLoader.load(name);
 | 
			
		||||
            ProfilesStage.closeView();
 | 
			
		||||
            router.replace("accounts");
 | 
			
		||||
            if (showPopup) Popups.message("The profile \"" + name + "\" has been loaded.");
 | 
			
		||||
            if (showPopup) Popups.message(profilesVBox, "The profile \"" + name + "\" has been loaded.");
 | 
			
		||||
            return true;
 | 
			
		||||
        } catch (ProfileLoadException e) {
 | 
			
		||||
            Popups.error("Failed to load the profile: " + e.getMessage());
 | 
			
		||||
            Popups.error(profilesVBox, "Failed to load the 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.");
 | 
			
		||||
        boolean confirmA = Popups.confirm(profilesVBox, "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.");
 | 
			
		||||
            boolean confirmB = Popups.confirm(profilesVBox, "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));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -72,6 +72,7 @@ public class TransactionViewController {
 | 
			
		|||
 | 
			
		||||
    @FXML public void deleteTransaction() {
 | 
			
		||||
        boolean confirm = Popups.confirm(
 | 
			
		||||
            titleLabel,
 | 
			
		||||
            "Are you sure you want to delete this transaction? This will " +
 | 
			
		||||
            "permanently remove the transaction and its effects on any linked " +
 | 
			
		||||
            "accounts, as well as remove any attachments from storage within " +
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -177,7 +177,7 @@ public class TransactionsViewController implements RouteSelectionListener {
 | 
			
		|||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                Popups.error("An error occurred: " + e.getMessage());
 | 
			
		||||
                Popups.error(transactionsListBorderPane, e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,10 @@ import java.util.function.Consumer;
 | 
			
		|||
 *     class maintains a static <em>current</em> profile that can be loaded and
 | 
			
		||||
 *     unloaded.
 | 
			
		||||
 * </p>
 | 
			
		||||
 *
 | 
			
		||||
 * @param name The name of the profile.
 | 
			
		||||
 * @param settings The profile's settings.
 | 
			
		||||
 * @param dataSource The profile's data source.
 | 
			
		||||
 */
 | 
			
		||||
public record Profile(String name, Properties settings, DataSource dataSource) {
 | 
			
		||||
    private static final Logger log = LoggerFactory.getLogger(Profile.class);
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +69,7 @@ public record Profile(String name, Properties settings, DataSource dataSource) {
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
        currentProfileListeners.removeIf(ref -> ref.get() == null);
 | 
			
		||||
        log.debug("Current profile set to {}.", current.name());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void whenLoaded(Consumer<Profile> consumer) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package com.andrewlalis.perfin.model;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.PerfinApp;
 | 
			
		||||
import com.andrewlalis.perfin.control.Popups;
 | 
			
		||||
import com.andrewlalis.perfin.data.DataSourceFactory;
 | 
			
		||||
import com.andrewlalis.perfin.data.ProfileLoadException;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.FileUtil;
 | 
			
		||||
| 
						 | 
				
			
			@ -43,6 +44,21 @@ public class ProfileLoader {
 | 
			
		|||
        } catch (IOException e) {
 | 
			
		||||
            throw new ProfileLoadException("Failed to load profile settings.", e);
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            DataSourceFactory.SchemaStatus status = dataSourceFactory.getSchemaStatus(name);
 | 
			
		||||
            if (status == DataSourceFactory.SchemaStatus.NEEDS_MIGRATION) {
 | 
			
		||||
                boolean confirm = Popups.confirm(window, "The profile \"" + name + "\" has an outdated data schema and needs to be migrated to the latest version. Is this okay?");
 | 
			
		||||
                if (!confirm) {
 | 
			
		||||
                    throw new ProfileLoadException("User rejected migration.");
 | 
			
		||||
                }
 | 
			
		||||
            } else if (status == DataSourceFactory.SchemaStatus.INCOMPATIBLE) {
 | 
			
		||||
                Popups.error(window, "The profile \"" + name + "\" has a data schema that's incompatible with this app.");
 | 
			
		||||
                throw new ProfileLoadException("Incompatible schema version.");
 | 
			
		||||
            }
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            throw new ProfileLoadException("Failed to get profile's schema status.", e);
 | 
			
		||||
        }
 | 
			
		||||
        Popups.message(window, "Test!");
 | 
			
		||||
        return new Profile(name, settings, dataSourceFactory.getDataSource(name));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,6 +9,7 @@ import javafx.stage.Stage;
 | 
			
		|||
import javafx.stage.StageStyle;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -60,6 +61,10 @@ public class StartupSplashScreen extends Stage implements Consumer<String> {
 | 
			
		|||
        return scene;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Runs all tasks sequentially, invoking each one on the JavaFX main thread,
 | 
			
		||||
     * and quitting if there's any exception thrown.
 | 
			
		||||
     */
 | 
			
		||||
    private void runTasks() {
 | 
			
		||||
        Thread.ofVirtual().start(() -> {
 | 
			
		||||
            try {
 | 
			
		||||
| 
						 | 
				
			
			@ -69,7 +74,16 @@ public class StartupSplashScreen extends Stage implements Consumer<String> {
 | 
			
		|||
            }
 | 
			
		||||
            for (var task : tasks) {
 | 
			
		||||
                try {
 | 
			
		||||
                    task.accept(this);
 | 
			
		||||
                    CompletableFuture<Void> future = new CompletableFuture<>();
 | 
			
		||||
                    Platform.runLater(() -> {
 | 
			
		||||
                        try {
 | 
			
		||||
                            task.accept(this);
 | 
			
		||||
                            future.complete(null);
 | 
			
		||||
                        } catch (Exception e) {
 | 
			
		||||
                            future.completeExceptionally(e);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    future.join();
 | 
			
		||||
                    Thread.sleep(500);
 | 
			
		||||
                } catch (Exception e) {
 | 
			
		||||
                    accept("Startup failed: " + e.getMessage());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue