diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
index fef87da..186a096 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
@@ -178,6 +178,29 @@ public void start(final Stage stage) throws Exception {
gameEngineController, clickerGame, findStockGame, timeInputsGame
);
+ // In-game quit confirmation dialog: replaces the previous immediate
+ // "auto-save and return to main menu" behaviour with a popup that
+ // lets the player choose Continue / Save / Save and quit to main
+ // menu / Save and quit game / Sell portfolio, save and quit.
+ //
+ // Constructed up here (before the STATE_RESET subscriber below) so
+ // the subscriber can rebind it to the freshly loaded player/exchange
+ // alongside the other controllers.
+ QuitDialogView quitDialogView = new QuitDialogView();
+ QuitDialogController quitDialogController =
+ new QuitDialogController(quitDialogView, eventManager, inGameView,
+ gameStateLoader, saveGameService, player, exchange);
+ quitDialogController.setOnExitApplication(() -> {
+ try {
+ javafx.application.Platform.exit();
+ } finally {
+ // Fallback in case there are non-daemon background threads
+ // keeping the JVM alive after JavaFX shuts down.
+ System.exit(0);
+ }
+ });
+ topBarController.setOnQuitRequested(quitDialogController::show);
+
// Adds a generic event subscriber to the event manager
// that handles STATE_RESET events. These events are
// triggered when a new save file is loaded.
@@ -209,6 +232,7 @@ public The quit dialog is shown when the player clicks the "Quit" button
* from the in-game dashboard, and lets them choose between continuing,
- * saving in place, saving and returning to the main menu, or saving
- * and exiting the application.
Wires the four dialog buttons to the appropriate save / scene-change + *
Wires the five dialog buttons to the appropriate save / scene-change * / application-exit actions:
*The save logic mirrors the auto-save hook in {@code Main}: a missing @@ -45,6 +56,24 @@ public final class QuitDialogController /** Persists save games to disk. */ private final SaveGameService saveGameService; + /** + * The {@link Player} whose portfolio is liquidated by the + * "Sell portfolio, save and quit" button. + * + *
Not final because the active player can be swapped out when a + * save is loaded; {@link #handleContextUpdate} is the entry point + * the host application uses to rebind it.
+ * */ + private Player player; + + /** + * The {@link Exchange} used to execute the sell-all transactions. + * + *Like {@link #player}, this is reassigned when a save is loaded + * via {@link #handleContextUpdate}.
+ * */ + private Exchange exchange; + /** Runnable invoked to close the application. */ private Runnable onExitApplication = () -> { }; @@ -58,6 +87,10 @@ public final class QuitDialogController * @param inGameView the in-game view that hosts this overlay. * @param gameStateLoader the loader used to snapshot the active save. * @param saveGameService the service used to persist saves to disk. + * @param player the active {@link Player}, whose portfolio + * is liquidated by the sell-all-and-quit flow. + * @param exchange the active {@link Exchange}, used to execute + * the sell transactions. * * @throws IllegalArgumentException if any constructor argument is null. * */ @@ -65,16 +98,22 @@ public QuitDialogController(final QuitDialogView view, final EventManager eventManager, final InGameView inGameView, final GameStateLoader gameStateLoader, - final SaveGameService saveGameService) + final SaveGameService saveGameService, + final Player player, + final Exchange exchange) throws IllegalArgumentException { this.inGameView = inGameView; this.gameStateLoader = gameStateLoader; this.saveGameService = saveGameService; + this.player = player; + this.exchange = exchange; super(view, eventManager); eventManager.addSubscriber(this, EventType.SHOW_QUIT_OPTIONS); if (inGameView == null || gameStateLoader == null - || saveGameService == null) { + || saveGameService == null + || player == null + || exchange == null) { throw new IllegalArgumentException( "Invalid QuitDialogController arguments!"); } @@ -124,6 +163,68 @@ protected void initInteractions() { onExitApplication.run(); } }); + + getViewElement().setOnAction(QuitDialogActions.SELL_ALL_AND_QUIT, () -> { + sellEntirePortfolio(); + if (performSave()) { + close(); + changeScene(ViewEnum.MAIN_MENU); + } + }); + } + + /** + * Sells every share the player currently owns, one ticker at a time. + * + *Uses the same {@link Exchange#sell(BigDecimal, String, Player)} + * entry point as the dashboard's sell button and the stats widget's + * sell-all button, so commissions, taxes and net-worth listeners all + * fire exactly as if the player had sold each ticker manually.
+ * + *If the portfolio is already empty this is a no-op.
+ * */ + private void sellEntirePortfolio() { + // Snapshot first so we don't iterate over a list that mutates + // while we sell. + MapMirrors the {@code handleContextUpdate} hook on + * {@code StatsController} - the host application reassigns its + * own player/exchange fields when a {@code STATE_RESET} event fires, + * and must call this so the sell-all-and-quit flow operates on the + * newly active save instead of the original starting state.
+ * + * @param updatedExchange the current active exchange engine. + * @param updatedPlayer the current active player context profile. + * */ + public void handleContextUpdate(final Exchange updatedExchange, + final Player updatedPlayer) { + if (updatedExchange == null || updatedPlayer == null) { + return; + } + this.exchange = updatedExchange; + this.player = updatedPlayer; } /** diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogView.java index ba9dce1..e2c759a 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogView.java @@ -16,12 +16,13 @@ * top bar. * *The root of this view is a dimmer {@link StackPane} that covers the - * current center widget. Inside, a centered panel hosts four action buttons + * current center widget. Inside, a centered panel hosts five action buttons * that let the player choose how to leave (or stay in) the game.
* *Layout: a header row with the title and a close (X) button, a short - * description, and a vertical stack of four buttons: Continue, Save, - * Save and quit to main menu, Save and quit game.
+ * description, and a vertical stack of five buttons: Continue, Save, + * Save and quit to main menu, Save and quit game, Sell portfolio save + * and quit. * *This view is purely presentational - the {@link QuitDialogController} * wires the buttons to the actual save/quit logic.
@@ -44,6 +45,11 @@ public final class QuitDialogView /** Saves and exits the application. */ private Button saveAndQuitGameButton; + /** + * Sells the entire portfolio, then saves and returns to the main menu. + * */ + private Button sellAllAndQuitButton; + /** Label used to communicate save status to the player. */ private Label statusLabel; @@ -107,18 +113,21 @@ protected void initLayout() { description.getStyleClass().add("quit-dialog-description"); description.setWrapText(true); - // The four action buttons. Each is full-width inside the panel so the + // The five action buttons. Each is full-width inside the panel so the // longer labels do not overflow. continueButton = new Button("Continue"); saveButton = new Button("Save"); saveAndQuitMainMenuButton = new Button("Save and quit to main menu"); saveAndQuitGameButton = new Button("Save and quit game"); + sellAllAndQuitButton = + new Button("Sell portfolio, save and quit"); continueButton.setFocusTraversable(false); saveButton.setFocusTraversable(false); saveAndQuitMainMenuButton.setFocusTraversable(false); saveAndQuitGameButton.setFocusTraversable(false); + sellAllAndQuitButton.setFocusTraversable(false); continueButton.getStyleClass().addAll( "quit-dialog-button", "quit-dialog-button-primary"); @@ -126,17 +135,21 @@ protected void initLayout() { saveAndQuitMainMenuButton.getStyleClass().add("quit-dialog-button"); saveAndQuitGameButton.getStyleClass().addAll( "quit-dialog-button", "quit-dialog-button-danger"); + sellAllAndQuitButton.getStyleClass().addAll( + "quit-dialog-button", "quit-dialog-button-danger"); continueButton.setMaxWidth(Double.MAX_VALUE); saveButton.setMaxWidth(Double.MAX_VALUE); saveAndQuitMainMenuButton.setMaxWidth(Double.MAX_VALUE); saveAndQuitGameButton.setMaxWidth(Double.MAX_VALUE); + sellAllAndQuitButton.setMaxWidth(Double.MAX_VALUE); VBox buttonColumn = new VBox(10, continueButton, saveButton, saveAndQuitMainMenuButton, - saveAndQuitGameButton); + saveAndQuitGameButton, + sellAllAndQuitButton); buttonColumn.setAlignment(Pos.CENTER); // Status label - hidden until the controller writes something into it. @@ -156,9 +169,9 @@ protected void initLayout() { // shrink when the status label appears/disappears, and so it stays // visually aligned with the settings overlay (which also has a // fixed height). - panel.setMinHeight(420); - panel.setPrefHeight(420); - panel.setMaxHeight(420); + panel.setMinHeight(480); + panel.setPrefHeight(480); + panel.setMaxHeight(480); panel.getStyleClass().add("quit-dialog-panel"); // Dimmer wrapper. @@ -172,6 +185,8 @@ protected void initLayout() { saveAndQuitMainMenuButton); registerButton(QuitDialogActions.SAVE_AND_QUIT_GAME, saveAndQuitGameButton); + registerButton(QuitDialogActions.SELL_ALL_AND_QUIT, + sellAllAndQuitButton); // The X button reuses the CONTINUE action so we don't need a // dedicated enum entry just for "close". closeButton.setOnAction(e -> continueButton.fire()); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsActions.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsActions.java index 761e670..23f8afc 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsActions.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsActions.java @@ -3,11 +3,14 @@ /** * Enum representing all interactable actions in {@link StatsView}. * - *The stats widget is mostly an informational dashboard, so it currently - * has no top-level buttons of its own. {@code SELECT_HOLDING} is reserved - * for future use when individual holdings rows become clickable.
+ *{@code SELECT_HOLDING} is reserved for future use when individual + * holdings rows become clickable. {@code SELL_ENTIRE_PORTFOLIO} is the + * "sell everything" button shown at the bottom of the allocation panel.
* */ public enum StatsActions { /** Reserved for future holding-row click handling. */ - SELECT_HOLDING; + SELECT_HOLDING, + + /** Sells every share the player owns, across all tickers. */ + SELL_ENTIRE_PORTFOLIO; } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsController.java index 52e23bc..b934d50 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsController.java @@ -110,6 +110,45 @@ protected void initInteractions() { player.getNetWorthAsFloatProperty().addListener((observable, o, n) -> { pushSnapshot(); }); + + getViewElement().setOnAction( + StatsActions.SELL_ENTIRE_PORTFOLIO, this::sellEntirePortfolio); + } + + /** + * Sells every share the player currently owns, one ticker at a time. + * + *Uses the same {@link Exchange#sell(BigDecimal, String, Player)} + * entry point as the dashboard's individual sell button, so the + * commission, tax and {@code networthAsFloatProperty} updates all + * fire exactly as if the player had sold each ticker manually. The + * existing player-property listeners then refresh the stats widget + * (and every other widget that listens on net-worth) automatically.
+ * + *If the portfolio is already empty this is a no-op.
+ * */ + private void sellEntirePortfolio() { + // Snapshot the symbols and their totals first so we don't iterate + // over a list that mutates while we sell. + Map