diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/StockAppController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/StockAppController.java
index 7406913..513a9ca 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/StockAppController.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/StockAppController.java
@@ -6,6 +6,9 @@
import edu.ntnu.idi.idatt2003.gruppe42.model.transaction.Transaction;
import edu.ntnu.idi.idatt2003.gruppe42.view.apps.StockApp;
import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
@@ -17,39 +20,30 @@
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
-/**
- * Controller for the Stock application.
- *
- *
This controller manages:
- *
- * - Displaying and updating stock market data
- * - Searching and filtering stocks
- * - Navigating between stock overview and detail views
- * - Handling buy and sell transactions
- * - Updating UI state based on game conditions (e.g. weekends)
- *
- *
- * It acts as the bridge between the market model, player portfolio,
- * and the stock trading UI.
- */
+/** Controller for StockApp. */
public final class StockAppController implements AppController {
private final MarketController marketController;
private final StockApp stockApp;
private final Player player;
private Stock currentStock;
+
+ private enum SortType {
+ ALPHA, GAINER, PRICE
+ }
+
+ private SortType currentSort = SortType.ALPHA;
+ private boolean isDescending = false;
+
+ /** True while the game is on Saturday or Sunday. */
private boolean isWeekend = false;
+
+ /** Called when the player clicks Trade during a weekend. */
private Runnable onMarketClosed;
+
private Runnable onInsufficientFunds;
- /**
- * Constructs the Stock application controller and initializes stock data,
- * UI listeners, and trading actions.
- *
- * @param stockApp the stock application view
- * @param marketController the market controller providing stock data and trading logic
- * @param player the player performing trades
- */
+ /** Constructs the controller. */
public StockAppController(
final StockApp stockApp,
final MarketController marketController,
@@ -59,8 +53,7 @@ public StockAppController(
this.marketController = marketController;
this.player = player;
- stockApp.getStockList().getItems()
- .addAll(marketController.getExchange().getAllStocks());
+ updateStockList();
stockApp.getStockList().setCellFactory(lv -> createStockCell());
@@ -69,10 +62,39 @@ public StockAppController(
if (currentStock != null && !newValue.isEmpty()) {
navigateToSearch();
}
- stockApp.getStockList().getItems()
- .setAll(marketController.getMarket(newValue));
+ updateStockList();
});
+ stockApp.getAlphaButton().setOnAction(e -> {
+ if (currentSort == SortType.ALPHA) {
+ isDescending = !isDescending;
+ } else {
+ currentSort = SortType.ALPHA;
+ isDescending = false;
+ }
+ updateStockList();
+ });
+
+ stockApp.getGainerButton().setOnAction(e -> {
+ if (currentSort == SortType.GAINER) {
+ isDescending = !isDescending;
+ } else {
+ currentSort = SortType.GAINER;
+ isDescending = false;
+ }
+ updateStockList();
+ });
+
+ stockApp.getPriceButton().setOnAction(e -> {
+ if (currentSort == SortType.PRICE) {
+ isDescending = !isDescending;
+ } else {
+ currentSort = SortType.PRICE;
+ isDescending = true;
+ }
+ updateStockList();
+ });
+
stockApp.getQuantitySpinner().valueProperty().addListener(
(obs, old, newValue) -> {
if (currentStock != null) {
@@ -85,11 +107,7 @@ public StockAppController(
stockApp.getBackButton().setOnAction(e -> navigateToSearch());
}
- /**
- * Navigates the UI to display details for a selected stock.
- *
- * @param stock the stock to display
- */
+ /** Navigates to stock detail page. */
public void navigateToStock(final Stock stock) {
currentStock = stock;
stock.getStockGraph().setVisibility(true);
@@ -98,6 +116,7 @@ public void navigateToStock(final Stock stock) {
updateReceipt();
}
+ /** Navigates to search page. */
private void navigateToSearch() {
if (currentStock != null) {
currentStock.getStockGraph().setVisibility(false);
@@ -106,6 +125,7 @@ private void navigateToSearch() {
stockApp.showSearchPage();
}
+ /** Creates list cell for stock row. */
private ListCell createStockCell() {
return new ListCell<>() {
private final HBox row = new HBox(15);
@@ -119,7 +139,7 @@ private ListCell createStockCell() {
row.setAlignment(Pos.CENTER_LEFT);
row.getStyleClass().add("stock-list-item");
- final VBox names = new VBox(2, symbolLabel, companyLabel);
+ VBox names = new VBox(2, symbolLabel, companyLabel);
symbolLabel.getStyleClass().add("stock-symbol");
companyLabel.getStyleClass().add("stock-company");
@@ -167,20 +187,14 @@ protected void updateItem(final Stock stock, final boolean empty) {
};
}
- /**
- * Sets whether the market is currently in weekend mode.
- *
- * When weekend mode is active, trading is disabled.
- *
- * @param weekend true if it is weekend, false otherwise
- */
+ /** Sets weekend status. */
public void setWeekend(final boolean weekend) {
this.isWeekend = weekend;
- Platform.runLater(this::updateTradeUi);
+ Platform.runLater(this::updateTradeUI);
}
/** Updates the Trade button and spinner state. */
- private void updateTradeUi() {
+ private void updateTradeUI() {
Button tradeButton = stockApp.getConfirmButton();
var spinner = stockApp.getQuantitySpinner();
@@ -212,24 +226,17 @@ private void updateTradeUi() {
}
}
- /**
- * Sets a callback executed when the user attempts to trade while the market is closed.
- *
- * @param callback action to run when market is closed
- */
+ /** Sets market closed callback. */
public void setOnMarketClosed(final Runnable callback) {
this.onMarketClosed = callback;
}
- /**
- * Sets a callback executed when a buy transaction fails due to insufficient funds.
- *
- * @param callback action to run when funds are insufficient
- */
+ /** Sets insufficient funds callback. */
public void setOnInsufficientFunds(final Runnable callback) {
this.onInsufficientFunds = callback;
}
+ /** Handles trade button action. */
private void handleTransaction() {
if (isWeekend) {
if (onMarketClosed != null) {
@@ -261,15 +268,18 @@ private void handleTransaction() {
}
}
+ /** Buys given stock. */
private void handleBuy(final Stock stock, final BigDecimal quantity) throws Exception {
Transaction transaction = marketController.getExchange()
.buy(stock.getSymbol(), quantity);
if (transaction != null) {
transaction.commit(player);
player.getPortfolio().addShare(transaction.getShare());
+ player.updateStatus();
}
}
+ /** Sells given stock. */
private void handleSell(final Stock stock, final BigDecimal quantity) {
player.getPortfolio().getShares().stream()
.filter(share -> share.getStock().getSymbol().equals(stock.getSymbol()))
@@ -288,28 +298,47 @@ private void handleSell(final Stock stock, final BigDecimal quantity) {
System.out.println("Transaction failed");
}
player.getPortfolio().removeShare(transaction.getShare());
+ player.updateStatus();
}
});
}
+ /** Updates receipt view. */
private void updateReceipt() {
if (currentStock != null) {
Platform.runLater(() -> {
- stockApp.getReceipt().update(
+ stockApp.getReceipt().update(
currentStock,
new BigDecimal(stockApp.getQuantitySpinner().getValue())
- );
- updateTradeUi();
+ );
+ updateTradeUI();
});
}
}
- /**
- * Advances the stock market simulation by one tick.
- *
- *
This updates stock prices, refreshes the UI list, and updates
- * the currently selected stock view if applicable.
- */
+ /** Updates the stock list with current sort and filter. */
+ private void updateStockList() {
+ List stocks = new ArrayList<>(marketController.getMarket(stockApp.getSearchField().getText()));
+ Comparator comparator;
+ switch (currentSort) {
+ case GAINER:
+ comparator = Comparator.comparing(Stock::getLatestPriceChange);
+ break;
+ case PRICE:
+ comparator = Comparator.comparing(Stock::getSalesPrice);
+ break;
+ default:
+ comparator = Comparator.comparing(Stock::getSymbol);
+ break;
+ }
+ if (isDescending) {
+ comparator = comparator.reversed();
+ }
+ stocks.sort(comparator);
+ stockApp.getStockList().getItems().setAll(stocks);
+ }
+
+ /** Processes next tick. */
@Override
public void nextTick() {
marketController.updateMarket();
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopViewController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopViewController.java
index b866710..7e340f4 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopViewController.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopViewController.java
@@ -121,7 +121,7 @@ public DesktopViewController(
() -> {
News news = new News(event.company(), event.trend());
mailController.createMessage(
- news.generateAuthor(), event.company(), news.generateNews());
+ news.generateAuthor(), "BREAKING NEWS", news.generateNews());
}));
}
@@ -215,8 +215,10 @@ private void setupDesktopUi() {
}
private void setupListeners() {
- timeAndWeatherController.hourProperty().addListener((obs, ov, nv) ->
- desktopView.updateClock(timeAndWeatherController.getTimeString()));
+ timeAndWeatherController.hourProperty().addListener((obs, ov, nv) -> {
+ desktopView.updateClock(timeAndWeatherController.getTimeString());
+ player.updateStatus();
+ });
timeAndWeatherController.dayIndexProperty().addListener((obs, ov, nv) -> {
String dayName = timeAndWeatherController.getDayOfWeekString();
@@ -235,6 +237,7 @@ private void setupListeners() {
player.getNameProperty().addListener((obs, ov, nv) ->
desktopView.updatePlayerName(nv));
+ player.updateStatus();
player.getStatusProperty().addListener((obs, ov, nv) ->
desktopView.updatePlayerStatus(nv.name()));
@@ -264,6 +267,7 @@ private void doAdvanceWeek() {
marketController.advanceWeek();
timeAndWeatherController.advanceWeek();
mailController.clearMessages();
+ player.updateStatus();
}
private void showMarketClosedWarning() {
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/News.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/News.java
index 87e41ab..8eb8d75 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/News.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/News.java
@@ -4,49 +4,66 @@
import java.util.List;
import java.util.Random;
-/**
- * Generates news articles and author information for a given company based on its market trend.
- */
public class News {
private final String company;
private final int trend;
private final Random random = new Random();
-
- /**
- * Constructs a News generator for the given company and trend.
- *
- * @param company the name of the company the news is about.
- * @param trend a positive value for good news, negative for bad news.
- */
- public News(String company, int trend) {
+ public News(String company, int trend){
this.company = company;
this.trend = trend;
}
-
- /**
- * Returns a randomly selected author email address for the news article.
- *
- * @return the author's email address.
- */
- public String generateAuthor() {
+ public String generateAuthor(){
List authors = new ArrayList<>();
authors.add("market@stocks.com");
+ authors.add("myfatherworksatmicrosoft@hotmail.com");
+ authors.add("definitely.obama@whitehouse.gov");
+ authors.add("bigfoot.financial@yeti.net");
+ authors.add("notaciaagent@cia.gov");
+ authors.add("iamyourfather@deathstar.empire");
+ authors.add("shakespeareswifi@globe.theatre");
+ authors.add("dinosaur.investor@jurassic.park");
+ authors.add("flat.earth.stonks@edgeoftheworld.com");
+ authors.add("timtraveler2089@yahoo.com");
+ authors.add("schroedingers.portfolio@maybe.com");
+ authors.add("elon@mymusk.biz");
+ authors.add("god@heaven.org");
+ authors.add("santaclaus@northpole.gifts");
+ authors.add("area51.leaks@ufo.gov");
+ authors.add("theguywhosneezedonthestockmarket@2008crash.com");
+ authors.add("nephew.of.warren.buffet@gmail.com");
+ authors.add("batman@notbruce.wayne");
+ authors.add("mycatinvestedmysavings@meow.finance");
+ authors.add("nsa.definitely.not.reading.this@nsa.gov");
return authors.get(random.nextInt(authors.size()));
}
-
- /**
- * Generates a news headline based on the company's trend direction.
- *
- * @return a positive headline if the trend is good, a negative headline otherwise.
- */
- public String generateNews() {
+ public String generateNews(){
if (trend > 0) {
List goodNews = new ArrayList<>();
- goodNews.add("Turns out the product of " + company + " is good for the environment");
+ goodNews.add("Turns out the product of " + company + " is good for the environment.");
+ goodNews.add("Beaver population increased due to the efforts of " + company + ".");
+ goodNews.add("Eminem dropped new hit single, namedropping " + company + ".");
+ goodNews.add("Founder of " + company + " seen feeding the homeless.");
+ goodNews.add(company + " got frontpage in new forbes magazine, on top companies to invest in.");
+ goodNews.add("IShowSpeed seen using " + company + " products in the background of viral livestream.");
+ goodNews.add("New studies show that " + company + " took part in inventing the rainbow back in 4,542,997,974 BC");
+ goodNews.add(company + " has made breakthroughs in the field of artificial intelligence.");
+ goodNews.add("After the release of the brand new logo of " + company + ", the logo has been called the \"a glimpse of the future\".");
+ goodNews.add("EX-EMPLOYEES of " + company + " REVEAL that the work environment and safety regulations are not up to basic standards but far surpassing them.");
+ goodNews.add("Founder of " + company + " seen rudely pushing a homeless man out of the road, saving his life.");
return goodNews.get(random.nextInt(goodNews.size()));
}
List badNews = new ArrayList<>();
- badNews.add("Turns out the product of " + company + " is bad for the environment");
+ badNews.add("Turns out the product of " + company + " is bad for the environment.");
+ badNews.add("Founder of " + company + " seen rudely pushing a homeless man out of the way.");
+ badNews.add(company + " mentioned in epstein files.");
+ badNews.add(company + " has created many dams for beavers, causing them to lay around in the sun all day and die of laziness.");
+ badNews.add(company + " initiated a campaign to feed the homeless with burgers, using dog meat. This has decreased the number of starvation deaths among the homeless.");
+ badNews.add("Turns out " + company + " created the first covid-19 vaccines, which are not safe for humans.");
+ badNews.add("Extensive research of " + company + " shows financial holes in the company.");
+ badNews.add("Founder of " + company + " heiling at press conference discussing great ideas.");
+ badNews.add("EX-EMPLOYEES of " + company + " REVEAL that the work environment and safety regulations are not up to basic standards.");
+ badNews.add("US-Army found using " + company + " issued weapons that malfunctioned causing mass civilian casualties.");
+ badNews.add(company + " got frontpage in new forbes magazine, on top companies not to invest in.");
return badNews.get(random.nextInt(badNews.size()));
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Player.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Player.java
index a8f24c6..d07f6ff 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Player.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Player.java
@@ -3,18 +3,10 @@
import edu.ntnu.idi.idatt2003.gruppe42.model.exceptions.InsufficientFunds;
import edu.ntnu.idi.idatt2003.gruppe42.model.transaction.TransactionArchive;
import java.math.BigDecimal;
-import javafx.beans.property.IntegerProperty;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.SimpleIntegerProperty;
-import javafx.beans.property.SimpleObjectProperty;
-import javafx.beans.property.SimpleStringProperty;
-import javafx.beans.property.StringProperty;
-
-/**
- * Represents a player in the simulation.
- *
- * Tracks the player's name, funds, portfolio, lives, and progression status.
- */
+
+import javafx.beans.property.*;
+
+/** Represents a player. */
public class Player {
private static final int MAX_LIVES = 3;
@@ -27,12 +19,6 @@ public class Player {
private final IntegerProperty lives = new SimpleIntegerProperty(MAX_LIVES);
private final ObjectProperty status = new SimpleObjectProperty<>(Status.NOVICE);
- /**
- * Constructs a new Player with the given name and starting funds.
- *
- * @param name the player's display name.
- * @param startingMoney the amount of money the player starts with.
- */
public Player(final String name, final BigDecimal startingMoney) {
this.name.set(name);
this.startingMoney = startingMoney;
@@ -41,18 +27,27 @@ public Player(final String name, final BigDecimal startingMoney) {
this.transactionArchive = new TransactionArchive();
}
+
+ /** @return name property. */
public StringProperty getNameProperty() {
return name;
}
-
+ /** @return player's name. */
public String getName() {
return name.get();
}
+ /** Sets player's name. */
+ public void setName(final String name) {
+ this.name.set(name);
+ }
+
+ /** @return starting money. */
public BigDecimal getStartingMoney() {
return startingMoney;
}
+ /** @return current money. */
public BigDecimal getMoney() {
return money;
}
@@ -61,106 +56,77 @@ public String getStatus() {
return status.get().name();
}
- /**
- * Sets the player's display name.
- *
- * @param name the new name.
- */
- public void setName(final String name) {
- this.name.set(name);
- }
-
- /**
- * Adds the given amount to the player's funds.
- *
- * @param amount the amount to add.
- */
+ /** Adds money. */
public void addMoney(final BigDecimal amount) {
money = money.add(amount);
+ updateStatus();
}
- /**
- * Withdraws the given amount from the player's funds.
- *
- * @param amount the amount to withdraw.
- * @throws InsufficientFunds if the player does not have enough money.
- */
+ /** Withdraws money. */
public void withdrawMoney(final BigDecimal amount) throws InsufficientFunds {
if (money.compareTo(amount) < 0) {
throw new InsufficientFunds("Not enough money to withdraw " + amount);
}
money = money.subtract(amount);
+ updateStatus();
}
- /**
- * Deducts rent from the player's funds, allowing the balance to go negative.
- *
- * @param amount the rent amount to deduct.
- */
+ /** Deducts rent. */
public void deductRent(final BigDecimal amount) {
money = money.subtract(amount);
+ updateStatus();
}
+ /** @return true if in debt. */
public boolean isInDebt() {
return money.compareTo(BigDecimal.ZERO) < 0;
}
+ /** @return total net worth. */
public BigDecimal getNetWorth() {
return money.add(portfolio.getNetWorth());
}
+
+ /** @return lives property. */
public IntegerProperty getLivesProperty() {
return lives;
}
+ /** @return number of lives. */
public int getLives() {
return lives.get();
}
- /**
- * Decrements the player's life count by one, to a minimum of zero.
- */
+ /** Decrements life. */
public void loseLife() {
lives.set(Math.max(0, lives.get() - 1));
}
- /**
- * Returns the player's stock portfolio.
- *
- * @return the {@link Portfolio}.
- */
+
+ /** @return player's portfolio. */
public Portfolio getPortfolio() {
return portfolio;
}
- /**
- * Returns the player's transaction history.
- *
- * @return the {@link TransactionArchive}.
- */
+ /** @return transaction archive. */
public TransactionArchive getTransactionArchive() {
return transactionArchive;
}
- /**
- * Evaluates and returns the player's current status property.
- *
- * Status is determined by the number of weeks the player has been active
- * and their net worth relative to their starting money.
- *
- * @return the status {@link ObjectProperty}, updated to reflect the current progression level.
- */
- public ObjectProperty getStatusProperty() {
+
+ /** Updates the player's status. */
+ public void updateStatus() {
final int investorWeeks = 10;
final int speculatorWeeks = 20;
- final double investorMultiplier = 1.2;
- final double speculatorMultiplier = 2.0;
+ final double investorMult = 1.2;
+ final double speculatorMult = 5.0;
int weeksActive = transactionArchive.countDistinctWeeks();
BigDecimal netWorth = getNetWorth();
- BigDecimal investorTarget = startingMoney.multiply(BigDecimal.valueOf(investorMultiplier));
- BigDecimal speculatorTarget = startingMoney.multiply(BigDecimal.valueOf(speculatorMultiplier));
+ BigDecimal investorTarget = startingMoney.multiply(BigDecimal.valueOf(investorMult));
+ BigDecimal speculatorTarget = startingMoney.multiply(BigDecimal.valueOf(speculatorMult));
boolean isSpeculator = weeksActive >= speculatorWeeks
&& netWorth.compareTo(speculatorTarget) >= 0;
@@ -174,6 +140,11 @@ public ObjectProperty getStatusProperty() {
} else {
status.set(Status.NOVICE);
}
+ }
+
+
+ /** @return player status property. */
+ public ObjectProperty getStatusProperty() {
return status;
}
}
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Stock.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Stock.java
index 0604a52..9751c9a 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Stock.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/Stock.java
@@ -6,26 +6,14 @@
import java.util.ArrayList;
import java.util.List;
-/**
- * Represents a publicly traded stock of a company in the simulation.
- *
- * Tracks historical prices, provides market data, and manages
- * its associated price graph and price update logic.
- */
+/** Represents a stock of a company. */
public class Stock {
private final String symbol;
private final String company;
private StockGraph stockGraph;
- private final List prices = new ArrayList<>();
- private final StockController stockController;
-
- /**
- * Constructs a new Stock with an initial sales price.
- *
- * @param symbol the ticker symbol of the stock.
- * @param company the name of the company.
- * @param salesPrice the initial sales price.
- */
+ private List prices = new ArrayList<>();
+ private StockController stockController;
+ /** Constructs a new stock. */
public Stock(
final String symbol,
final String company,
@@ -37,82 +25,57 @@ public Stock(
stockController = new StockController(this);
}
+ /** @return the symbol. */
public String getSymbol() {
return symbol;
}
+ /** @return the company. */
public String getCompany() {
return company;
}
+ /** @return current sales price. */
public BigDecimal getSalesPrice() {
return prices.isEmpty() ? BigDecimal.ZERO : prices.get(prices.size() - 1);
}
- /**
- * Adds a new sales price to the price history.
- *
- * @param salesPrice the new price to record.
- */
+ /** Adds a new sales price. */
public void addNewSalesPrice(final BigDecimal salesPrice) {
prices.add(salesPrice);
}
- /**
- * Returns an unmodifiable copy of the full price history.
- *
- * @return the list of historical prices.
- */
+ /** @return historical prices. */
public List getHistoricalPrices() {
return List.copyOf(prices);
}
- /**
- * Returns the highest price the stock has ever reached.
- *
- * @return the all-time high price, or {@link BigDecimal#ZERO} if no prices exist.
- */
+ /** @return highest price. */
public BigDecimal getHighestPrice() {
return prices.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
}
- /**
- * Returns the lowest price the stock has ever reached.
- *
- * @return the all-time low price, or {@link BigDecimal#ZERO} if no prices exist.
- */
+ /** @return lowest price. */
public BigDecimal getLowestPrice() {
return prices.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
}
- /**
- * Returns the price change between the two most recent recorded prices.
- *
- * @return the difference between the latest and previous price,
- * or {@link BigDecimal#ZERO} if fewer than two prices exist.
- */
+ /** @return price change. */
public BigDecimal getLatestPriceChange() {
- final int minSize = 2;
- if (prices.size() < minSize) {
+ if (prices.isEmpty()) {
return BigDecimal.ZERO;
}
- return prices.get(prices.size() - 1)
- .subtract(prices.get(prices.size() - 2));
+ int index120Ago = Math.max(0, prices.size() - 121);
+ return getSalesPrice().subtract(prices.get(index120Ago));
}
- /**
- * Updates the stock price using the associated {@link StockController}.
- */
+ /** Updates the price. */
public void updatePrice() {
BigDecimal newPrice = stockController.updatePrice();
prices.add(newPrice);
}
- /**
- * Returns the stock's price graph, creating it lazily if not yet initialized.
- *
- * @return the {@link StockGraph} for this stock.
- */
+ /** @return stock graph. */
public StockGraph getStockGraph() {
if (stockGraph == null) {
stockGraph = new StockGraph();
@@ -120,12 +83,7 @@ public StockGraph getStockGraph() {
return stockGraph;
}
- /**
- * Generates a simulated price history by advancing the price a given number of steps.
- * The history is then reversed so prices appear in chronological order.
- *
- * @param stockResolution the number of price steps to simulate.
- */
+ /** Generates fake history. */
public void fakeHistory(int stockResolution) {
for (int i = 0; i < stockResolution; i++) {
updatePrice();
@@ -133,9 +91,7 @@ public void fakeHistory(int stockResolution) {
java.util.Collections.reverse(prices);
}
- /**
- * Updates the stock graph if it has been initialized and is currently visible.
- */
+ /** Updates the stock graph. */
public void updateStockGraph() {
if (stockGraph != null && stockGraph.getVisibility()) {
stockGraph.update(this);
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/Popup.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/Popup.java
index 4899203..e0d27dd 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/Popup.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/Popup.java
@@ -37,7 +37,6 @@
*/
public abstract class Popup {
- private static final boolean DEV_CSS_RELOAD = true;
private static final double ARC = 16;
private final App type;
@@ -48,6 +47,7 @@ public abstract class Popup {
private final Button closeButton;
protected final VBox content;
+ protected final ScrollPane scrollPane;
/**
* Constructs a popup window with specified size and position.
@@ -63,14 +63,13 @@ protected Popup(int width, int height, int x, int y, App type) {
content = new VBox();
content.getStyleClass().add("popup-content");
- content.setPrefSize(width, height);
+ content.setFillWidth(true);
- ScrollPane scrollPane = new ScrollPane(content);
+ scrollPane = new ScrollPane(content);
scrollPane.getStyleClass().add("popup-scroll-pane");
- scrollPane.setMinSize(width, height);
scrollPane.setFitToWidth(true);
- scrollPane.setFitToHeight(true);
- scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
+ scrollPane.setFitToHeight(false);
+ scrollPane.setPrefSize(width, height);
closeButton = buildCloseButton();
header = buildHeader(closeButton, type.getDisplayName());
@@ -98,15 +97,6 @@ protected Popup(int width, int height, int x, int y, App type) {
loadStylesheets();
- if (DEV_CSS_RELOAD) {
- wrapper.setFocusTraversable(true);
- wrapper.setOnKeyPressed(event -> {
- if (event.getCode() == javafx.scene.input.KeyCode.R) {
- loadStylesheets();
- System.out.println("[CSS] Popup stylesheets reloaded");
- }
- });
- }
}
private Rectangle buildClip(BorderPane target) {
@@ -140,6 +130,7 @@ private void loadStylesheets() {
// Map App to CSS
String appCss = switch (type) {
case BANK -> "/css/bank.css";
+ case MAIL -> "/css/mail.css";
case SETTINGS -> "/css/settings.css";
case STOCK -> "/css/stock.css";
case FILEPICKER -> "/css/filepicker.css";
@@ -157,28 +148,14 @@ private void loadStylesheets() {
}
try {
- if (DEV_CSS_RELOAD) {
- String projectDir = System.getProperty("user.dir");
- for (String s : sheets) {
- root.getStylesheets().add(devCopyToTemp(projectDir + "/src/main/resources" + s));
- }
- } else {
- for (String s : sheets) {
- root.getStylesheets().add(resource(s));
- }
+ for (String s : sheets) {
+ root.getStylesheets().add(resource(s));
}
} catch (Exception e) {
System.err.println("[CSS] Failed to load popup stylesheets: " + e.getMessage());
}
}
- private String devCopyToTemp(final String sourcePath) throws Exception {
- java.nio.file.Path src = java.nio.file.Paths.get(sourcePath);
- java.nio.file.Path tmp = java.nio.file.Files.createTempFile("css_", ".css");
- java.nio.file.Files.copy(src, tmp, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
- tmp.toFile().deleteOnExit();
- return tmp.toUri().toString();
- }
private Button buildCloseButton() {
Button button = new Button();
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/BankApp.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/BankApp.java
index c37e588..9d34d70 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/BankApp.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/BankApp.java
@@ -35,7 +35,7 @@ public class BankApp extends Popup {
/** Constructs the Bank app. */
public BankApp() {
- super(500, 550, 220, 100, App.BANK);
+ super(600, 550, 220, 100, App.BANK);
portfolioList = new ListView<>();
portfolioList.setSelectionModel(null);
@@ -73,7 +73,7 @@ private VBox buildSummaryCard() {
totalReturnLabel.setMinWidth(Region.USE_PREF_SIZE);
GridPane grid = new GridPane();
- grid.setHgap(40);
+ grid.setHgap(20);
grid.setVgap(15);
ColumnConstraints c0 = new ColumnConstraints();
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/MailApp.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/MailApp.java
index eec86c4..d420fae 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/MailApp.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/MailApp.java
@@ -13,9 +13,6 @@
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
-import javafx.scene.paint.Color;
-import javafx.scene.text.Font;
-import javafx.scene.text.FontWeight;
/**
* Mail app popup for viewing and managing messages.
@@ -30,7 +27,7 @@ public class MailApp extends Popup {
* @param controller the controller managing mail data
*/
public MailApp(MailController controller) {
- super(450, 400, 140, 140, App.MAIL);
+ super(450, 600, 140, 140, App.MAIL);
this.controller = controller;
this.mailList = new VBox(10);
buildContent();
@@ -42,9 +39,14 @@ private void buildContent() {
content.setPadding(new Insets(20));
content.setSpacing(15);
+ mailList.setFillWidth(true);
+ mailList.setMaxWidth(Double.MAX_VALUE);
+ VBox.setVgrow(mailList, Priority.ALWAYS);
+
Label title = new Label("Inbox");
- title.setFont(Font.font("System", FontWeight.BOLD, 22));
+ title.getStyleClass().add("mail-title");
content.getChildren().add(title);
+ mailList.getStyleClass().add("mail-list");
content.getChildren().add(mailList);
}
@@ -60,39 +62,46 @@ private void refreshMailList() {
}
private VBox createMailItem(Message message) {
- VBox item = new VBox(5);
- item.setPadding(new Insets(12));
- item.setStyle("-fx-background-color: #ffffff; -fx-border-color: #eeeeee; "
- + "-fx-border-radius: 8; -fx-background-radius: 8;");
+ VBox item = new VBox(8);
+ item.setPadding(new Insets(15));
+ item.setMaxWidth(Double.MAX_VALUE);
+ item.getStyleClass().add("mail-item");
+ if (!message.isSeen()) {
+ item.getStyleClass().add("mail-item-unread");
+ }
HBox top = new HBox(10);
top.setAlignment(Pos.CENTER_LEFT);
Label senderLabel = new Label(message.getAuthor());
- senderLabel.setFont(Font.font("System", FontWeight.BOLD, 14));
+ senderLabel.setMinWidth(0);
+ senderLabel.getStyleClass().add("mail-sender");
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
Label timeLabel = new Label(message.getFormattedTime());
- timeLabel.setTextFill(Color.GRAY);
- timeLabel.setFont(Font.font(12));
+ timeLabel.getStyleClass().add("mail-time");
+ timeLabel.setMinWidth(Region.USE_PREF_SIZE);
top.getChildren().addAll(senderLabel, spacer, timeLabel);
Label subLabel = new Label(message.getTitle());
- subLabel.setFont(Font.font("System", FontWeight.MEDIUM, 13));
+ subLabel.getStyleClass().add("mail-subject");
+ subLabel.setWrapText(true);
+ subLabel.setMaxWidth(Double.MAX_VALUE);
Label contentLabel = new Label(message.getContent());
- contentLabel.setTextFill(Color.GRAY);
+ contentLabel.getStyleClass().add("mail-content");
contentLabel.setWrapText(true);
+ contentLabel.setMaxWidth(Double.MAX_VALUE);
item.getChildren().addAll(top, subLabel, contentLabel);
if (message.isSeen()) {
HBox bottom = new HBox();
bottom.setAlignment(Pos.CENTER_RIGHT);
- Button seenButton = new Button("Seen");
+ Button seenButton = new Button("Mark as Read");
seenButton.getStyleClass().add("primary-button");
seenButton.setOnAction(event -> {
message.setSeen(true);
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/StockApp.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/StockApp.java
index a25141c..c9d30d8 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/StockApp.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/StockApp.java
@@ -25,6 +25,9 @@ public class StockApp extends Popup {
private final ListView stockList;
private final TextField searchField;
+ private final Button alphaButton;
+ private final Button gainerButton;
+ private final Button priceButton;
private Label priceLabel;
private final Spinner quantitySpinner;
private final Button confirmButton;
@@ -40,6 +43,13 @@ public StockApp(final Player player) {
this.searchField.getStyleClass().add("search-field");
this.searchField.setMaxWidth(Double.MAX_VALUE);
+ this.alphaButton = new Button("A-Z");
+ this.alphaButton.getStyleClass().add("sort-button");
+ this.gainerButton = new Button("Gainer");
+ this.gainerButton.getStyleClass().add("sort-button");
+ this.priceButton = new Button("Price");
+ this.priceButton.getStyleClass().add("sort-button");
+
this.stockList = new ListView<>();
VBox.setVgrow(this.stockList, Priority.ALWAYS);
this.stockList.setSelectionModel(null);
@@ -70,7 +80,9 @@ public StockApp(final Player player) {
public void showSearchPage() {
content.setPadding(new Insets(20));
content.setSpacing(20);
- content.getChildren().setAll(searchField, stockList);
+ HBox searchBox = new HBox(10, searchField, alphaButton, gainerButton, priceButton);
+ HBox.setHgrow(searchField, Priority.ALWAYS);
+ content.getChildren().setAll(searchBox, stockList);
}
/** Shows stock detail page. */
@@ -152,4 +164,19 @@ public Receipt getReceipt() {
public Button getBackButton() {
return backButton;
}
+
+ /** @return alpha button. */
+ public Button getAlphaButton() {
+ return alphaButton;
+ }
+
+ /** @return gainer button. */
+ public Button getGainerButton() {
+ return gainerButton;
+ }
+
+ /** @return price button. */
+ public Button getPriceButton() {
+ return priceButton;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/views/DesktopView.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/views/DesktopView.java
index 1a3b94d..19f352d 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/views/DesktopView.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/views/DesktopView.java
@@ -30,7 +30,6 @@
*/
public final class DesktopView {
- private static final boolean DEV_CSS_RELOAD = true;
public enum ModalLayer { RAPPORT, WARNING, GAME_OVER }
@@ -94,18 +93,6 @@ public Pane getRoot() {
loadStylesheets();
- if (DEV_CSS_RELOAD) {
- sceneRoot.setFocusTraversable(true);
- sceneRoot.sceneProperty().addListener((obs, oldScene, newScene) -> {
- if (newScene != null) sceneRoot.requestFocus();
- });
- sceneRoot.setOnKeyPressed(event -> {
- if (event.getCode() == KeyCode.R) {
- loadStylesheets();
- System.out.println("[CSS] Stylesheets reloaded");
- }
- });
- }
}
return sceneRoot;
}
@@ -123,46 +110,24 @@ public Pane getPopupOverlay() {
private void loadStylesheets() {
sceneRoot.getStylesheets().clear();
try {
- if (DEV_CSS_RELOAD) {
- String projectDir = System.getProperty("user.dir");
- sceneRoot.getStylesheets().addAll(
- devCopyToTemp(projectDir + "/src/main/resources/css/global.css"),
- devCopyToTemp(projectDir + "/src/main/resources/css/desktop.css"),
- devCopyToTemp(projectDir + "/src/main/resources/css/components.css"),
- devCopyToTemp(projectDir + "/src/main/resources/css/stock.css"),
- devCopyToTemp(projectDir + "/src/main/resources/css/bank.css"),
- devCopyToTemp(projectDir + "/src/main/resources/css/receipt.css"),
- devCopyToTemp(projectDir + "/src/main/resources/css/rapport.css"),
- devCopyToTemp(projectDir + "/src/main/resources/css/filepicker.css"),
- devCopyToTemp(projectDir + "/src/main/resources/css/settings.css"),
- devCopyToTemp(projectDir + "/src/main/resources/css/warning.css")
- );
- } else {
- sceneRoot.getStylesheets().addAll(
- resource("/css/global.css"),
- resource("/css/desktop.css"),
- resource("/css/components.css"),
- resource("/css/stock.css"),
- resource("/css/bank.css"),
- resource("/css/receipt.css"),
- resource("/css/rapport.css"),
- resource("/css/filepicker.css"),
- resource("/css/settings.css"),
- resource("/css/warning.css")
- );
- }
+ sceneRoot.getStylesheets().addAll(
+ resource("/css/global.css"),
+ resource("/css/desktop.css"),
+ resource("/css/components.css"),
+ resource("/css/mail.css"),
+ resource("/css/stock.css"),
+ resource("/css/bank.css"),
+ resource("/css/receipt.css"),
+ resource("/css/rapport.css"),
+ resource("/css/filepicker.css"),
+ resource("/css/settings.css"),
+ resource("/css/warning.css")
+ );
} catch (Exception e) {
System.err.println("[CSS] Failed to load stylesheets: " + e.getMessage());
}
}
- private String devCopyToTemp(final String sourcePath) throws Exception {
- Path src = Paths.get(sourcePath);
- Path tmp = Files.createTempFile("css_", ".css");
- Files.copy(src, tmp, StandardCopyOption.REPLACE_EXISTING);
- tmp.toFile().deleteOnExit();
- return tmp.toUri().toString();
- }
/** Registers a popup root. */
public void registerPopup(final Node popupRoot) {
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/views/StartView.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/views/StartView.java
index 4c4d18a..3f04b6f 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/views/StartView.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/views/StartView.java
@@ -18,8 +18,6 @@
/** Start screen view. */
public class StartView {
- // Set to false (or delete this + all DEV_CSS_RELOAD blocks) before release.
- private static final boolean DEV_CSS_RELOAD = true;
private final TextField usernameField = new TextField();
private final Button loginButton = new Button("→");
@@ -101,19 +99,6 @@ public BorderPane getRoot() {
loadStylesheets();
- // DEV_CSS_RELOAD: press R to hot-reload CSS from source without restarting
- if (DEV_CSS_RELOAD) {
- content.setFocusTraversable(true);
- content.sceneProperty().addListener((obs, oldScene, newScene) -> {
- if (newScene != null) content.requestFocus();
- });
- content.setOnKeyPressed(event -> {
- if (event.getCode() == javafx.scene.input.KeyCode.R) {
- loadStylesheets();
- System.out.println("[CSS] Stylesheets reloaded");
- }
- });
- }
root.setCenter(content);
return root;
@@ -122,32 +107,15 @@ public BorderPane getRoot() {
private void loadStylesheets() {
root.getStylesheets().clear();
try {
- if (DEV_CSS_RELOAD) {
- // DEV_CSS_RELOAD: reads directly from source, copies to temp to bust JavaFX cache
- String projectDir = System.getProperty("user.dir");
- root.getStylesheets().addAll(
- copyToTemp(projectDir + "/src/main/resources/css/global.css"),
- copyToTemp(projectDir + "/src/main/resources/css/login.css")
- );
- } else {
- root.getStylesheets().addAll(
- getClass().getResource("/css/global.css").toExternalForm(),
- getClass().getResource("/css/login.css").toExternalForm()
- );
- }
+ root.getStylesheets().addAll(
+ getClass().getResource("/css/global.css").toExternalForm(),
+ getClass().getResource("/css/login.css").toExternalForm()
+ );
} catch (Exception e) {
System.err.println("[CSS] Failed to load stylesheets: " + e.getMessage());
}
}
- // DEV_CSS_RELOAD: copies a CSS file to a unique temp path so JavaFX can't cache it
- private String copyToTemp(String sourcePath) throws Exception {
- java.nio.file.Path src = java.nio.file.Paths.get(sourcePath);
- java.nio.file.Path tmp = java.nio.file.Files.createTempFile("css_", ".css");
- java.nio.file.Files.copy(src, tmp, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
- tmp.toFile().deleteOnExit();
- return tmp.toUri().toString();
- }
/** @return the username field. */
public TextField getUsernameField() {
diff --git a/src/main/resources/css/mail.css b/src/main/resources/css/mail.css
new file mode 100644
index 0000000..8069bdc
--- /dev/null
+++ b/src/main/resources/css/mail.css
@@ -0,0 +1,57 @@
+.mail-list {
+ -fx-background-color: transparent;
+}
+
+.mail-item {
+ -fx-background-color: #ffffff;
+ -fx-border-color: #eeeeee;
+ -fx-border-width: 1;
+ -fx-border-radius: 8;
+ -fx-background-radius: 8;
+}
+
+.mail-item:hover {
+ -fx-background-color: #f8f9fa;
+ -fx-border-color: #e0e0e0;
+}
+
+.mail-item-unread {
+ -fx-border-color: #3498db;
+ -fx-border-width: 1.5;
+}
+
+.mail-sender {
+ -fx-font-weight: bold;
+ -fx-font-size: 14px;
+ -fx-text-fill: #2c3e50;
+ -fx-text-overrun: ellipsis;
+}
+
+.mail-time {
+ -fx-font-family: "System";
+ -fx-font-size: 11px;
+ -fx-text-fill: #7f8c8d;
+}
+
+.mail-subject {
+ -fx-font-family: "System";
+ -fx-font-weight: 600;
+ -fx-font-size: 13px;
+ -fx-text-fill: #34495e;
+ -fx-wrap-text: true;
+}
+
+.mail-content {
+ -fx-font-family: "System";
+ -fx-font-size: 12px;
+ -fx-text-fill: #7f8c8d;
+ -fx-wrap-text: true;
+}
+
+.mail-title {
+ -fx-font-family: "System";
+ -fx-font-weight: bold;
+ -fx-font-size: 24px;
+ -fx-text-fill: #2c3e50;
+ -fx-wrap-text: true;
+}
diff --git a/src/main/resources/css/popup.css b/src/main/resources/css/popup.css
index f15ca7b..d9b3508 100644
--- a/src/main/resources/css/popup.css
+++ b/src/main/resources/css/popup.css
@@ -37,12 +37,16 @@
}
.popup-scroll-pane {
- -fx-background-color: -popup-bg;
- -fx-viewport-background-color: -popup-bg;
+ -fx-background-color: white;
+ -fx-background: white;
}
-.popup-scroll-pane:focused {
- -fx-background-insets: 0;
+.popup-scroll-pane > .viewport {
+ -fx-background-color: white;
+}
+
+.popup-content {
+ -fx-background-color: white;
}
.popup-content {
diff --git a/src/main/resources/css/stock.css b/src/main/resources/css/stock.css
index add33f1..987276f 100644
--- a/src/main/resources/css/stock.css
+++ b/src/main/resources/css/stock.css
@@ -54,6 +54,14 @@
-fx-padding: 5 15 5 15;
}
+.sort-button {
+ -fx-background-color: -surface;
+ -fx-background-radius: 15;
+ -fx-border-color: -popup-border;
+ -fx-border-radius: 15;
+ -fx-cursor: hand;
+}
+
.stock-price-large {
-fx-font-weight: bold;
-fx-font-size: 24px;