Skip to content

Commit

Permalink
fix handling of csv files such that user selected file is formated, e…
Browse files Browse the repository at this point in the history
…lse a warning with error lines will appear
  • Loading branch information
peretr committed May 15, 2026
1 parent fcab19d commit e20e426
Show file tree
Hide file tree
Showing 11 changed files with 241 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -1,36 +1,92 @@
package edu.ntnu.idi.idatt2003.gruppe42.Controller.AppControllers;

import edu.ntnu.idi.idatt2003.gruppe42.Model.Exchange;
import edu.ntnu.idi.idatt2003.gruppe42.Model.StockFileHandler;
import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.SettingsApp;
import java.nio.file.Path;
import java.util.Objects;

public class SettingsAppController implements AppController {

private SettingsApp settingsApp;
private Path userSelectedPath;
private Runnable onUserSelection;
private Runnable onSelectedFileFailed;
private String errorMessage;

public SettingsAppController(SettingsApp settingsApp) {
private final SettingsApp settingsApp;

public SettingsAppController(final SettingsApp settingsApp) {
this.settingsApp = settingsApp;
userSelectedPath = loadDefaultPath();
refreshView();

if (!settingsApp.isLoggedIn()) {
settingsApp.getOpenFileChooserButton().setOnAction(event -> {
userSelectedPath = settingsApp.getFileChooser().showOpenDialog(settingsApp.getRoot().getScene().getWindow()).toPath();
System.out.println(userSelectedPath.toString());
onUserSelection.run();
});
settingsApp.getOpenFileChooserButton().setOnAction(event -> openFileChooser());
}
}

private void openFileChooser() {
var file = settingsApp.getFileChooser()
.showOpenDialog(settingsApp.getRoot().getScene().getWindow());

if (file == null) {
return; // user cancelled — keep current path
}

Path selected = file.toPath();

if (isValidStockFile(selected)) {
userSelectedPath = selected;
onUserSelection.run();
} else {
onSelectedFileFailed.run();
userSelectedPath = loadDefaultPath();
}

refreshView();
}

private boolean isValidStockFile(final Path path) {
try {
new Exchange(StockFileHandler.readFromFile(path));
return true;
} catch (Exception e) {
errorMessage = e.getMessage();
return false;
}
}

private Path loadDefaultPath() {
try {
return Path.of(Objects.requireNonNull(
getClass().getClassLoader().getResource("stocks.csv")).toURI());
} catch (Exception e) {
System.err.println("Failed to load default stocks.csv: " + e.getMessage());
return null;
}
}

private void refreshView() {
settingsApp.setPath(userSelectedPath);
settingsApp.updateContent();
}

public Path getUserSelectedPath() {
return userSelectedPath;
}

public void setOnUserPathSelected(Runnable callback) {
public void setOnUserPathSelected(final Runnable callback) {
onUserSelection = callback;
}

@Override
public void nextTick() {
public void setOnSelectedFileFailed(final Runnable callback) {
onSelectedFileFailed = callback;
}

public String getErrorMessage() {
return errorMessage;
}
}

@Override
public void nextTick() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,14 @@ public final class MarketController {
/**
* Constructs a new MarketController and starts the market.
*/
public MarketController(Path userSelectedPath) {
public MarketController(Path path) {
this.stockResolution = 120;
System.out.println(userSelectedPath);
try {
Path path = Path.of(Objects.requireNonNull(getClass().getClassLoader()
.getResource("stocks.csv")).toURI());
if (userSelectedPath != null) {
path = userSelectedPath;
System.out.println(path);
}
exchange = new Exchange(StockFileHandler.readFromFile(path));

startMarket();
System.out.println("Market loaded");
} catch (IOException exception) {
System.out.println("File not found");
} catch (URISyntaxException exception) {
System.out.println("Invalid file path");
} catch (StockFileParseException exception) {
System.out.println("Invalid stock file format");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public PopupsController(List<Popup> popups) {

private void initPopup(Popup popup) {
makeDraggable(popup);
popup.getCloseButton().setOnAction(event -> hide(popup));
popup.getCloseButton().setOnAction(event -> hide(popup.getType()));
}

public boolean show(App type) {
Expand All @@ -45,11 +45,16 @@ public boolean show(App type) {
return true;
}

public boolean hide(Popup popup) {
if (popup == null) {
public boolean hide(App type) {
if (type == null) {
return false;
}
popup.getRoot().setVisible(false);
popups.stream()
.filter(popup -> popup.getType().equals(type))
.findFirst()
.ifPresent(popup -> {
popup.getRoot().setVisible(false);
});
audioManager.playSFX(Audio.CLOSE_APP);
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Random;

/**
Expand All @@ -29,6 +30,7 @@ public class StartViewController {
private final PopupsController popupsController;
private Path userSelectedPath;
private SettingsAppController settingsAppController;
private WarningApp warningApp;

private static final List<String> TATE_NAMES = List.of(
"Top G",
Expand Down Expand Up @@ -58,34 +60,31 @@ public StartViewController(final Millions application, final StartView startView
SettingsApp settingsApp = new SettingsApp(false);
settingsAppController = new SettingsAppController(settingsApp);

WarningApp warningApp = new WarningApp();
warningApp = new WarningApp();
warningApp.centerInParent();

startView.getRoot().getChildren().addAll(settingsApp.getRoot(), warningApp.getRoot());

settingsAppController.setOnUserPathSelected(this::updateUserSelectedPath);

warningApp.configure(
"⚠",
"Difficulty Required",
"Choose how much starting money you want before jumping in.",
"Got it"
);
settingsAppController.setOnSelectedFileFailed(this::showFileWarning);

popups.add(settingsApp);
popups.add(warningApp);
popupsController = new PopupsController(popups);

warningApp.getPrimaryButton().setOnAction(e -> {
popupsController.hide(warningApp);
});

startView.getUsernameField().textProperty().addListener(
(obs, old, newValue) -> username = newValue
);

startView.getLoginButton().setOnAction(event -> {
if (startView.getSelectedMode() == null) {
warningApp.configure(
"⚠",
"Difficulty Required",
"Choose how much starting money you want before jumping in.",
"Got it"
);
warningApp.getPrimaryButton().setOnAction(e -> popupsController.hide(App.WARNING));
popupsController.show(App.WARNING);
return;
}
Expand Down Expand Up @@ -124,6 +123,17 @@ private boolean isValidName(final String value) {
return value.matches("[a-zA-Z0-9]+");
}

private void showFileWarning(){
warningApp.configure(
"⚠",
"Invalid File",
settingsAppController.getErrorMessage(),
"Revert to default file"
);
warningApp.getPrimaryButton().setOnAction(e -> popupsController.hide(App.WARNING));
popupsController.show(App.WARNING);
}

private void updateUserSelectedPath() {
userSelectedPath = settingsAppController.getUserSelectedPath();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
* Includes methods for reading and writing from file.
*/
public final class StockFileHandler {
private static final Pattern SYMBOL_PATTERN = Pattern.compile("^[A-Z]{3}$");
private static final Pattern SYMBOL_PATTERN = Pattern.compile("^[A-Z.]{1,5}$");
private static final Pattern PRICE_PATTERN = Pattern.compile("^\\d+\\.\\d{2}$");
private static final int EXPECTED_FIELDS = 3;
private static final int EXPECTED_FIELDS = 3;

/**
* Private constructor to prevent instantiation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@

import edu.ntnu.idi.idatt2003.gruppe42.Model.App;
import edu.ntnu.idi.idatt2003.gruppe42.View.Popup;
import java.nio.file.Path;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.stage.FileChooser;

public class SettingsApp extends Popup {

private Button openFileChooserButton;
private FileChooser fileChooser;
private boolean isLoggedIn;
private Path userSelectedPath;

public SettingsApp(boolean isLoggedIn) {
super(720, 480, 80, 60, App.SETTINGS);
this.isLoggedIn = isLoggedIn;
openFileChooserButton = new Button("Import stock file");
fileChooser = new FileChooser();
updateContent();
}

Expand All @@ -36,16 +41,18 @@ public void setLoggedIn(boolean status) {

public void updateContent() {
if (!isLoggedIn) {
openFileChooserButton = new Button("Import stock file");
fileChooser = new FileChooser();
fileChooser.setTitle("Select .csv stock file");
fileChooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("CSV files","*.csv"));

content.getChildren().setAll(openFileChooserButton);
Label currentPathLabel = new Label("Current path: " + userSelectedPath);
content.getChildren().setAll(openFileChooserButton, currentPathLabel);
} else {
//TODO: settings in desktopView
content.getChildren().setAll();
}
}
public void setPath(Path path) {
userSelectedPath = path;
updateContent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;

/**
* Compact, reusable warning popup used across the entire game.
*
* configuring, then show the root and overlay via the desktop controller.</p>
*
* <p>The close button is always re-wired by the configure methods so it
* mirrors the appropriate dismissal action.</p>
*/
Expand All @@ -28,7 +27,7 @@ public final class WarningApp extends Popup {
private final HBox buttons;

public WarningApp() {
super(370, 230, 0, 0, App.WARNING);
super(370, 270, 0, 0, App.WARNING);

iconLabel.setStyle("-fx-font-size: 30px;");
iconLabel.setAlignment(Pos.CENTER);
Expand All @@ -41,6 +40,14 @@ public WarningApp() {
messageLabel.setWrapText(true);
messageLabel.setAlignment(Pos.CENTER);

ScrollPane messageScroll = new ScrollPane(messageLabel);
messageScroll.setFitToWidth(true);
messageScroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
messageScroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
messageScroll.setPrefHeight(200);
messageScroll.getStyleClass().add("warning-scroll");
messageScroll.setStyle("-fx-background-color: transparent; -fx-background: transparent;");

primaryButton.getStyleClass().add("primary-button");
primaryButton.setPrefWidth(150);

Expand All @@ -50,7 +57,7 @@ public WarningApp() {
buttons = new HBox(12, secondaryButton, primaryButton);
buttons.setAlignment(Pos.CENTER);

VBox inner = new VBox(14, iconLabel, titleLabel, messageLabel, buttons);
VBox inner = new VBox(10, iconLabel, titleLabel, messageScroll, buttons);
inner.setAlignment(Pos.CENTER);

content.setPadding(new Insets(24, 28, 24, 28));
Expand All @@ -62,9 +69,9 @@ public WarningApp() {
* Configures for a single-button (dismiss-only) scenario.
* The secondary button is hidden. The close button mirrors the primary.
*
* @param icon emoji icon shown at the top
* @param title bold heading inside the card
* @param message body text below the heading
* @param icon emoji icon shown at the top
* @param title bold heading inside the card
* @param message body text below the heading
* @param primaryLabel label on the single action button
*/
public void configure(
Expand All @@ -81,7 +88,6 @@ public void configure(
secondaryButton.setVisible(false);
secondaryButton.setManaged(false);

// Clear old handlers so callers can set fresh ones after configure
primaryButton.setOnAction(null);
secondaryButton.setOnAction(null);
getCloseButton().setOnAction(event -> primaryButton.fire());
Expand All @@ -91,11 +97,11 @@ public void configure(
* Configures for a two-button (choice) scenario.
* The secondary button is the "safe" cancel option; the close button mirrors it.
*
* @param icon emoji icon shown at the top
* @param title bold heading inside the card
* @param message body text below the heading
* @param secondaryLabel label on the left / cancel button
* @param primaryLabel label on the right / action button
* @param icon emoji icon shown at the top
* @param title bold heading inside the card
* @param message body text below the heading
* @param secondaryLabel label on the left / cancel button
* @param primaryLabel label on the right / action button
*/
public void configure(
final String icon,
Expand All @@ -118,7 +124,7 @@ public void configure(
getCloseButton().setOnAction(event -> secondaryButton.fire());
}

public void show(){
public void show() {
getRoot().setVisible(true);
getRoot().toFront();
}
Expand All @@ -132,4 +138,4 @@ public Button getPrimaryButton() {
public Button getSecondaryButton() {
return secondaryButton;
}
}
}
Loading

0 comments on commit e20e426

Please sign in to comment.