From d9295750aab492c188a74a74052c421dadf6026c Mon Sep 17 00:00:00 2001 From: Roar Date: Wed, 15 Apr 2026 19:38:12 +0200 Subject: [PATCH 001/142] Updated Exchange Added private method getTopStocks to avoid duplicate code, and change getGainers and getLosers to use getTopStocks. --- .../main/java/no/ntnu/gruppe53/Exchange.java | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/Exchange.java b/millions/src/main/java/no/ntnu/gruppe53/Exchange.java index 0f729a5..31fc20f 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Exchange.java +++ b/millions/src/main/java/no/ntnu/gruppe53/Exchange.java @@ -147,24 +147,34 @@ public void advance() { } /** - * Lists the given amount of stocks sorted by highest increase in sales price since last week. - * @param entries amount of stocks to be shown in list - * @return a descending list of stocks with the highest price increase since last week - * @throws IllegalArgumentException if number of entries in list is negative + * Sorts the stocks in an ascending order and returns a list of stocks. + * + * @param entries amount of entries in the list to display + * @param comparator the comparator used to compare the stocks + * @return a list of stocks + * @throws IllegalArgumentException if the amount of entries is negative */ - public List getGainers(int entries) { + private List getTopStocks(int entries, Comparator comparator) { if (entries < 0) { throw new IllegalArgumentException("Amount of entries in list cannot be negative."); } return stockMap.values().stream() - .sorted(Comparator.comparing(Stock::getLatestPriceChange).reversed()) - .filter(stock -> stock.getLatestPriceChange().compareTo(BigDecimal.ZERO) > 0) + .sorted(comparator) .limit(entries) - .collect(Collectors.toList()); } + /** + * Lists the given amount of stocks sorted by highest increase in sales price since last week. + * @param entries amount of stocks to be shown in list + * @return a descending list of stocks with the highest price increase since last week + * @throws IllegalArgumentException if number of entries in list is negative + */ + public List getGainers(int entries) { + return getTopStocks(entries, Comparator.comparing(Stock::getLatestPriceChange).reversed()); + } + /** * Lists the given amount of stocks sorted by the stocks with the lowest price increase (or * highest price decrease) @@ -174,16 +184,7 @@ public List getGainers(int entries) { * @throws IllegalArgumentException if number of entries in list is negative */ public List getLosers(int entries) { - if (entries < 0) { - throw new IllegalArgumentException("Amount of entries in list cannot be negative."); - } - - return stockMap.values().stream() - .sorted(Comparator.comparing(Stock::getLatestPriceChange)) - .filter(stock -> stock.getLatestPriceChange().compareTo(BigDecimal.ZERO) < 0) - .limit(entries) - - .collect(Collectors.toList()); + return getTopStocks(entries, Comparator.comparing(Stock::getLatestPriceChange)); } From 082a39e4c25f5efe48109f1fd0afa45873ec9f9b Mon Sep 17 00:00:00 2001 From: Roar Date: Wed, 15 Apr 2026 19:39:20 +0200 Subject: [PATCH 002/142] Updated ExchangeTest Added test methods for getGainers and getLosers that also target getTopStocks. --- .../java/no/ntnu/gruppe53/ExchangeTest.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/millions/src/test/java/no/ntnu/gruppe53/ExchangeTest.java b/millions/src/test/java/no/ntnu/gruppe53/ExchangeTest.java index d01ca79..7167bd1 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/ExchangeTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/ExchangeTest.java @@ -162,4 +162,47 @@ void advanceWeekTest() { } + @Test + void getGainersShouldSortCorrectly() { + appleStock.addNewSalesPrice(new BigDecimal("3000")); + microStock.addNewSalesPrice(new BigDecimal("100")); + ntnuStock.addNewSalesPrice(new BigDecimal("1500")); + + var gainList = exchange.getGainers(3); + + assertEquals(appleStock.getSymbol(), gainList.getFirst().getSymbol(), + "Biggest gainer should be 'AAPL'."); + assertEquals(ntnuStock.getSymbol(), gainList.get(1).getSymbol(), + "Second biggest gainer should be 'NTNU'."); + assertEquals(microStock.getSymbol(), gainList.getLast().getSymbol(), + "Third biggest gainer should be 'MSFT'."); + } + + @Test + void getLosersShouldSortCorrectly() { + appleStock.addNewSalesPrice(new BigDecimal("3000")); + microStock.addNewSalesPrice(new BigDecimal("100")); + ntnuStock.addNewSalesPrice(new BigDecimal("1500")); + + var gainList = exchange.getLosers(3); + + System.out.println(gainList.size()); + + assertEquals(microStock.getSymbol(), gainList.get(0).getSymbol(), + "Biggest loser should me 'MSFT'"); + assertEquals(ntnuStock.getSymbol(), gainList.get(1).getSymbol(), + "Second biggest loser should be 'NTNU'"); + assertEquals(appleStock.getSymbol(), gainList.get(2).getSymbol(), + "Least biggest loser should be 'AAPL'"); + } + + @Test + void getTopStocksShouldThrowIllegalArgumentExceptionsIfEntriesIsNegative() { + assertThrows(IllegalArgumentException.class, () -> exchange.getLosers(-1), + "It should not be possible to list a negative amount of entries."); + assertThrows(IllegalArgumentException.class, () -> exchange.getGainers(-2), + "It should not be possible to list a negative amount of entries."); + + } + } From 6f7e3d4cb1c32948a8c042892c0fdf2aa594d501 Mon Sep 17 00:00:00 2001 From: Roar Date: Wed, 15 Apr 2026 20:22:58 +0200 Subject: [PATCH 003/142] Updated FileHandler Changed logic to hopefully be OS independent. --- .../java/no/ntnu/gruppe53/FileHandler.java | 143 +++++++++--------- 1 file changed, 74 insertions(+), 69 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java b/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java index 8133e72..3fd4727 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java +++ b/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java @@ -4,7 +4,7 @@ import java.io.BufferedWriter; import java.io.IOException; import java.math.BigDecimal; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -12,117 +12,122 @@ import java.util.List; /** - * Handles file operations for the application. + * Handles reading and writing of .csv files for stocks. */ - public class FileHandler { /** - * Writes a list of {@link Stock}s to a text-file. Class is structed for {@code .csv} files. + * Writes a list of {@link Stock}s to a CSV file in UTF-8 encoding. * - * @param stockList the list of stocks to be converted to text - * @param path output path for the file - * @param filename name of the file (not including extension) + * @param stockList the list of stocks to be written + * @param path directory path for the file + * @param filename name of the file (without or with .csv extension) + * @throws IllegalArgumentException if stockList, path, or filename is null, or if stockList is empty */ public static void writeStocksToFile(List stockList, String path, String filename) { - String pathString = path + filename; - Path filePath = Paths.get(pathString); - - try (BufferedWriter writer = Files.newBufferedWriter(filePath, Charset.defaultCharset())) { - if (!Files.exists(filePath)) { - Files.createDirectories(filePath); + if (stockList == null) throw new IllegalArgumentException("StockList cannot be null"); + if (path == null || path.isBlank()) throw new IllegalArgumentException("Path cannot be null or blank"); + if (filename == null || filename.isBlank()) throw new IllegalArgumentException( + "Filename cannot be null or blank"); + + + path = path.trim(); + filename = filename.trim(); + String filenameWithExtension = filename.endsWith(".csv") ? filename : filename + ".csv"; + + Path filePath = Paths.get(path, filenameWithExtension); + + Path dirPath = filePath.getParent(); + if (dirPath != null && !Files.exists(dirPath)) { + try { + Files.createDirectories(dirPath); + } catch (IOException e) { + System.out.println("ERROR: Could not create directories."); + e.printStackTrace(); + return; } + } + try (BufferedWriter writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8)) { writer.write("#Ticker,Name,Price"); writer.newLine(); - writer.newLine(); for (Stock stock : stockList) { - String line = String.join(",", stock.getSymbol(), stock.getCompany(), + if (stock == null) continue; + String line = String.join(",", + stock.getSymbol(), + stock.getCompany(), stock.getSalesPrice().toString()); writer.write(line); writer.newLine(); } } catch (IOException e) { - System.out.println("ERROR: Something went wrong while writing the csv file."); - System.out.println("Message: " + e.getMessage()); + System.out.println("ERROR: Something went wrong while writing the CSV file."); + e.printStackTrace(); } } /** - * Reads a .csv file and converts it to a list of {@code Stock} objects. + * Reads a CSV file and converts it to a list of {@link Stock} objects. * - * @param path path to the file to be read - * @param filename name of the file to be read (including extension) - * @return a list of {@code Stock} objects converted from text + * @param path directory path of the CSV file + * @param filename CSV file name (with or without .csv extension) + * @return list of Stock objects */ public static List readStocksFromFile(String path, String filename) { List stocks = new ArrayList<>(); - String pathString = path + filename; - Path filePath = Paths.get(pathString); + if (path == null || filename == null) return stocks; + + path = path.trim(); + filename = filename.trim(); + String filenameWithExtension = filename.endsWith(".csv") ? filename : filename + ".csv"; - try (BufferedReader reader = Files.newBufferedReader(filePath, Charset.defaultCharset())) { + Path filePath = Paths.get(path, filenameWithExtension); + + if (!Files.exists(filePath)) { + System.out.println("ERROR: File not found: " + filePath.toAbsolutePath()); + return stocks; + } + + try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) { String line; - String symbol; - String name; - BigDecimal price; while ((line = reader.readLine()) != null) { - symbol = null; - name = null; - price = null; - int counter = 0; - String trimValue = line.trim(); + line = line.trim(); + + if (line.isBlank() || line.startsWith("#")) continue; - if (trimValue.isBlank() || (line.charAt(0) == '#')) { + String[] values = line.split(",", -1); + if (values.length != 3) { + System.out.println("ERROR: Bad line in CSV (expected 3 values): " + line); continue; } - String[] values = line.split(","); + String symbol = values[0].trim(); + String name = values[1].trim(); + BigDecimal price; - if (values.length != 3) { - System.out.println("ERROR: Bad line in csv: "); - System.out.print(line); - System.out.println("There should be 3 values total: " + - "symbol, name, price."); + try { + price = new BigDecimal(values[2].trim()); + } catch (NumberFormatException e) { + System.out.println("ERROR: Price is not a valid number: " + values[2]); continue; } - for (String value : values) { - trimValue = value.trim(); - - switch (counter) { - case 0: - symbol = trimValue; - break; - case 1: - name = trimValue; - break; - case 2: - try { - price = new BigDecimal(trimValue); - } - catch (NumberFormatException e) { - System.out.println("ERROR: Not a number. Last column in .csv file " + - "needs to be the price of the stock (number). Skipping..."); - price = null; - break; - } - } - counter++; - } - if (symbol == null || symbol.isBlank() || name == null || name.isBlank() || - price == null || price.compareTo(BigDecimal.ZERO) <= 0) { + if (symbol.isBlank() || name.isBlank() || price.compareTo(BigDecimal.ZERO) <= 0) { + System.out.println("ERROR: Invalid stock data, skipping: " + line); continue; } + stocks.add(new Stock(symbol, name, price)); } + + } catch (IOException e) { + System.out.println("ERROR: Something went wrong while reading the CSV file."); + e.printStackTrace(); } - catch (IOException e) { - System.out.println("ERROR: Something went wrong while reading the csv file."); - System.out.println("Message: " + e.getMessage()); - } + return stocks; } -} +} \ No newline at end of file From 127dd2f82acb1bb972fe7f65196f9354f331340a Mon Sep 17 00:00:00 2001 From: Roar Date: Wed, 15 Apr 2026 20:23:46 +0200 Subject: [PATCH 004/142] Updated FileHandlerTest Added tests for the new FileHandler logic. --- .../no/ntnu/gruppe53/FileHandlerTest.java | 148 ++++++++++-------- 1 file changed, 83 insertions(+), 65 deletions(-) diff --git a/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java b/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java index 76c4914..a5611b6 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java @@ -1,90 +1,108 @@ package no.ntnu.gruppe53; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.IOException; +import java.io.File; import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; +import java.nio.file.Path; +import java.util.Arrays; import java.util.List; -import static no.ntnu.gruppe53.FileHandler.*; import static org.junit.jupiter.api.Assertions.*; class FileHandlerTest { + + private Path tempDir; + + @BeforeEach + void setup() throws Exception { + tempDir = Files.createTempDirectory("stock_test_"); + } + + @AfterEach + void cleanup() throws Exception { + if (tempDir != null && Files.exists(tempDir)) { + Files.walk(tempDir) + .map(Path::toFile) + .forEach(File::delete); + } + } + @Test - void writeTest() throws IOException { - String filePathString = System.getProperty("user.dir") + "\\target\\test-output\\"; - String fileName = "stockTest.csv"; + void testWriteAndReadStocks() { + Stock stock1 = new Stock("AAPL", "Apple Inc.", new BigDecimal("150.25")); + Stock stock2 = new Stock("MSFT", "Microsoft Corp.", new BigDecimal("300.50")); + List stocks = Arrays.asList(stock1, stock2); + + String filename = "testStocks.csv"; + FileHandler.writeStocksToFile(stocks, tempDir.toString(), filename); - String symbol1 = "AAPL"; - String company1 = "Apple Inc."; - BigDecimal price1 = new BigDecimal("100"); + List readStocks = FileHandler.readStocksFromFile(tempDir.toString(), filename); + + assertEquals(stocks.size(), readStocks.size(), "Stock list size should match"); + for (int i = 0; i < stocks.size(); i++) { + Stock expected = stocks.get(i); + Stock actual = readStocks.get(i); + assertEquals(expected.getSymbol(), actual.getSymbol()); + assertEquals(expected.getCompany(), actual.getCompany()); + assertEquals(0, expected.getSalesPrice().compareTo(actual.getSalesPrice())); + } + } - String symbol2 = "NVDA"; - String company2 = "Nvidia"; - BigDecimal price2 = new BigDecimal("191.27"); + @Test + void testWriteWithEmptyList() { + List emptyList = List.of(); + String filename = "emptyStocks.csv"; - Stock testStock1 = new Stock(symbol1,company1, price1); - Stock testStock2 = new Stock(symbol2, company2, price2); + FileHandler.writeStocksToFile(emptyList, tempDir.toString(), filename); - List stockList = new ArrayList<>(); - stockList.add(testStock1); - stockList.add(testStock2); + List result = FileHandler.readStocksFromFile(tempDir.toString(), filename); + assertTrue(result.isEmpty(), "Reading empty stock list should return empty list"); + } - writeStocksToFile(stockList, filePathString, fileName); + @Test + void testInvalidArguments() { + assertThrows(IllegalArgumentException.class, () -> + FileHandler.writeStocksToFile(null, tempDir.toString(), "file.csv")); - List lines = Files.readAllLines(Paths.get(filePathString + fileName)); + assertThrows(IllegalArgumentException.class, () -> + FileHandler.writeStocksToFile(List.of(new Stock("AAPL", "Apple", BigDecimal.ONE)), " ", "file.csv")); - assertTrue(Files.exists(Paths.get(filePathString + fileName)), - "File should have been created successfully."); - assertEquals("#Ticker,Name,Price",lines.getFirst(), "First line " + - "should contain correct headers."); - assertEquals("", lines.get(1), "Second line should be empty."); - assertEquals(symbol1 + "," + company1 + "," + price1, lines.get(2), - "Third line should contain correct values for first stock."); - assertEquals(symbol2 + "," + company2 + "," + price2, lines.getLast(), - "Last line should contain correct values for second stock."); + assertThrows(IllegalArgumentException.class, () -> + FileHandler.writeStocksToFile(List.of(new Stock("AAPL", "Apple", BigDecimal.ONE)), tempDir.toString(), " ")); } @Test - void readTest() { - String filePathString = System.getProperty("user.dir") + "\\target\\test-output\\"; - String fileName = "stockTest.csv"; - - - String symbol1 = "AAPL"; - String company1 = "Apple Inc."; - BigDecimal price1 = new BigDecimal("100"); - - String symbol2 = "NVDA"; - String company2 = "Nvidia"; - BigDecimal price2 = new BigDecimal("191.27"); - - Stock testStock1 = new Stock(symbol1,company1, price1); - Stock testStock2 = new Stock(symbol2, company2, price2); - - List stockList = new ArrayList<>(); - stockList.add(testStock1); - stockList.add(testStock2); - - writeStocksToFile(stockList, filePathString, fileName); - - List readStocks = readStocksFromFile(filePathString, fileName); - - assertEquals(symbol1, readStocks.getFirst().getSymbol(), - "First stock's symbol should be AAPL."); - assertEquals(company1, readStocks.getFirst().getCompany(), - "First stock's company name should be Apple Inc."); - assertEquals(0, price1.compareTo(readStocks.getFirst().getSalesPrice()), - "First stock's price should be 100."); - assertEquals(symbol2, readStocks.getLast().getSymbol(), - "Second stock's symbol should be NVDA."); - assertEquals(company2, readStocks.getLast().getCompany(), - "Second stock's company name should be Nvidia."); - assertEquals(0, price2.compareTo(readStocks.getLast().getSalesPrice()), - "Second stock's price should be 191.27"); + void testReadStocksWithMalformedLines() throws Exception { + String filename = "malformedStocks.csv"; + Path filePath = tempDir.resolve(filename); + + List lines = List.of( + "# This is a comment", + "AAPL,Apple Inc.,150.25", + "INVALIDLINEWITHOUTCOMMAS", + "MSFT,Microsoft Corp.,300.50", + "GOOG,Google,,", + "TSLA,Tesla Inc.,-100" + ); + + Files.write(filePath, lines, StandardCharsets.UTF_8); + + List stocks = FileHandler.readStocksFromFile(tempDir.toString(), filename); + + assertEquals(2, stocks.size(), "Only 2 valid stocks should be read"); + + assertEquals("AAPL", stocks.get(0).getSymbol()); + assertEquals("Apple Inc.", stocks.get(0).getCompany()); + assertEquals(0, stocks.get(0).getSalesPrice().compareTo(new BigDecimal("150.25"))); + + assertEquals("MSFT", stocks.get(1).getSymbol()); + assertEquals("Microsoft Corp.", stocks.get(1).getCompany()); + assertEquals(0, stocks.get(1).getSalesPrice().compareTo(new BigDecimal("300.50"))); } } \ No newline at end of file From 01aa629fa4d120d07e6f6a6fb4fffee445040b9f Mon Sep 17 00:00:00 2001 From: Roar Date: Wed, 15 Apr 2026 22:56:11 +0200 Subject: [PATCH 005/142] Updated FileHandler Fixed logic for handling exceptions thrown by Stock object for illegal parameters. --- millions/src/main/java/no/ntnu/gruppe53/FileHandler.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java b/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java index 3fd4727..b1c616b 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java +++ b/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java @@ -38,7 +38,7 @@ public static void writeStocksToFile(List stockList, String path, String Path filePath = Paths.get(path, filenameWithExtension); Path dirPath = filePath.getParent(); - if (dirPath != null && !Files.exists(dirPath)) { + if (!Files.exists(dirPath)) { try { Files.createDirectories(dirPath); } catch (IOException e) { @@ -77,7 +77,12 @@ public static void writeStocksToFile(List stockList, String path, String */ public static List readStocksFromFile(String path, String filename) { List stocks = new ArrayList<>(); - if (path == null || filename == null) return stocks; + if (path == null || path.isBlank()) { + throw new IllegalArgumentException("Path cannot be null or blank."); + } + if (filename == null || filename.isBlank()) { + throw new IllegalArgumentException("Filename cannot be null or blank."); + } path = path.trim(); filename = filename.trim(); From b572be694d22d34ae66c9a327776f806849c5355 Mon Sep 17 00:00:00 2001 From: Roar Date: Wed, 15 Apr 2026 22:58:54 +0200 Subject: [PATCH 006/142] Updated FileHandlerTest Added tests for everything but IOException. --- .../no/ntnu/gruppe53/FileHandlerTest.java | 149 ++++++++++++++++-- 1 file changed, 139 insertions(+), 10 deletions(-) diff --git a/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java b/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java index a5611b6..8b860b9 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import java.io.File; +import java.io.IOException; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -33,7 +34,7 @@ void cleanup() throws Exception { } @Test - void testWriteAndReadStocks() { + void writingAndReadingStocksFromListShouldYieldIdenticalStockList() { Stock stock1 = new Stock("AAPL", "Apple Inc.", new BigDecimal("150.25")); Stock stock2 = new Stock("MSFT", "Microsoft Corp.", new BigDecimal("300.50")); List stocks = Arrays.asList(stock1, stock2); @@ -55,7 +56,54 @@ void testWriteAndReadStocks() { } @Test - void testWriteWithEmptyList() { + void writerShouldIgnoreNullStockObjects() { + Stock stock1 = new Stock("AAPL", "Apple Inc.", new BigDecimal("150.25")); + Stock stock2 = new Stock("MSFT", "Microsoft Corp.", new BigDecimal("300.50")); + List stocks = Arrays.asList(stock1, null, stock2); + + FileHandler.writeStocksToFile(stocks, tempDir.toString(), "stocks.csv"); + List writtenStocks = FileHandler.readStocksFromFile(tempDir.toString(),"stocks.csv"); + + assertEquals(3, stocks.size(), "Stock list should include null stock."); + assertEquals(stocks.size()-1, writtenStocks.size(), + "Null stock should be ignored by the writer."); + + } + + @Test + void writeFileNameWithoutExtensionShouldAddExtension() { + + List stocks = List.of( + new Stock("AAPL", "Apple Inc.", new BigDecimal("150.00")) + ); + + String filenameWithoutExtension = "stocks"; + + FileHandler.writeStocksToFile(stocks, tempDir.toString(), filenameWithoutExtension); + + Path expectedFile = tempDir.resolve("stocks.csv"); + + assertTrue(Files.exists(expectedFile), "File should end with .csv extension."); + } + + @Test + void writeToNonExistingDirectoriesShouldCreateTheDirectories() { + Path nonExistingDir = tempDir.resolve("subdir1/subdir2"); + + List stocks = List.of( + new Stock("AAPL", "Apple Inc.", new BigDecimal("150.00")) + ); + + FileHandler.writeStocksToFile(stocks, nonExistingDir.toString(), "stocks"); + + Path expectedFile = nonExistingDir.resolve("stocks.csv"); + + assertTrue(Files.exists(expectedFile), "File should be created in newly created directories"); + assertTrue(Files.exists(nonExistingDir), "Directories should have been created"); + } + + @Test + void writeEmptyListShouldReturnEmptyList() { List emptyList = List.of(); String filename = "emptyStocks.csv"; @@ -66,19 +114,29 @@ void testWriteWithEmptyList() { } @Test - void testInvalidArguments() { + void writeInvalidStockListShouldThrowIllegalArgumentException() { assertThrows(IllegalArgumentException.class, () -> FileHandler.writeStocksToFile(null, tempDir.toString(), "file.csv")); + } + @Test + void writeInvalidFolderPathShouldThrowIllegalArgumentException() { assertThrows(IllegalArgumentException.class, () -> FileHandler.writeStocksToFile(List.of(new Stock("AAPL", "Apple", BigDecimal.ONE)), " ", "file.csv")); + assertThrows(IllegalArgumentException.class, () -> + FileHandler.writeStocksToFile(List.of(new Stock("AAPL", "Apple", BigDecimal.ONE)),null, "file.csv")); + } + @Test + void writeInvalidFileNameShouldThrowIllegalArgumentException() { assertThrows(IllegalArgumentException.class, () -> FileHandler.writeStocksToFile(List.of(new Stock("AAPL", "Apple", BigDecimal.ONE)), tempDir.toString(), " ")); + assertThrows(IllegalArgumentException.class, () -> + FileHandler.writeStocksToFile(List.of(new Stock("AAPL", "Apple", BigDecimal.ONE)), tempDir.toString(), null)); } @Test - void testReadStocksWithMalformedLines() throws Exception { + void readStocksShouldIgnoreMalformedLines() throws Exception { String filename = "malformedStocks.csv"; Path filePath = tempDir.resolve(filename); @@ -86,9 +144,7 @@ void testReadStocksWithMalformedLines() throws Exception { "# This is a comment", "AAPL,Apple Inc.,150.25", "INVALIDLINEWITHOUTCOMMAS", - "MSFT,Microsoft Corp.,300.50", - "GOOG,Google,,", - "TSLA,Tesla Inc.,-100" + "MSFT,Microsoft Corp.,300.50" ); Files.write(filePath, lines, StandardCharsets.UTF_8); @@ -97,12 +153,85 @@ void testReadStocksWithMalformedLines() throws Exception { assertEquals(2, stocks.size(), "Only 2 valid stocks should be read"); - assertEquals("AAPL", stocks.get(0).getSymbol()); - assertEquals("Apple Inc.", stocks.get(0).getCompany()); - assertEquals(0, stocks.get(0).getSalesPrice().compareTo(new BigDecimal("150.25"))); + assertEquals("AAPL", stocks.getFirst().getSymbol()); + assertEquals("Apple Inc.", stocks.getFirst().getCompany()); + assertEquals(0, stocks.getFirst().getSalesPrice().compareTo(new BigDecimal("150.25"))); assertEquals("MSFT", stocks.get(1).getSymbol()); assertEquals("Microsoft Corp.", stocks.get(1).getCompany()); assertEquals(0, stocks.get(1).getSalesPrice().compareTo(new BigDecimal("300.50"))); } + + @Test + void readStocksShouldCatchExceptionsFromIllegalStockParameters() throws IOException { + String filename = "malformedStocks.csv"; + Path filePath = tempDir.resolve(filename); + + List lines = List.of( + "GOOG,Google,,", + "GGL, , 200", + " , Google, 100", + "GGL, Google, A", + " ", + "TSLA,Tesla Inc.,-100" + ); + Files.write(filePath, lines, StandardCharsets.UTF_8); + + List stocks = FileHandler.readStocksFromFile(tempDir.toString(), filename); + + assertEquals(0, stocks.size(), "No stocks with illegal parameters should be added."); + } + + @Test + void readStocksShouldThrowIllegalArgumentExceptionIfPathIsNullOrBlank() { + Stock stock1 = new Stock("AAPL", "Apple Inc.", new BigDecimal("150.25")); + Stock stock2 = new Stock("MSFT", "Microsoft Corp.", new BigDecimal("300.50")); + List stocks = Arrays.asList(stock1, stock2); + String filename = "stocks.csv"; + + FileHandler.writeStocksToFile(stocks, tempDir.toString(), filename); + + assertThrows(IllegalArgumentException.class, () -> FileHandler.readStocksFromFile(null, filename), + "Null path should not be allowed."); + assertThrows(IllegalArgumentException.class, () -> FileHandler.readStocksFromFile(" ", filename), + "Blank path should not be allowed."); + } + + @Test + void readStocksShouldThrowIllegalArgumentExceptionIfFileNameIsNullOrBlank() { + Stock stock1 = new Stock("AAPL", "Apple Inc.", new BigDecimal("150.25")); + Stock stock2 = new Stock("MSFT", "Microsoft Corp.", new BigDecimal("300.50")); + List stocks = Arrays.asList(stock1, stock2); + String filename = "stocks.csv"; + + FileHandler.writeStocksToFile(stocks, tempDir.toString(), filename); + + assertThrows(IllegalArgumentException.class, () -> FileHandler.readStocksFromFile( + tempDir.toString(), null), + "Null filename should not be allowed."); + assertThrows(IllegalArgumentException.class, () -> FileHandler.readStocksFromFile( + tempDir.toString(), " "), + "Blank filename should not be allowed."); + } + + @Test + void readShouldAddExtensionIfMissingFromFileName() { + Stock stock1 = new Stock("AAPL", "Apple Inc.", new BigDecimal("150.25")); + Stock stock2 = new Stock("MSFT", "Microsoft Corp.", new BigDecimal("300.50")); + List stocks = Arrays.asList(stock1, stock2); + String filename = "stocks"; + String filenameWithExtension = filename + ".csv"; + + FileHandler.writeStocksToFile(stocks, tempDir.toString(), filenameWithExtension); + List readStocks = FileHandler.readStocksFromFile(tempDir.toString(), filename); + + assertEquals(stocks.size(), readStocks.size(), "All stocks should be read successfully."); + } + + @Test + void readerShouldReturnEmptyListIfFileIsMissing() { + List stocks = FileHandler.readStocksFromFile(tempDir.toString(), "stocklist.csv"); + + assertEquals(0, stocks.size(), "Stock list should be empty."); + } } \ No newline at end of file From 4f181a3bde8b14c2c1a5aabce3b8893a93e82cd5 Mon Sep 17 00:00:00 2001 From: Roar Date: Thu, 16 Apr 2026 00:08:44 +0200 Subject: [PATCH 007/142] Updated pom.xml Added App as the main class. --- millions/pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/millions/pom.xml b/millions/pom.xml index bd65466..9981974 100644 --- a/millions/pom.xml +++ b/millions/pom.xml @@ -46,6 +46,9 @@ org.openjfx javafx-maven-plugin 0.0.8 + + no.ntnu.gruppe53.App + org.apache.maven.plugins From 9a940644df59ff642f92a7f62570f22ba9d926c7 Mon Sep 17 00:00:00 2001 From: Roar Date: Thu, 16 Apr 2026 00:11:39 +0200 Subject: [PATCH 008/142] Added ViewController Class for switching views. --- .../gruppe53/controller/ViewController.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java new file mode 100644 index 0000000..c71dd4c --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java @@ -0,0 +1,60 @@ +package no.ntnu.gruppe53.controller; + +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +import java.util.HashMap; +import java.util.Map; + +/** + * Manages application views and handles switching between them. + *

+ * Stores a mapping of view names to JavaFX {@link Parent} nodes and updates + * the current Scene by replacing its root node when switching views. + */ +public class ViewController { + /** Primary application window used to display the Scene. */ + private Stage stage; + /** Scene containing the currently active view. */ + private Scene scene; + /** Map of view identifiers to their corresponding UI roots. */ + private Map views = new HashMap<>(); + + /** + * Constructs a ViewController and initializes a new Scene. + * + * @param stage the primary application stage where the scene will be set + */ + public ViewController(Stage stage) { + this.stage = stage; + this.scene = new Scene(new javafx.scene.layout.StackPane(), 800, 600); + stage.setScene(scene); + } + + /** + * Registers a view in the controller. + * + * @param name unique identifier used to reference the view later + * @param view the JavaFX root node representing the view + */ + public void addView(String name, Parent view) { + views.put(name, view); + } + + /** + * Switches the currently displayed view by replacing the Scene root. + * + * @param name the identifier of the view to display + * @throws IllegalArgumentException if no view is registered under the given name + */ + public void switchView(String name) { + Parent view = views.get(name); + + if (view == null) { + throw new IllegalArgumentException("No views found matching: " + name); + } + + scene.setRoot(view); + } +} From 7169f15b41c3dde5c82ae6c727d035f5a295ed35 Mon Sep 17 00:00:00 2001 From: Roar Date: Thu, 16 Apr 2026 00:12:39 +0200 Subject: [PATCH 009/142] Updated ViewController Fixed javadoc typos. --- .../main/java/no/ntnu/gruppe53/controller/ViewController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java index c71dd4c..f33de22 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java @@ -12,6 +12,7 @@ *

* Stores a mapping of view names to JavaFX {@link Parent} nodes and updates * the current Scene by replacing its root node when switching views. + *

*/ public class ViewController { /** Primary application window used to display the Scene. */ From 15b7d8641cbf68f967a7938c08b6d5e501993292 Mon Sep 17 00:00:00 2001 From: Roar Date: Thu, 16 Apr 2026 00:14:14 +0200 Subject: [PATCH 010/142] Added StartView A currently dummy class to make the app launch with a default view. --- .../main/java/no/ntnu/gruppe53/views/StartView.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/views/StartView.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java b/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java new file mode 100644 index 0000000..a1215c6 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java @@ -0,0 +1,11 @@ +package no.ntnu.gruppe53.views; + +import javafx.scene.layout.VBox; +import no.ntnu.gruppe53.controller.ViewController; + +public class StartView extends VBox { + + public StartView(ViewController vm) { + + } +} \ No newline at end of file From 3b4cec7e92b81e5748cb7cf2c0fcc5623da54102 Mon Sep 17 00:00:00 2001 From: Roar Date: Thu, 16 Apr 2026 00:15:54 +0200 Subject: [PATCH 011/142] Updated App Added commands to make javafx work. --- .../src/main/java/no/ntnu/gruppe53/App.java | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/App.java b/millions/src/main/java/no/ntnu/gruppe53/App.java index 4205122..a446d34 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/App.java +++ b/millions/src/main/java/no/ntnu/gruppe53/App.java @@ -1,10 +1,25 @@ package no.ntnu.gruppe53; -/** - * Hello world! - */ -public class App { +import javafx.application.Application; +import javafx.stage.Stage; +import no.ntnu.gruppe53.controller.ViewController; +import no.ntnu.gruppe53.views.StartView; + + +public class App extends Application { + @Override + public void start(Stage stage) { + ViewController vm = new ViewController(stage); + + vm.addView("start", new StartView(vm)); + + vm.switchView("start"); + + stage.setTitle("Millions - the Stock Game"); + stage.show(); + } + public static void main(String[] args) { - System.out.println("Hello World!"); + launch(); } } From 80688f927daf85cc728bd74dd2ca672baa47a636 Mon Sep 17 00:00:00 2001 From: Roar Date: Thu, 16 Apr 2026 14:11:55 +0200 Subject: [PATCH 012/142] Updated StartView Converted the dummy view to the first user view. --- .../no/ntnu/gruppe53/views/StartView.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java b/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java index a1215c6..efbebf3 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java @@ -1,11 +1,50 @@ package no.ntnu.gruppe53.views; +import javafx.geometry.Pos; +import javafx.scene.control.Button; import javafx.scene.layout.VBox; +import no.ntnu.gruppe53.controller.StartViewController; import no.ntnu.gruppe53.controller.ViewController; +/** + * Represents the first page seen by the user of the application. + */ public class StartView extends VBox { + private final Button newGameButton; + private final Button quitButton; + /** + * Constructs the view and sets parameters to be exposed for the {@link StartViewController}. + * + * @param vm the {@link ViewController} for the view + */ public StartView(ViewController vm) { + this.setAlignment(Pos.CENTER); + this.setSpacing(20); + newGameButton = new Button("New Game"); + quitButton = new Button("Quit"); + + newGameButton.setPrefWidth(200); + quitButton.setPrefWidth(200); + + this.getChildren().addAll(newGameButton, quitButton); + } + + /** + * Sets the method to be run when + * + * @param action + */ + public void setOnNewGame(Runnable action) { + newGameButton.setOnAction(e -> action.run()); + } + + /** + * + * @param action + */ + public void setOnQuit(Runnable action) { + quitButton.setOnAction(e -> action.run()); } } \ No newline at end of file From 4544ad39cb27d31049914d33a7fba66ab255fb19 Mon Sep 17 00:00:00 2001 From: Roar Date: Thu, 16 Apr 2026 14:13:18 +0200 Subject: [PATCH 013/142] Added StartViewController Controller class for StartView --- .../controller/StartViewController.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java new file mode 100644 index 0000000..48bf965 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java @@ -0,0 +1,19 @@ +package no.ntnu.gruppe53.controller; + +import no.ntnu.gruppe53.views.StartView; +import javafx.application.Platform; + +/** + * aaaa + */ +public class StartViewController { + + public StartViewController(StartView view, ViewController vm) { + + view.setOnNewGame(() -> { + System.out.println("Start new game"); + }); + + view.setOnQuit(Platform::exit); + } +} \ No newline at end of file From 3126d0c301a0bc2f7a6d89469ac75de0594b4250 Mon Sep 17 00:00:00 2001 From: Roar Date: Thu, 16 Apr 2026 14:15:03 +0200 Subject: [PATCH 014/142] Updated App Added StartView to the ViewController --- millions/src/main/java/no/ntnu/gruppe53/App.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/App.java b/millions/src/main/java/no/ntnu/gruppe53/App.java index a446d34..851d613 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/App.java +++ b/millions/src/main/java/no/ntnu/gruppe53/App.java @@ -2,6 +2,7 @@ import javafx.application.Application; import javafx.stage.Stage; +import no.ntnu.gruppe53.controller.StartViewController; import no.ntnu.gruppe53.controller.ViewController; import no.ntnu.gruppe53.views.StartView; @@ -11,8 +12,10 @@ public class App extends Application { public void start(Stage stage) { ViewController vm = new ViewController(stage); - vm.addView("start", new StartView(vm)); + StartView startView = new StartView(vm); + new StartViewController(startView, vm); + vm.addView("start", startView); vm.switchView("start"); stage.setTitle("Millions - the Stock Game"); From 5911e9b0ae910084204723e83891959eba6c7ba4 Mon Sep 17 00:00:00 2001 From: Are Lodgaard Date: Fri, 24 Apr 2026 15:10:33 +0200 Subject: [PATCH 015/142] Added a Sale View and Portfolio view with gamecontroller --- millions/pom.xml | 2 +- .../src/main/java/no/ntnu/gruppe53/App.java | 3 + .../java/no/ntnu/gruppe53/FileHandler.java | 1 + .../gruppe53/controller/GameController.java | 75 +++ .../controller/StartViewController.java | 4 +- .../no/ntnu/gruppe53/views/PortfolioView.java | 77 +++ .../no/ntnu/gruppe53/views/PurchaseView.java | 134 +++++ millions/src/main/resources/sp500.csv | 506 ++++++++++++++++++ .../compile/default-compile/createdFiles.lst | 20 +- .../compile/default-compile/inputFiles.lst | 33 +- 10 files changed, 827 insertions(+), 28 deletions(-) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java create mode 100644 millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java create mode 100644 millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java create mode 100644 millions/src/main/resources/sp500.csv diff --git a/millions/pom.xml b/millions/pom.xml index 9981974..fed2ae7 100644 --- a/millions/pom.xml +++ b/millions/pom.xml @@ -26,7 +26,7 @@ org.openjfx javafx-controls - 25.0.2 + 25.0.3 diff --git a/millions/src/main/java/no/ntnu/gruppe53/App.java b/millions/src/main/java/no/ntnu/gruppe53/App.java index 851d613..f3ec24a 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/App.java +++ b/millions/src/main/java/no/ntnu/gruppe53/App.java @@ -13,9 +13,12 @@ public void start(Stage stage) { ViewController vm = new ViewController(stage); StartView startView = new StartView(vm); + new StartViewController(startView, vm); + vm.addView("start", startView); + vm.switchView("start"); stage.setTitle("Millions - the Stock Game"); diff --git a/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java b/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java index b1c616b..13e26a6 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java +++ b/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java @@ -16,6 +16,7 @@ */ public class FileHandler { + /** * Writes a list of {@link Stock}s to a CSV file in UTF-8 encoding. * diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java new file mode 100644 index 0000000..310578a --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -0,0 +1,75 @@ +package no.ntnu.gruppe53.controller; + +import java.math.BigDecimal; +import java.util.List; + +import no.ntnu.gruppe53.Exchange; +import no.ntnu.gruppe53.FileHandler; +import no.ntnu.gruppe53.Player; +import no.ntnu.gruppe53.Stock; +import no.ntnu.gruppe53.views.PortfolioView; +import no.ntnu.gruppe53.views.PurchaseView; + +public class GameController { + private final ViewController vm; + private final FileHandler fileHandler = new FileHandler(); + + private Player player; + private Exchange exchange; + private List stocks; + private PurchaseView purchaseView; + private PortfolioView portfolioView; + + public GameController(ViewController vm) { + this.vm = vm; + } + + public void startNewGame() { + player = new Player("Player 1", new BigDecimal("100000")); + stocks = fileHandler.readStocksFromFile("src/main/resources", "sp500.csv"); + exchange = new Exchange("Millions Exchange", stocks); + + purchaseView = new PurchaseView(); + purchaseView.setPlayer(player); + purchaseView.setExchange(exchange); + purchaseView.setPurchaseHandler(this::purchaseStock); + + portfolioView = new PortfolioView(); + portfolioView.setPlayer(player); + + vm.addView("purchase", purchaseView); + vm.addView("portfolio", portfolioView); + + vm.switchView("purchase"); + } + + private void purchaseStock(String symbol, int quantity) { + if (exchange == null || player == null || symbol == null || quantity <= 0) { + return; + } + + try { + exchange.buy(symbol, BigDecimal.valueOf(quantity), player); + purchaseView.setPlayer(player); + purchaseView.refreshStocks(); + portfolioView.setPlayer(player); + } catch (RuntimeException ex) { + System.out.println("Purchase failed: " + ex.getMessage()); + } + } + + public void showPortfolio() { + if (portfolioView != null) { + portfolioView.setPlayer(player); + vm.switchView("portfolio"); + } + } + + public void showPurchaseView() { + if (purchaseView != null) { + purchaseView.setPlayer(player); + purchaseView.refreshStocks(); + vm.switchView("purchase"); + } + } +} \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java index 48bf965..acf3d43 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java @@ -11,7 +11,9 @@ public class StartViewController { public StartViewController(StartView view, ViewController vm) { view.setOnNewGame(() -> { - System.out.println("Start new game"); + GameController gameController = new GameController(vm); + gameController.startNewGame(); + }); view.setOnQuit(Platform::exit); diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java new file mode 100644 index 0000000..776cea2 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -0,0 +1,77 @@ +package no.ntnu.gruppe53.views; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; +import java.util.stream.Collectors; + +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.layout.VBox; +import no.ntnu.gruppe53.Player; +import no.ntnu.gruppe53.Share; + +public class PortfolioView extends VBox { + private final Label titleLabel; + private final Label playerNameLabel; + private final Label netWorthLabel; + private final ListView portfolioListView; + + private Player player; + + public PortfolioView() { + setSpacing(12); + setPadding(new Insets(16)); + + titleLabel = new Label("Portfolio"); + titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); + + playerNameLabel = new Label("Player: -"); + netWorthLabel = new Label("Net worth: $0.00"); + + portfolioListView = new ListView<>(); + portfolioListView.setPrefHeight(300); + + getChildren().addAll(titleLabel, playerNameLabel, netWorthLabel, portfolioListView); + } + + public void setPlayer(Player player) { + this.player = player; + refresh(); + } + + public void refresh() { + if (player == null) { + playerNameLabel.setText("Player: -"); + netWorthLabel.setText("Net worth: $0.00"); + portfolioListView.getItems().clear(); + return; + } + + playerNameLabel.setText("Player: " + player.getName()); + netWorthLabel.setText("Net worth: $" + formatMoney(player.getNetWorth())); + + List shares = player.getPortfolio().getShares(); + + portfolioListView.getItems().setAll( + shares.stream() + .map(this::formatShare) + .collect(Collectors.toList()) + ); + } + + private String formatShare(Share share) { + return share.getStock().getSymbol() + + " - " + + share.getStock().getCompany() + + " | Qty: " + + share.getQuantity() + + " | Buy price: $" + + formatMoney(share.getPurchasePrice()); + } + + private String formatMoney(BigDecimal money) { + return money == null ? "0.00" : money.setScale(2, RoundingMode.HALF_UP).toPlainString(); + } +} \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java new file mode 100644 index 0000000..e1e47a2 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java @@ -0,0 +1,134 @@ +package no.ntnu.gruppe53.views; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.control.Spinner; +import javafx.scene.control.SpinnerValueFactory; +import javafx.scene.layout.VBox; +import no.ntnu.gruppe53.Exchange; +import no.ntnu.gruppe53.Player; +import no.ntnu.gruppe53.Stock; + +public class PurchaseView extends VBox { + private final Label playerMoneyLabel; + private final ListView stocksListView; + private final Spinner quantitySpinner; + private final Button purchaseButton; + + private Exchange exchange; + private Player player; + private BiConsumer purchaseHandler; + + public PurchaseView() { + setSpacing(12); + setPadding(new Insets(16)); + + Label titleLabel = new Label("Stock Market"); + titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); + + playerMoneyLabel = new Label("Money: $0.00"); + Label stocksTitleLabel = new Label("Available stocks:"); + + stocksListView = new ListView<>(); + stocksListView.setPrefHeight(300); + + Label quantityLabel = new Label("Quantity:"); + quantitySpinner = new Spinner<>(); + quantitySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1_000_000, 1)); + quantitySpinner.setEditable(true); + + purchaseButton = new Button("Purchase"); + purchaseButton.setOnAction(event -> { + if (purchaseHandler == null) { + return; + } + + String selectedStock = getSelectedSymbol(); + if (selectedStock == null) { + return; + } + + int quantity = quantitySpinner.getValue(); + purchaseHandler.accept(selectedStock, quantity); + }); + + getChildren().addAll( + titleLabel, + playerMoneyLabel, + stocksTitleLabel, + stocksListView, + quantityLabel, + quantitySpinner, + purchaseButton + ); + } + + public void setPurchaseHandler(BiConsumer purchaseHandler) { + this.purchaseHandler = purchaseHandler; + } + + public void setPlayer(Player player) { + this.player = player; + + if (player == null) { + playerMoneyLabel.setText("Money: $0.00"); + return; + } + + playerMoneyLabel.setText("Money: $" + formatMoney(player.getMoney())); + } + + public void setExchange(Exchange exchange) { + this.exchange = exchange; + refreshStocks(); + } + + public void refreshPlayer() { + setPlayer(player); + } + + public void refreshStocks() { + if (exchange == null) { + stocksListView.getItems().clear(); + System.out.println("Exchange is null"); + return; + } + + List stocks = exchange.findStocks(""); + stocksListView.getItems().setAll( + stocks.stream() + .map(this::formatStock) + .collect(Collectors.toList()) + ); + } + + private String getSelectedSymbol() { + String selectedItem = stocksListView.getSelectionModel().getSelectedItem(); + if (selectedItem == null || selectedItem.isBlank()) { + return null; + } + + int separatorIndex = selectedItem.indexOf(" - "); + return separatorIndex > 0 ? selectedItem.substring(0, separatorIndex) : null; + } + + private String formatStock(Stock stock) { + return stock.getSymbol() + + " - " + + stock.getCompany() + + " | Price: $" + + formatMoney(stock.getSalesPrice()); + } + + private String formatMoney(BigDecimal money) { + return money == null ? "0.00" : money.setScale(2, RoundingMode.HALF_UP).toPlainString(); + } +} \ No newline at end of file diff --git a/millions/src/main/resources/sp500.csv b/millions/src/main/resources/sp500.csv new file mode 100644 index 0000000..d9cec61 --- /dev/null +++ b/millions/src/main/resources/sp500.csv @@ -0,0 +1,506 @@ +# S&P 500 Companies by Market Cap +# Ticker,Name,Price + +NVDA,Nvidia,191.27 +AAPL,Apple Inc.,276.43 +MSFT,Microsoft,404.68 +AMZN,Amazon,204.62 +GOOGL,Alphabet Inc. (Class A),311.20 +GOOG,Alphabet Inc. (Class C),311.62 +META,Meta Platforms,669.41 +AVGO,Broadcom,343.35 +TSLA,Tesla Inc.,426.52 +BRK.B,Berkshire Hathaway,501.05 +WMT,Walmart,128.75 +LLY,Lilly (Eli),1014.43 +JPM,JPMorgan Chase,311.14 +XOM,ExxonMobil,155.28 +V,Visa Inc.,329.54 +JNJ,Johnson & Johnson,240.70 +MA,Mastercard,539.52 +MU,Micron Technology,411.25 +ORCL,Oracle Corporation,157.08 +COST,Costco,979.71 +BAC,Bank of America,54.06 +ABBV,AbbVie,220.17 +HD,Home Depot (The),389.46 +PG,Procter & Gamble,159.45 +CVX,Chevron Corporation,185.66 +CAT,Caterpillar Inc.,773.53 +AMD,Advanced Micro Devices,213.00 +CSCO,Cisco,85.82 +KO,Coca-Cola Company (The),78.51 +NFLX,Netflix,79.94 +GE,GE Aerospace,314.37 +PLTR,Palantir Technologies,135.59 +LRCX,Lam Research,236.60 +MRK,Merck & Co.,118.79 +PM,Philip Morris International,185.99 +GS,Goldman Sachs,949.29 +MS,Morgan Stanley,176.86 +WFC,Wells Fargo,89.07 +AMAT,Applied Materials,342.19 +RTX,RTX Corporation,197.56 +IBM,IBM,273.86 +UNH,UnitedHealth Group,278.79 +AXP,American Express,355.18 +INTC,Intel,47.85 +TMUS,T-Mobile US,207.62 +PEP,PepsiCo,168.71 +MCD,McDonald's,323.50 +GEV,GE Vernova,822.50 +LIN,Linde plc,466.04 +C,Citigroup,117.93 +TXN,Texas Instruments,226.34 +VZ,Verizon,48.55 +T,AT&T,28.21 +TMO,Thermo Fisher Scientific,525.00 +AMGN,Amgen,364.77 +ABT,Abbott Laboratories,112.97 +KLAC,KLA Corporation,1492.27 +GILD,Gilead Sciences,155.71 +DIS,Walt Disney Company (The),108.30 +NEE,NextEra Energy,91.19 +BA,Boeing,236.98 +ANET,Arista Networks,141.06 +APH,Amphenol,144.60 +ISRG,Intuitive Surgical,496.14 +CRM,Salesforce,184.94 +SCHW,Charles Schwab Corporation,95.50 +BLK,BlackRock,1083.35 +TJX,TJX Companies,150.56 +DE,Deere & Company,610.03 +ADI,Analog Devices,336.71 +LOW,Lowe's,286.57 +PFE,Pfizer,27.77 +UNP,Union Pacific Corporation,261.95 +DHR,Danaher Corporation,219.80 +APP,AppLovin Corporation,459.27 +HON,Honeywell,243.56 +ETN,Eaton Corporation,394.94 +QCOM,Qualcomm,141.90 +UBER,Uber,70.72 +LMT,Lockheed Martin,630.54 +WELL,Welltower,208.65 +ACN,Accenture,230.79 +BKNG,Booking Holdings,4322.85 +SYK,Stryker Corporation,362.53 +COP,ConocoPhillips,110.75 +NEM,Newmont,123.89 +COF,Capital One,214.93 +PLD,Prologis,140.35 +MDT,Medtronic,100.87 +CB,Chubb Limited,328.82 +PH,Parker Hannifin,998.24 +PGR,Progressive Corporation,209.33 +BMY,Bristol Myers Squibb,60.18 +HCA,HCA Healthcare,531.83 +SPGI,S&P Global,396.65 +CMCSA,Comcast,32.53 +VRTX,Vertex Pharmaceuticals,459.93 +MCK,McKesson Corporation,944.20 +PANW,Palo Alto Networks,165.49 +GLW,Corning Inc.,134.16 +SBUX,Starbucks,99.03 +INTU,Intuit,401.05 +MO,Altria,65.72 +BSX,Boston Scientific,73.76 +CME,CME Group,303.44 +NOW,ServiceNow,101.55 +ADBE,Adobe Inc.,258.39 +TT,Trane Technologies,473.75 +CRWD,CrowdStrike,414.94 +BX,Blackstone Inc.,133.29 +UPS,United Parcel Service,119.93 +SO,Southern Company,90.86 +CEG,Constellation Energy,274.37 +DUK,Duke Energy,124.86 +CVS,CVS Health,76.36 +MAR,Marriott International,360.71 +NOC,Northrop Grumman,680.45 +PNC,PNC Financial Services,237.28 +WM,Waste Management,234.67 +GD,General Dynamics,348.79 +WDC,Western Digital,277.26 +KKR,KKR,105.28 +HWM,Howmet Aerospace,233.13 +FCX,Freeport-McMoRan,65.30 +NKE,Nike Inc.,62.43 +USB,U.S. Bancorp,59.25 +MMM,3M,173.46 +SHW,Sherwin-Williams,364.16 +RCL,Royal Caribbean Group,331.76 +SNDK,Sandisk Corporation,607.86 +STX,Seagate Technology,409.41 +EMR,Emerson Electric,156.30 +ADP,Automatic Data Processing,217.59 +WMB,Williams Companies,71.48 +ICE,Intercontinental Exchange,153.60 +FDX,FedEx,368.49 +ITW,Illinois Tool Works,298.50 +JCI,Johnson Controls,140.75 +CRH,CRH plc,127.89 +ECL,Ecolab,301.35 +EQIX,Equinix,863.66 +BK,BNY Mellon,122.83 +MRSH,Marsh & McLennan Companies Inc.,174.09 +AMT,American Tower,179.46 +CMI,Cummins,601.45 +SNPS,Synopsys,433.56 +REGN,Regeneron Pharmaceuticals,780.09 +DELL,Dell Technologies,124.37 +CDNS,Cadence Design Systems,298.74 +CTAS,Cintas,201.10 +ORLY,O'Reilly Auto Parts,93.87 +MNST,Monster Beverage,80.88 +MDLZ,Mondelez International,61.45 +PWR,Quanta Services,523.69 +CI,Cigna,292.46 +CSX,CSX Corporation,41.30 +CL,Colgate-Palmolive,95.04 +SLB,Schlumberger,51.14 +HLT,Hilton Worldwide,327.38 +DASH,DoorDash,175.41 +TDG,TransDigm Group,1325.26 +MCO,Moody's Corporation,415.20 +APO,Apollo Global Management,127.53 +ELV,Elevance Health,329.59 +ABNB,Airbnb,119.56 +GM,General Motors,79.78 +NSC,Norfolk Southern Railway,316.73 +COR,Cencora,365.43 +MSI,Motorola Solutions,423.10 +KMI,Kinder Morgan,31.64 +RSG,Republic Services,226.72 +HOOD,Robinhood Markets Inc.,77.55 +WBD,Warner Bros. Discovery,28.01 +TFC,Truist Financial,54.42 +PCAR,Paccar,129.93 +AON,Aon,314.02 +TEL,TE Connectivity,227.16 +APD,Air Products,293.38 +AEP,American Electric Power,122.18 +FTNT,Fortinet,87.72 +TRV,Travelers Companies (The),299.75 +PSX,Phillips 66,161.13 +LHX,L3Harris,341.14 +EOG,EOG Resources,117.39 +SPG,Simon Property Group,195.66 +NXPI,NXP Semiconductors,249.26 +ROST,Ross Stores,192.31 +VLO,Valero Energy,203.89 +AZO,AutoZone,3733.09 +MPC,Marathon Petroleum,207.85 +BKR,Baker Hughes,61.16 +AFL,Aflac,116.20 +DLR,Digital Realty,174.16 +SRE,Sempra,90.83 +O,Realty Income,64.39 +MPWR,Monolithic Power Systems,1197.55 +GWW,W. W. Grainger,1202.13 +ZTS,Zoetis,128.19 +CARR,Carrier Global,66.80 +D,Dominion Energy,64.61 +F,Ford Motor Company,13.78 +URI,United Rentals,870.17 +AME,Ametek,236.33 +VST,Vistra Corp.,160.43 +FAST,Fastenal,47.14 +ALL,Allstate,205.91 +OKE,ONEOK,85.03 +AJG,Arthur J. Gallagher & Co.,207.61 +CAH,Cardinal Health,225.15 +CVNA,Carvana Co.,365.94 +IDXX,Idexx Laboratories,647.63 +MET,MetLife,78.87 +TGT,Target Corporation,114.12 +PSA,Public Storage,293.33 +BDX,Becton Dickinson,179.62 +CTVA,Corteva,75.48 +TER,Teradyne,323.92 +EA,Electronic Arts,201.72 +ADSK,Autodesk,232.93 +FITB,Fifth Third Bancorp,54.59 +CMG,Chipotle Mexican Grill,37.35 +FANG,Diamondback Energy,168.93 +TRGP,Targa Resources,222.03 +FIX,Comfort Systems USA Inc.,1345.62 +DHI,D. R. Horton,163.35 +HSY,Hershey Company (The),231.46 +OXY,Occidental Petroleum,47.34 +DAL,Delta Air Lines,71.16 +ROK,Rockwell Automation,413.43 +NDAQ,Nasdaq Inc.,81.05 +XEL,Xcel Energy,77.79 +EW,Edwards Lifesciences,78.67 +CCL,Carnival,32.80 +CBRE,CBRE Group,151.76 +ETR,Entergy,100.79 +EXC,Exelon,44.57 +AMP,Ameriprise Financial,489.27 +NUE,Nucor,194.78 +DDOG,Datadog,126.70 +YUM,Yum! Brands,160.35 +MCHP,Microchip Technology,80.90 +WAB,Wabtec,255.15 +KR,Kroger,68.68 +AIG,American International Group,79.36 +VMC,Vulcan Materials Company,321.01 +CIEN,Ciena Corporation,301.11 +SYY,Sysco,88.04 +PEG,Public Service Enterprise Group,83.85 +COIN,Coinbase Global,152.61 +ODFL,Old Dominion,195.74 +KEYS,Keysight Technologies,237.88 +KDP,Keurig Dr Pepper,29.84 +VTR,Ventas,85.29 +MLM,Martin Marietta Materials,663.38 +GRMN,Garmin,206.52 +ED,Consolidated Edison,109.36 +HIG,Hartford (The),142.02 +LVS,Las Vegas Sands,57.75 +CPRT,Copart,39.73 +EL,Estée Lauder Companies (The),106.09 +IR,Ingersoll Rand,96.98 +WDAY,Workday Inc.,145.19 +MSCI,MSCI,519.16 +TTWO,Take-Two Interactive,204.25 +RMD,ResMed,259.29 +EBAY,eBay,82.95 +PCG,PG&E Corporation,17.02 +CCI,Crown Castle,85.66 +PYPL,PayPal,40.26 +PRU,Prudential Financial,105.34 +WEC,WEC Energy Group,113.19 +UAL,United Airlines Holdings,113.50 +STT,State Street Corporation,131.37 +HBAN,Huntington Bancshares,18.02 +A,Agilent Technologies,128.22 +GEHC,GE HealthCare,79.29 +MTB,M&T Bank,235.06 +EME,EMCOR Group Inc.,803.53 +ACGL,Arch Capital Group,98.49 +KMB,Kimberly-Clark,107.35 +ROP,Roper Technologies,333.96 +EQT,EQT Corporation,56.73 +KVUE,Kenvue,18.44 +LYV,Live Nation Entertainment,150.60 +OTIS,Otis Worldwide,89.85 +AXON,Axon Enterprise,436.36 +NRG,NRG Energy,160.11 +CTSH,Cognizant,71.22 +IBKR,Interactive Brokers Group,76.54 +PAYX,Paychex,94.49 +FISV,Fiserv Inc.,62.72 +ADM,Archer Daniels Midland,69.24 +XYZ,Block Inc.,53.87 +FICO,Fair Isaac,1369.86 +DG,Dollar General,147.49 +DOV,Dover Corporation,232.52 +ROL,Rollins Inc.,65.74 +HPE,Hewlett Packard Enterprise,23.62 +RJF,Raymond James Financial,159.60 +TPR,Tapestry Inc.,154.43 +VICI,Vici Properties,29.18 +TDY,Teledyne Technologies,657.92 +XYL,Xylem Inc.,126.71 +CHTR,Charter Communications,241.08 +ARES,Ares Management Corporation,137.90 +ULTA,Ulta Beauty,684.25 +STLD,Steel Dynamics,206.43 +EXR,Extra Space Storage,142.02 +LEN,Lennar,120.76 +IQV,IQVIA,175.80 +IRM,Iron Mountain,99.67 +KHC,Kraft Heinz,24.88 +PPG,PPG Industries,130.53 +HAL,Halliburton,34.84 +ATO,Atmos Energy,175.22 +DTE,DTE Energy,139.17 +TSCO,Tractor Supply,54.53 +EXPE,Expedia Group,235.08 +CFG,Citizens Financial Group,66.87 +AEE,Ameren,106.08 +TPL,Texas Pacific Land Corporation,416.14 +CBOE,Cboe Global Markets,271.99 +ON,ON Semiconductor,70.68 +MTD,Mettler Toledo,1391.33 +STZ,Constellation Brands,163.02 +BIIB,Biogen,191.29 +DVN,Devon Energy,44.78 +FE,FirstEnergy,47.94 +JBL,Jabil,260.92 +NTRS,Northern Trust,147.45 +HUBB,Hubbell Incorporated,513.63 +WTW,Willis Towers Watson,282.86 +WRB,W. R. Berkley Corporation,71.20 +RF,Regions Financial Corporation,30.86 +PHM,PulteGroup,139.04 +CNP,CenterPoint Energy,40.91 +PPL,PPL Corporation,35.97 +DXCM,Dexcom,68.19 +SW,Smurfit WestRock,50.23 +ES,Eversource Energy,69.77 +GIS,General Mills,48.40 +EIX,Edison International,66.94 +IP,International Paper,48.64 +WSM,Williams-Sonoma,214.52 +CINF,Cincinnati Financial,164.11 +LUV,Southwest Airlines,51.66 +AVB,AvalonBay Communities,179.80 +SYF,Synchrony Financial,72.95 +FIS,Fidelity National Information Services,48.73 +KEY,KeyCorp,22.67 +DLTR,Dollar Tree,125.35 +EQR,Equity Residential,65.09 +EXE,Expand Energy,103.09 +DRI,Darden Restaurants,212.58 +FSLR,First Solar,227.60 +DOW,Dow Inc.,33.98 +CPAY,Corpay,346.25 +AWK,American Water Works,123.54 +CHD,Church & Dwight,100.17 +LH,LabCorp,289.08 +VRSK,Verisk Analytics,171.87 +Q,Qnity Electronics,114.06 +CTRA,Coterra,31.48 +STE,Steris,243.09 +EFX,Equifax,197.29 +VLTO,Veralto,95.33 +BG,Bunge Global,121.39 +DGX,Quest Diagnostics,209.53 +CHRW,C.H. Robinson,196.77 +AMCR,Amcor,49.62 +TSN,Tyson Foods,64.52 +L,Loews Corporation,110.15 +CMS,CMS Energy,74.16 +BRO,Brown & Brown,67.07 +LDOS,Leidos,174.83 +PKG,Packaging Corporation of America,244.06 +JBHT,J.B. Hunt,231.62 +OMC,Omnicom Group,69.53 +EXPD,Expeditors International,163.06 +RL,Ralph Lauren Corporation,359.55 +NVR,NVR Inc.,8082.38 +DD,DuPont,51.29 +HUM,Humana,176.34 +NI,NiSource,44.78 +NTAP,NetApp,105.69 +GPC,Genuine Parts Company,149.43 +LULU,Lululemon Athletica,176.88 +ALB,Albemarle Corporation,176.03 +TROW,T. Rowe Price,94.24 +PFG,Principal Financial Group,92.86 +CSGP,CoStar Group,48.09 +GPN,Global Payments,72.67 +SBAC,SBA Communications,190.43 +SNA,Snap-on,383.21 +CNC,Centene Corporation,40.26 +VRSN,Verisign,215.66 +WAT,Waters Corporation,331.52 +IFF,International Flavors & Fragrances,76.65 +BR,Broadridge Financial Solutions,167.88 +WY,Weyerhaeuser,27.09 +INCY,Incyte,99.35 +LII,Lennox International,553.57 +LYB,LyondellBasell,59.29 +SMCI,Supermicro,31.69 +MKC,McCormick & Company,70.30 +ZBH,Zimmer Biomet,95.21 +PTC,PTC Inc.,155.52 +FTV,Fortive,58.93 +VTRS,Viatris,16.02 +EVRG,Evergy,78.94 +BALL,Ball Corporation,67.29 +HPQ,HP Inc.,19.63 +WST,West Pharmaceutical Services,247.87 +PODD,Insulet Corporation,253.09 +APTV,Aptiv,83.64 +CDW,CDW,135.32 +LNT,Alliant Energy,68.17 +TXT,Textron,96.68 +ESS,Essex Property Trust,262.76 +HOLX,Hologic,75.11 +J,Jacobs Solutions,142.70 +INVH,Invitation Homes,27.16 +TKO,TKO Group Holdings,210.88 +NDSN,Nordson Corporation,295.64 +DECK,Deckers Brands,115.15 +PNR,Pentair,99.79 +COO,Cooper Companies (The),82.84 +MAA,Mid-America Apartment Communities,136.74 +FFIV,F5 Inc.,282.22 +MAS,Masco,76.24 +IEX,IDEX Corporation,211.28 +MRNA,Moderna,40.21 +TRMB,Trimble Inc.,65.06 +ALLE,Allegion,179.40 +HII,Huntington Ingalls Industries,392.15 +CLX,Clorox,125.65 +CF,CF Industries,97.23 +GEN,Gen Digital,24.64 +AVY,Avery Dennison,192.77 +KIM,Kimco Realty,21.96 +HAS,Hasbro,105.26 +ERIE,Erie Indemnity,279.84 +TYL,Tyler Technologies,339.51 +UHS,Universal Health Services,231.22 +BEN,Franklin Resources,27.64 +ALGN,Align Technology,197.24 +SOLV,Solventum,81.25 +BBY,Best Buy,66.84 +REG,Regency Centers,76.49 +SWK,Stanley Black & Decker,90.31 +BF.B,Brown–Forman,30.11 +BLDR,Builders FirstSource,125.44 +HST,Host Hotels & Resorts,19.98 +AKAM,Akamai Technologies,95.00 +EG,Everest Group,331.67 +UDR,UDR Inc.,39.84 +TTD,The Trade Desk Inc.,27.19 +HRL,Hormel Foods,23.79 +DPZ,Domino's,385.50 +ZBRA,Zebra Technologies,250.71 +GNRC,Generac,214.94 +FOX,Fox Corporation (Class B),56.02 +FOXA,Fox Corporation (Class A),61.76 +GDDY,GoDaddy,91.44 +PSKY,Paramount Skydance Corp,10.96 +WYNN,Wynn Resorts,115.18 +JKHY,Jack Henry & Associates,165.63 +CPT,Camden Property Trust,111.66 +DOC,Healthpeak Properties,16.95 +SJM,J.M. Smucker Company (The),109.98 +IVZ,Invesco,26.42 +AES,AES Corporation,16.45 +IT,Gartner,160.21 +GL,Globe Life,144.35 +BAX,Baxter International,22.30 +PNW,Pinnacle West,95.57 +RVTY,Revvity,100.89 +AOS,A. O. Smith,80.03 +AIZ,Assurant,216.77 +TAP,Molson Coors Beverage Company,52.98 +NCLH,Norwegian Cruise Line Holdings,22.76 +POOL,Pool Corporation,270.70 +EPAM,EPAM Systems,180.79 +APA,APA Corporation,28.11 +TECH,Bio-Techne,63.46 +MOS,Mosaic Company (The),31.21 +BXP,BXP Inc.,61.61 +DVA,DaVita,144.55 +SWKS,Skyworks Solutions,63.62 +HSIC,Henry Schein,81.21 +CAG,Conagra Brands,19.95 +MGM,MGM Resorts,36.39 +ARE,Alexandria Real Estate Equities,53.92 +FRT,Federal Realty Investment Trust,107.14 +CPB,Campbell Soup Company,29.16 +NWSA,News Corp (Class A),23.30 +CRL,Charles River Laboratories,164.83 +MTCH,Match Group,31.27 +FDS,FactSet,193.29 +LW,Lamb Weston,50.33 +PAYC,Paycom,117.68 +MOH,Molina Healthcare,122.46 +NWS,News Corp (Class B),26.91 diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index 5a390b4..dae9c4c 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,13 +1,7 @@ -no\ntnu\gruppe53\Portfolio.class -no\ntnu\gruppe53\PurchaseCalculator.class -no\ntnu\gruppe53\Stock.class -no\ntnu\gruppe53\Purchase.class -no\ntnu\gruppe53\Share.class -no\ntnu\gruppe53\TransactionCalculator.class -no\ntnu\gruppe53\SaleCalculator.class -no\ntnu\gruppe53\App.class -no\ntnu\gruppe53\Exchange.class -no\ntnu\gruppe53\TransactionArchive.class -no\ntnu\gruppe53\Transaction.class -no\ntnu\gruppe53\Player.class -no\ntnu\gruppe53\Sale.class +no/ntnu/gruppe53/FileHandler.class +no/ntnu/gruppe53/controller/ViewController.class +no/ntnu/gruppe53/controller/GameController.class +no/ntnu/gruppe53/views/PortfolioView.class +no/ntnu/gruppe53/controller/StartViewController.class +no/ntnu/gruppe53/views/PurchaseView.class +no/ntnu/gruppe53/views/StartView.class diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index aa43cc4..0a05fbe 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -1,13 +1,20 @@ -C:\Users\Roar\Desktop\Github Repoer\Millions\millions\src\main\java\no\ntnu\gruppe53\App.java -C:\Users\Roar\Desktop\Github Repoer\Millions\millions\src\main\java\no\ntnu\gruppe53\Exchange.java -C:\Users\Roar\Desktop\Github Repoer\Millions\millions\src\main\java\no\ntnu\gruppe53\Player.java -C:\Users\Roar\Desktop\Github Repoer\Millions\millions\src\main\java\no\ntnu\gruppe53\Portfolio.java -C:\Users\Roar\Desktop\Github Repoer\Millions\millions\src\main\java\no\ntnu\gruppe53\Purchase.java -C:\Users\Roar\Desktop\Github Repoer\Millions\millions\src\main\java\no\ntnu\gruppe53\PurchaseCalculator.java -C:\Users\Roar\Desktop\Github Repoer\Millions\millions\src\main\java\no\ntnu\gruppe53\Sale.java -C:\Users\Roar\Desktop\Github Repoer\Millions\millions\src\main\java\no\ntnu\gruppe53\SaleCalculator.java -C:\Users\Roar\Desktop\Github Repoer\Millions\millions\src\main\java\no\ntnu\gruppe53\Share.java -C:\Users\Roar\Desktop\Github Repoer\Millions\millions\src\main\java\no\ntnu\gruppe53\Stock.java -C:\Users\Roar\Desktop\Github Repoer\Millions\millions\src\main\java\no\ntnu\gruppe53\Transaction.java -C:\Users\Roar\Desktop\Github Repoer\Millions\millions\src\main\java\no\ntnu\gruppe53\TransactionArchive.java -C:\Users\Roar\Desktop\Github Repoer\Millions\millions\src\main\java\no\ntnu\gruppe53\TransactionCalculator.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/App.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Exchange.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Player.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Portfolio.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Purchase.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Sale.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/SaleCalculator.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Share.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Stock.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Transaction.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/TransactionArchive.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/TransactionCalculator.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java +/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java From 4de2b5dec34c29df08305fabe6d806306d4c89de Mon Sep 17 00:00:00 2001 From: Are Lodgaard Date: Fri, 24 Apr 2026 15:12:06 +0200 Subject: [PATCH 016/142] Added navigation bar to Purchase View --- .../no/ntnu/gruppe53/views/PurchaseView.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java index e1e47a2..26c67b6 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java @@ -4,6 +4,7 @@ import java.math.RoundingMode; import java.util.List; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.stream.Collectors; import javafx.geometry.Insets; @@ -12,6 +13,7 @@ import javafx.scene.control.ListView; import javafx.scene.control.Spinner; import javafx.scene.control.SpinnerValueFactory; +import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import no.ntnu.gruppe53.Exchange; import no.ntnu.gruppe53.Player; @@ -22,15 +24,22 @@ public class PurchaseView extends VBox { private final ListView stocksListView; private final Spinner quantitySpinner; private final Button purchaseButton; + private final Button portfolioButton; private Exchange exchange; private Player player; private BiConsumer purchaseHandler; + private Consumer showPortfolioHandler; public PurchaseView() { setSpacing(12); setPadding(new Insets(16)); + HBox navigationBar = new HBox(10); + Button purchaseViewButton = new Button("Market"); + portfolioButton = new Button("Portfolio"); + navigationBar.getChildren().addAll(purchaseViewButton, portfolioButton); + Label titleLabel = new Label("Stock Market"); titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); @@ -60,7 +69,18 @@ public PurchaseView() { purchaseHandler.accept(selectedStock, quantity); }); + purchaseViewButton.setOnAction(event -> { + // already on purchase/market view + }); + + portfolioButton.setOnAction(event -> { + if (showPortfolioHandler != null) { + showPortfolioHandler.accept(null); + } + }); + getChildren().addAll( + navigationBar, titleLabel, playerMoneyLabel, stocksTitleLabel, @@ -75,6 +95,10 @@ public void setPurchaseHandler(BiConsumer purchaseHandler) { this.purchaseHandler = purchaseHandler; } + public void setShowPortfolioHandler(Consumer showPortfolioHandler) { + this.showPortfolioHandler = showPortfolioHandler; + } + public void setPlayer(Player player) { this.player = player; @@ -98,7 +122,6 @@ public void refreshPlayer() { public void refreshStocks() { if (exchange == null) { stocksListView.getItems().clear(); - System.out.println("Exchange is null"); return; } From cb0c503412e51714334557a658b8511225a8fe00 Mon Sep 17 00:00:00 2001 From: Are Lodgaard Date: Fri, 24 Apr 2026 15:13:06 +0200 Subject: [PATCH 017/142] Added navigation bar to Portfolio View --- .../no/ntnu/gruppe53/views/PortfolioView.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index 776cea2..7059120 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -3,11 +3,14 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.List; +import java.util.function.Consumer; import java.util.stream.Collectors; import javafx.geometry.Insets; +import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ListView; +import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import no.ntnu.gruppe53.Player; import no.ntnu.gruppe53.Share; @@ -17,13 +20,21 @@ public class PortfolioView extends VBox { private final Label playerNameLabel; private final Label netWorthLabel; private final ListView portfolioListView; + private final Button marketButton; + private final Button portfolioButton; private Player player; + private Consumer showMarketHandler; public PortfolioView() { setSpacing(12); setPadding(new Insets(16)); + HBox navigationBar = new HBox(10); + marketButton = new Button("Market"); + portfolioButton = new Button("Portfolio"); + navigationBar.getChildren().addAll(marketButton, portfolioButton); + titleLabel = new Label("Portfolio"); titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); @@ -33,7 +44,21 @@ public PortfolioView() { portfolioListView = new ListView<>(); portfolioListView.setPrefHeight(300); - getChildren().addAll(titleLabel, playerNameLabel, netWorthLabel, portfolioListView); + marketButton.setOnAction(event -> { + if (showMarketHandler != null) { + showMarketHandler.accept(null); + } + }); + + portfolioButton.setOnAction(event -> { + // already on portfolio view + }); + + getChildren().addAll(navigationBar, titleLabel, playerNameLabel, netWorthLabel, portfolioListView); + } + + public void setShowMarketHandler(Consumer showMarketHandler) { + this.showMarketHandler = showMarketHandler; } public void setPlayer(Player player) { From 25a9f6f5dda596f4c62d9481eca888b2df831be5 Mon Sep 17 00:00:00 2001 From: Are Lodgaard Date: Fri, 24 Apr 2026 15:13:47 +0200 Subject: [PATCH 018/142] Added logic to gamecontroller for navigation bar --- .../main/java/no/ntnu/gruppe53/controller/GameController.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index 310578a..d325483 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -37,6 +37,9 @@ public void startNewGame() { portfolioView = new PortfolioView(); portfolioView.setPlayer(player); + purchaseView.setShowPortfolioHandler(v -> showPortfolio()); + portfolioView.setShowMarketHandler(v -> showPurchaseView()); + vm.addView("purchase", purchaseView); vm.addView("portfolio", portfolioView); From 0a34f3c70361ce58169b884013c25164d833a95e Mon Sep 17 00:00:00 2001 From: Are Lodgaard Date: Fri, 24 Apr 2026 15:23:28 +0200 Subject: [PATCH 019/142] Regression fix for portfolio and purchase view --- .../gruppe53/controller/GameController.java | 29 +++++++- .../no/ntnu/gruppe53/views/PortfolioView.java | 68 +++++++++++++++---- .../no/ntnu/gruppe53/views/PurchaseView.java | 9 ++- .../compile/default-compile/createdFiles.lst | 6 -- 4 files changed, 84 insertions(+), 28 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index d325483..3ca1d94 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -6,6 +6,7 @@ import no.ntnu.gruppe53.Exchange; import no.ntnu.gruppe53.FileHandler; import no.ntnu.gruppe53.Player; +import no.ntnu.gruppe53.Share; import no.ntnu.gruppe53.Stock; import no.ntnu.gruppe53.views.PortfolioView; import no.ntnu.gruppe53.views.PurchaseView; @@ -37,8 +38,9 @@ public void startNewGame() { portfolioView = new PortfolioView(); portfolioView.setPlayer(player); - purchaseView.setShowPortfolioHandler(v -> showPortfolio()); - portfolioView.setShowMarketHandler(v -> showPurchaseView()); + purchaseView.setShowPortfolioHandler(this::showPortfolio); + portfolioView.setShowMarketHandler(this::showPurchaseView); + portfolioView.setSellHandler(this::sellShare); vm.addView("purchase", purchaseView); vm.addView("portfolio", portfolioView); @@ -75,4 +77,27 @@ public void showPurchaseView() { vm.switchView("purchase"); } } + + private void sellShare(Share share, int quantity) { + if (exchange == null || player == null || share == null || quantity <= 0) { + return; + } + + try { + Share shareToSell = new Share( + share.getStock(), + BigDecimal.valueOf(quantity), + share.getPurchasePrice() + ); + + exchange.sell(shareToSell, player); + purchaseView.setPlayer(player); + purchaseView.refreshStocks(); + portfolioView.setPlayer(player); + } catch (RuntimeException ex) { + System.out.println("Sell failed: " + ex.getMessage()); + } + } + + } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index 7059120..f444051 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -2,14 +2,15 @@ import java.math.BigDecimal; import java.math.RoundingMode; -import java.util.List; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import java.util.stream.Collectors; import javafx.geometry.Insets; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ListView; +import javafx.scene.control.Spinner; +import javafx.scene.control.SpinnerValueFactory; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import no.ntnu.gruppe53.Player; @@ -19,12 +20,15 @@ public class PortfolioView extends VBox { private final Label titleLabel; private final Label playerNameLabel; private final Label netWorthLabel; - private final ListView portfolioListView; + private final ListView portfolioListView; private final Button marketButton; private final Button portfolioButton; + private final Spinner quantitySpinner; + private final Button sellButton; private Player player; - private Consumer showMarketHandler; + private BiConsumer sellHandler; + private Runnable showMarketHandler; public PortfolioView() { setSpacing(12); @@ -43,10 +47,26 @@ public PortfolioView() { portfolioListView = new ListView<>(); portfolioListView.setPrefHeight(300); + portfolioListView.setCellFactory(list -> new javafx.scene.control.ListCell<>() { + @Override + protected void updateItem(Share item, boolean empty) { + super.updateItem(item, empty); + setText(empty || item == null ? null : formatShare(item)); + } + }); + + HBox sellBar = new HBox(10); + Label quantityLabel = new Label("Quantity:"); + quantitySpinner = new Spinner<>(); + quantitySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1_000_000, 1)); + quantitySpinner.setEditable(true); + + sellButton = new Button("Sell"); + sellBar.getChildren().addAll(quantityLabel, quantitySpinner, sellButton); marketButton.setOnAction(event -> { if (showMarketHandler != null) { - showMarketHandler.accept(null); + showMarketHandler.run(); } }); @@ -54,13 +74,38 @@ public PortfolioView() { // already on portfolio view }); - getChildren().addAll(navigationBar, titleLabel, playerNameLabel, netWorthLabel, portfolioListView); + sellButton.setOnAction(event -> { + if (sellHandler == null) { + return; + } + + Share selectedShare = portfolioListView.getSelectionModel().getSelectedItem(); + if (selectedShare == null) { + return; + } + + int quantity = quantitySpinner.getValue(); + sellHandler.accept(selectedShare, quantity); + }); + + getChildren().addAll( + navigationBar, + titleLabel, + playerNameLabel, + netWorthLabel, + portfolioListView, + sellBar + ); } - public void setShowMarketHandler(Consumer showMarketHandler) { + public void setShowMarketHandler(Runnable showMarketHandler) { this.showMarketHandler = showMarketHandler; } + public void setSellHandler(BiConsumer sellHandler) { + this.sellHandler = sellHandler; + } + public void setPlayer(Player player) { this.player = player; refresh(); @@ -76,14 +121,7 @@ public void refresh() { playerNameLabel.setText("Player: " + player.getName()); netWorthLabel.setText("Net worth: $" + formatMoney(player.getNetWorth())); - - List shares = player.getPortfolio().getShares(); - - portfolioListView.getItems().setAll( - shares.stream() - .map(this::formatShare) - .collect(Collectors.toList()) - ); + portfolioListView.getItems().setAll(player.getPortfolio().getShares()); } private String formatShare(Share share) { diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java index 26c67b6..756e1d4 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java @@ -4,7 +4,6 @@ import java.math.RoundingMode; import java.util.List; import java.util.function.BiConsumer; -import java.util.function.Consumer; import java.util.stream.Collectors; import javafx.geometry.Insets; @@ -29,7 +28,7 @@ public class PurchaseView extends VBox { private Exchange exchange; private Player player; private BiConsumer purchaseHandler; - private Consumer showPortfolioHandler; + private Runnable showPortfolioHandler; public PurchaseView() { setSpacing(12); @@ -70,12 +69,12 @@ public PurchaseView() { }); purchaseViewButton.setOnAction(event -> { - // already on purchase/market view + // already on market view }); portfolioButton.setOnAction(event -> { if (showPortfolioHandler != null) { - showPortfolioHandler.accept(null); + showPortfolioHandler.run(); } }); @@ -95,7 +94,7 @@ public void setPurchaseHandler(BiConsumer purchaseHandler) { this.purchaseHandler = purchaseHandler; } - public void setShowPortfolioHandler(Consumer showPortfolioHandler) { + public void setShowPortfolioHandler(Runnable showPortfolioHandler) { this.showPortfolioHandler = showPortfolioHandler; } diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index dae9c4c..38227b7 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,7 +1 @@ no/ntnu/gruppe53/FileHandler.class -no/ntnu/gruppe53/controller/ViewController.class -no/ntnu/gruppe53/controller/GameController.class -no/ntnu/gruppe53/views/PortfolioView.class -no/ntnu/gruppe53/controller/StartViewController.class -no/ntnu/gruppe53/views/PurchaseView.class -no/ntnu/gruppe53/views/StartView.class From 7d0fca1841963074536c528b390719bf9853fcaa Mon Sep 17 00:00:00 2001 From: Are Lodgaard Date: Fri, 24 Apr 2026 15:24:19 +0200 Subject: [PATCH 020/142] Same regression fix for GameController --- .../gruppe53/controller/GameController.java | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index 3ca1d94..e5174a6 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -63,21 +63,6 @@ private void purchaseStock(String symbol, int quantity) { } } - public void showPortfolio() { - if (portfolioView != null) { - portfolioView.setPlayer(player); - vm.switchView("portfolio"); - } - } - - public void showPurchaseView() { - if (purchaseView != null) { - purchaseView.setPlayer(player); - purchaseView.refreshStocks(); - vm.switchView("purchase"); - } - } - private void sellShare(Share share, int quantity) { if (exchange == null || player == null || share == null || quantity <= 0) { return; @@ -99,5 +84,18 @@ private void sellShare(Share share, int quantity) { } } + public void showPortfolio() { + if (portfolioView != null) { + portfolioView.setPlayer(player); + vm.switchView("portfolio"); + } + } + public void showPurchaseView() { + if (purchaseView != null) { + purchaseView.setPlayer(player); + purchaseView.refreshStocks(); + vm.switchView("purchase"); + } + } } \ No newline at end of file From 027d42b0be4874e2bcb5adf7a2a9694749c06190 Mon Sep 17 00:00:00 2001 From: Are Lodgaard Date: Fri, 24 Apr 2026 15:55:07 +0200 Subject: [PATCH 021/142] I don't remember all that I changed --- .../main/java/no/ntnu/gruppe53/views/PortfolioView.java | 4 +--- .../src/main/java/no/ntnu/gruppe53/views/PurchaseView.java | 4 +--- .../compile/default-compile/createdFiles.lst | 7 +++++++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index f444051..6f3b4c6 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -70,9 +70,7 @@ protected void updateItem(Share item, boolean empty) { } }); - portfolioButton.setOnAction(event -> { - // already on portfolio view - }); + sellButton.setOnAction(event -> { if (sellHandler == null) { diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java index 756e1d4..d27610d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java @@ -68,9 +68,7 @@ public PurchaseView() { purchaseHandler.accept(selectedStock, quantity); }); - purchaseViewButton.setOnAction(event -> { - // already on market view - }); + portfolioButton.setOnAction(event -> { if (showPortfolioHandler != null) { diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index 38227b7..eedb63b 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1 +1,8 @@ no/ntnu/gruppe53/FileHandler.class +no/ntnu/gruppe53/controller/ViewController.class +no/ntnu/gruppe53/controller/GameController.class +no/ntnu/gruppe53/views/PortfolioView.class +no/ntnu/gruppe53/controller/StartViewController.class +no/ntnu/gruppe53/views/PurchaseView.class +no/ntnu/gruppe53/views/StartView.class +no/ntnu/gruppe53/views/PortfolioView$1.class From fbbd7ac0ee2f87d2f5f38b6951a31bce6b48ea65 Mon Sep 17 00:00:00 2001 From: Are Lodgaard Date: Mon, 11 May 2026 13:36:32 +0200 Subject: [PATCH 022/142] Refactored the navigation bar in Portfolio view --- .../no/ntnu/gruppe53/views/PortfolioView.java | 66 ++- millions/target/classes/sp500.csv | 506 ++++++++++++++++++ 2 files changed, 559 insertions(+), 13 deletions(-) create mode 100644 millions/target/classes/sp500.csv diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index 6f3b4c6..94a7b7b 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -3,7 +3,6 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.function.BiConsumer; -import java.util.stream.Collectors; import javafx.geometry.Insets; import javafx.scene.control.Button; @@ -11,36 +10,58 @@ import javafx.scene.control.ListView; import javafx.scene.control.Spinner; import javafx.scene.control.SpinnerValueFactory; +import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import no.ntnu.gruppe53.Player; import no.ntnu.gruppe53.Share; public class PortfolioView extends VBox { - private final Label titleLabel; + + + //Main pane of view + private final BorderPane borderPane; + + //Buttons + private final Button sellButton; + private Button marketButton; + private Button portfolioButton; + + //Static Labels + private final Label viewLabel; + + //Dynamic Labels private final Label playerNameLabel; private final Label netWorthLabel; + + private final ListView portfolioListView; - private final Button marketButton; - private final Button portfolioButton; + private final Spinner quantitySpinner; - private final Button sellButton; + private Player player; private BiConsumer sellHandler; private Runnable showMarketHandler; + + public PortfolioView() { + + this.borderPane = new BorderPane(); + + this.borderPane.setTop(navigationBar()); + + + setSpacing(12); setPadding(new Insets(16)); - HBox navigationBar = new HBox(10); - marketButton = new Button("Market"); - portfolioButton = new Button("Portfolio"); - navigationBar.getChildren().addAll(marketButton, portfolioButton); - titleLabel = new Label("Portfolio"); - titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); + + + viewLabel = new Label("Portfolio"); + viewLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); playerNameLabel = new Label("Player: -"); netWorthLabel = new Label("Net worth: $0.00"); @@ -87,8 +108,8 @@ protected void updateItem(Share item, boolean empty) { }); getChildren().addAll( - navigationBar, - titleLabel, + + viewLabel, playerNameLabel, netWorthLabel, portfolioListView, @@ -96,6 +117,24 @@ protected void updateItem(Share item, boolean empty) { ); } +private HBox navigationBar() { + + HBox hBox = new HBox(); + + //Sets simple styling + hBox.setPadding(new Insets(15, 12, 15, 12)); + hBox.setSpacing(10); + hBox.setStyle("-fx-background-color: #336699;"); + + marketButton = new Button("Market"); + portfolioButton = new Button("Portfolio"); + + hBox.getChildren().addAll(marketButton, portfolioButton); + + + return hBox; +} + public void setShowMarketHandler(Runnable showMarketHandler) { this.showMarketHandler = showMarketHandler; } @@ -132,6 +171,7 @@ private String formatShare(Share share) { + formatMoney(share.getPurchasePrice()); } + //Formats any BigDecimal to a legible money value with a scale of - > 0.00 to a plainStrign(). private String formatMoney(BigDecimal money) { return money == null ? "0.00" : money.setScale(2, RoundingMode.HALF_UP).toPlainString(); } diff --git a/millions/target/classes/sp500.csv b/millions/target/classes/sp500.csv new file mode 100644 index 0000000..d9cec61 --- /dev/null +++ b/millions/target/classes/sp500.csv @@ -0,0 +1,506 @@ +# S&P 500 Companies by Market Cap +# Ticker,Name,Price + +NVDA,Nvidia,191.27 +AAPL,Apple Inc.,276.43 +MSFT,Microsoft,404.68 +AMZN,Amazon,204.62 +GOOGL,Alphabet Inc. (Class A),311.20 +GOOG,Alphabet Inc. (Class C),311.62 +META,Meta Platforms,669.41 +AVGO,Broadcom,343.35 +TSLA,Tesla Inc.,426.52 +BRK.B,Berkshire Hathaway,501.05 +WMT,Walmart,128.75 +LLY,Lilly (Eli),1014.43 +JPM,JPMorgan Chase,311.14 +XOM,ExxonMobil,155.28 +V,Visa Inc.,329.54 +JNJ,Johnson & Johnson,240.70 +MA,Mastercard,539.52 +MU,Micron Technology,411.25 +ORCL,Oracle Corporation,157.08 +COST,Costco,979.71 +BAC,Bank of America,54.06 +ABBV,AbbVie,220.17 +HD,Home Depot (The),389.46 +PG,Procter & Gamble,159.45 +CVX,Chevron Corporation,185.66 +CAT,Caterpillar Inc.,773.53 +AMD,Advanced Micro Devices,213.00 +CSCO,Cisco,85.82 +KO,Coca-Cola Company (The),78.51 +NFLX,Netflix,79.94 +GE,GE Aerospace,314.37 +PLTR,Palantir Technologies,135.59 +LRCX,Lam Research,236.60 +MRK,Merck & Co.,118.79 +PM,Philip Morris International,185.99 +GS,Goldman Sachs,949.29 +MS,Morgan Stanley,176.86 +WFC,Wells Fargo,89.07 +AMAT,Applied Materials,342.19 +RTX,RTX Corporation,197.56 +IBM,IBM,273.86 +UNH,UnitedHealth Group,278.79 +AXP,American Express,355.18 +INTC,Intel,47.85 +TMUS,T-Mobile US,207.62 +PEP,PepsiCo,168.71 +MCD,McDonald's,323.50 +GEV,GE Vernova,822.50 +LIN,Linde plc,466.04 +C,Citigroup,117.93 +TXN,Texas Instruments,226.34 +VZ,Verizon,48.55 +T,AT&T,28.21 +TMO,Thermo Fisher Scientific,525.00 +AMGN,Amgen,364.77 +ABT,Abbott Laboratories,112.97 +KLAC,KLA Corporation,1492.27 +GILD,Gilead Sciences,155.71 +DIS,Walt Disney Company (The),108.30 +NEE,NextEra Energy,91.19 +BA,Boeing,236.98 +ANET,Arista Networks,141.06 +APH,Amphenol,144.60 +ISRG,Intuitive Surgical,496.14 +CRM,Salesforce,184.94 +SCHW,Charles Schwab Corporation,95.50 +BLK,BlackRock,1083.35 +TJX,TJX Companies,150.56 +DE,Deere & Company,610.03 +ADI,Analog Devices,336.71 +LOW,Lowe's,286.57 +PFE,Pfizer,27.77 +UNP,Union Pacific Corporation,261.95 +DHR,Danaher Corporation,219.80 +APP,AppLovin Corporation,459.27 +HON,Honeywell,243.56 +ETN,Eaton Corporation,394.94 +QCOM,Qualcomm,141.90 +UBER,Uber,70.72 +LMT,Lockheed Martin,630.54 +WELL,Welltower,208.65 +ACN,Accenture,230.79 +BKNG,Booking Holdings,4322.85 +SYK,Stryker Corporation,362.53 +COP,ConocoPhillips,110.75 +NEM,Newmont,123.89 +COF,Capital One,214.93 +PLD,Prologis,140.35 +MDT,Medtronic,100.87 +CB,Chubb Limited,328.82 +PH,Parker Hannifin,998.24 +PGR,Progressive Corporation,209.33 +BMY,Bristol Myers Squibb,60.18 +HCA,HCA Healthcare,531.83 +SPGI,S&P Global,396.65 +CMCSA,Comcast,32.53 +VRTX,Vertex Pharmaceuticals,459.93 +MCK,McKesson Corporation,944.20 +PANW,Palo Alto Networks,165.49 +GLW,Corning Inc.,134.16 +SBUX,Starbucks,99.03 +INTU,Intuit,401.05 +MO,Altria,65.72 +BSX,Boston Scientific,73.76 +CME,CME Group,303.44 +NOW,ServiceNow,101.55 +ADBE,Adobe Inc.,258.39 +TT,Trane Technologies,473.75 +CRWD,CrowdStrike,414.94 +BX,Blackstone Inc.,133.29 +UPS,United Parcel Service,119.93 +SO,Southern Company,90.86 +CEG,Constellation Energy,274.37 +DUK,Duke Energy,124.86 +CVS,CVS Health,76.36 +MAR,Marriott International,360.71 +NOC,Northrop Grumman,680.45 +PNC,PNC Financial Services,237.28 +WM,Waste Management,234.67 +GD,General Dynamics,348.79 +WDC,Western Digital,277.26 +KKR,KKR,105.28 +HWM,Howmet Aerospace,233.13 +FCX,Freeport-McMoRan,65.30 +NKE,Nike Inc.,62.43 +USB,U.S. Bancorp,59.25 +MMM,3M,173.46 +SHW,Sherwin-Williams,364.16 +RCL,Royal Caribbean Group,331.76 +SNDK,Sandisk Corporation,607.86 +STX,Seagate Technology,409.41 +EMR,Emerson Electric,156.30 +ADP,Automatic Data Processing,217.59 +WMB,Williams Companies,71.48 +ICE,Intercontinental Exchange,153.60 +FDX,FedEx,368.49 +ITW,Illinois Tool Works,298.50 +JCI,Johnson Controls,140.75 +CRH,CRH plc,127.89 +ECL,Ecolab,301.35 +EQIX,Equinix,863.66 +BK,BNY Mellon,122.83 +MRSH,Marsh & McLennan Companies Inc.,174.09 +AMT,American Tower,179.46 +CMI,Cummins,601.45 +SNPS,Synopsys,433.56 +REGN,Regeneron Pharmaceuticals,780.09 +DELL,Dell Technologies,124.37 +CDNS,Cadence Design Systems,298.74 +CTAS,Cintas,201.10 +ORLY,O'Reilly Auto Parts,93.87 +MNST,Monster Beverage,80.88 +MDLZ,Mondelez International,61.45 +PWR,Quanta Services,523.69 +CI,Cigna,292.46 +CSX,CSX Corporation,41.30 +CL,Colgate-Palmolive,95.04 +SLB,Schlumberger,51.14 +HLT,Hilton Worldwide,327.38 +DASH,DoorDash,175.41 +TDG,TransDigm Group,1325.26 +MCO,Moody's Corporation,415.20 +APO,Apollo Global Management,127.53 +ELV,Elevance Health,329.59 +ABNB,Airbnb,119.56 +GM,General Motors,79.78 +NSC,Norfolk Southern Railway,316.73 +COR,Cencora,365.43 +MSI,Motorola Solutions,423.10 +KMI,Kinder Morgan,31.64 +RSG,Republic Services,226.72 +HOOD,Robinhood Markets Inc.,77.55 +WBD,Warner Bros. Discovery,28.01 +TFC,Truist Financial,54.42 +PCAR,Paccar,129.93 +AON,Aon,314.02 +TEL,TE Connectivity,227.16 +APD,Air Products,293.38 +AEP,American Electric Power,122.18 +FTNT,Fortinet,87.72 +TRV,Travelers Companies (The),299.75 +PSX,Phillips 66,161.13 +LHX,L3Harris,341.14 +EOG,EOG Resources,117.39 +SPG,Simon Property Group,195.66 +NXPI,NXP Semiconductors,249.26 +ROST,Ross Stores,192.31 +VLO,Valero Energy,203.89 +AZO,AutoZone,3733.09 +MPC,Marathon Petroleum,207.85 +BKR,Baker Hughes,61.16 +AFL,Aflac,116.20 +DLR,Digital Realty,174.16 +SRE,Sempra,90.83 +O,Realty Income,64.39 +MPWR,Monolithic Power Systems,1197.55 +GWW,W. W. Grainger,1202.13 +ZTS,Zoetis,128.19 +CARR,Carrier Global,66.80 +D,Dominion Energy,64.61 +F,Ford Motor Company,13.78 +URI,United Rentals,870.17 +AME,Ametek,236.33 +VST,Vistra Corp.,160.43 +FAST,Fastenal,47.14 +ALL,Allstate,205.91 +OKE,ONEOK,85.03 +AJG,Arthur J. Gallagher & Co.,207.61 +CAH,Cardinal Health,225.15 +CVNA,Carvana Co.,365.94 +IDXX,Idexx Laboratories,647.63 +MET,MetLife,78.87 +TGT,Target Corporation,114.12 +PSA,Public Storage,293.33 +BDX,Becton Dickinson,179.62 +CTVA,Corteva,75.48 +TER,Teradyne,323.92 +EA,Electronic Arts,201.72 +ADSK,Autodesk,232.93 +FITB,Fifth Third Bancorp,54.59 +CMG,Chipotle Mexican Grill,37.35 +FANG,Diamondback Energy,168.93 +TRGP,Targa Resources,222.03 +FIX,Comfort Systems USA Inc.,1345.62 +DHI,D. R. Horton,163.35 +HSY,Hershey Company (The),231.46 +OXY,Occidental Petroleum,47.34 +DAL,Delta Air Lines,71.16 +ROK,Rockwell Automation,413.43 +NDAQ,Nasdaq Inc.,81.05 +XEL,Xcel Energy,77.79 +EW,Edwards Lifesciences,78.67 +CCL,Carnival,32.80 +CBRE,CBRE Group,151.76 +ETR,Entergy,100.79 +EXC,Exelon,44.57 +AMP,Ameriprise Financial,489.27 +NUE,Nucor,194.78 +DDOG,Datadog,126.70 +YUM,Yum! Brands,160.35 +MCHP,Microchip Technology,80.90 +WAB,Wabtec,255.15 +KR,Kroger,68.68 +AIG,American International Group,79.36 +VMC,Vulcan Materials Company,321.01 +CIEN,Ciena Corporation,301.11 +SYY,Sysco,88.04 +PEG,Public Service Enterprise Group,83.85 +COIN,Coinbase Global,152.61 +ODFL,Old Dominion,195.74 +KEYS,Keysight Technologies,237.88 +KDP,Keurig Dr Pepper,29.84 +VTR,Ventas,85.29 +MLM,Martin Marietta Materials,663.38 +GRMN,Garmin,206.52 +ED,Consolidated Edison,109.36 +HIG,Hartford (The),142.02 +LVS,Las Vegas Sands,57.75 +CPRT,Copart,39.73 +EL,Estée Lauder Companies (The),106.09 +IR,Ingersoll Rand,96.98 +WDAY,Workday Inc.,145.19 +MSCI,MSCI,519.16 +TTWO,Take-Two Interactive,204.25 +RMD,ResMed,259.29 +EBAY,eBay,82.95 +PCG,PG&E Corporation,17.02 +CCI,Crown Castle,85.66 +PYPL,PayPal,40.26 +PRU,Prudential Financial,105.34 +WEC,WEC Energy Group,113.19 +UAL,United Airlines Holdings,113.50 +STT,State Street Corporation,131.37 +HBAN,Huntington Bancshares,18.02 +A,Agilent Technologies,128.22 +GEHC,GE HealthCare,79.29 +MTB,M&T Bank,235.06 +EME,EMCOR Group Inc.,803.53 +ACGL,Arch Capital Group,98.49 +KMB,Kimberly-Clark,107.35 +ROP,Roper Technologies,333.96 +EQT,EQT Corporation,56.73 +KVUE,Kenvue,18.44 +LYV,Live Nation Entertainment,150.60 +OTIS,Otis Worldwide,89.85 +AXON,Axon Enterprise,436.36 +NRG,NRG Energy,160.11 +CTSH,Cognizant,71.22 +IBKR,Interactive Brokers Group,76.54 +PAYX,Paychex,94.49 +FISV,Fiserv Inc.,62.72 +ADM,Archer Daniels Midland,69.24 +XYZ,Block Inc.,53.87 +FICO,Fair Isaac,1369.86 +DG,Dollar General,147.49 +DOV,Dover Corporation,232.52 +ROL,Rollins Inc.,65.74 +HPE,Hewlett Packard Enterprise,23.62 +RJF,Raymond James Financial,159.60 +TPR,Tapestry Inc.,154.43 +VICI,Vici Properties,29.18 +TDY,Teledyne Technologies,657.92 +XYL,Xylem Inc.,126.71 +CHTR,Charter Communications,241.08 +ARES,Ares Management Corporation,137.90 +ULTA,Ulta Beauty,684.25 +STLD,Steel Dynamics,206.43 +EXR,Extra Space Storage,142.02 +LEN,Lennar,120.76 +IQV,IQVIA,175.80 +IRM,Iron Mountain,99.67 +KHC,Kraft Heinz,24.88 +PPG,PPG Industries,130.53 +HAL,Halliburton,34.84 +ATO,Atmos Energy,175.22 +DTE,DTE Energy,139.17 +TSCO,Tractor Supply,54.53 +EXPE,Expedia Group,235.08 +CFG,Citizens Financial Group,66.87 +AEE,Ameren,106.08 +TPL,Texas Pacific Land Corporation,416.14 +CBOE,Cboe Global Markets,271.99 +ON,ON Semiconductor,70.68 +MTD,Mettler Toledo,1391.33 +STZ,Constellation Brands,163.02 +BIIB,Biogen,191.29 +DVN,Devon Energy,44.78 +FE,FirstEnergy,47.94 +JBL,Jabil,260.92 +NTRS,Northern Trust,147.45 +HUBB,Hubbell Incorporated,513.63 +WTW,Willis Towers Watson,282.86 +WRB,W. R. Berkley Corporation,71.20 +RF,Regions Financial Corporation,30.86 +PHM,PulteGroup,139.04 +CNP,CenterPoint Energy,40.91 +PPL,PPL Corporation,35.97 +DXCM,Dexcom,68.19 +SW,Smurfit WestRock,50.23 +ES,Eversource Energy,69.77 +GIS,General Mills,48.40 +EIX,Edison International,66.94 +IP,International Paper,48.64 +WSM,Williams-Sonoma,214.52 +CINF,Cincinnati Financial,164.11 +LUV,Southwest Airlines,51.66 +AVB,AvalonBay Communities,179.80 +SYF,Synchrony Financial,72.95 +FIS,Fidelity National Information Services,48.73 +KEY,KeyCorp,22.67 +DLTR,Dollar Tree,125.35 +EQR,Equity Residential,65.09 +EXE,Expand Energy,103.09 +DRI,Darden Restaurants,212.58 +FSLR,First Solar,227.60 +DOW,Dow Inc.,33.98 +CPAY,Corpay,346.25 +AWK,American Water Works,123.54 +CHD,Church & Dwight,100.17 +LH,LabCorp,289.08 +VRSK,Verisk Analytics,171.87 +Q,Qnity Electronics,114.06 +CTRA,Coterra,31.48 +STE,Steris,243.09 +EFX,Equifax,197.29 +VLTO,Veralto,95.33 +BG,Bunge Global,121.39 +DGX,Quest Diagnostics,209.53 +CHRW,C.H. Robinson,196.77 +AMCR,Amcor,49.62 +TSN,Tyson Foods,64.52 +L,Loews Corporation,110.15 +CMS,CMS Energy,74.16 +BRO,Brown & Brown,67.07 +LDOS,Leidos,174.83 +PKG,Packaging Corporation of America,244.06 +JBHT,J.B. Hunt,231.62 +OMC,Omnicom Group,69.53 +EXPD,Expeditors International,163.06 +RL,Ralph Lauren Corporation,359.55 +NVR,NVR Inc.,8082.38 +DD,DuPont,51.29 +HUM,Humana,176.34 +NI,NiSource,44.78 +NTAP,NetApp,105.69 +GPC,Genuine Parts Company,149.43 +LULU,Lululemon Athletica,176.88 +ALB,Albemarle Corporation,176.03 +TROW,T. Rowe Price,94.24 +PFG,Principal Financial Group,92.86 +CSGP,CoStar Group,48.09 +GPN,Global Payments,72.67 +SBAC,SBA Communications,190.43 +SNA,Snap-on,383.21 +CNC,Centene Corporation,40.26 +VRSN,Verisign,215.66 +WAT,Waters Corporation,331.52 +IFF,International Flavors & Fragrances,76.65 +BR,Broadridge Financial Solutions,167.88 +WY,Weyerhaeuser,27.09 +INCY,Incyte,99.35 +LII,Lennox International,553.57 +LYB,LyondellBasell,59.29 +SMCI,Supermicro,31.69 +MKC,McCormick & Company,70.30 +ZBH,Zimmer Biomet,95.21 +PTC,PTC Inc.,155.52 +FTV,Fortive,58.93 +VTRS,Viatris,16.02 +EVRG,Evergy,78.94 +BALL,Ball Corporation,67.29 +HPQ,HP Inc.,19.63 +WST,West Pharmaceutical Services,247.87 +PODD,Insulet Corporation,253.09 +APTV,Aptiv,83.64 +CDW,CDW,135.32 +LNT,Alliant Energy,68.17 +TXT,Textron,96.68 +ESS,Essex Property Trust,262.76 +HOLX,Hologic,75.11 +J,Jacobs Solutions,142.70 +INVH,Invitation Homes,27.16 +TKO,TKO Group Holdings,210.88 +NDSN,Nordson Corporation,295.64 +DECK,Deckers Brands,115.15 +PNR,Pentair,99.79 +COO,Cooper Companies (The),82.84 +MAA,Mid-America Apartment Communities,136.74 +FFIV,F5 Inc.,282.22 +MAS,Masco,76.24 +IEX,IDEX Corporation,211.28 +MRNA,Moderna,40.21 +TRMB,Trimble Inc.,65.06 +ALLE,Allegion,179.40 +HII,Huntington Ingalls Industries,392.15 +CLX,Clorox,125.65 +CF,CF Industries,97.23 +GEN,Gen Digital,24.64 +AVY,Avery Dennison,192.77 +KIM,Kimco Realty,21.96 +HAS,Hasbro,105.26 +ERIE,Erie Indemnity,279.84 +TYL,Tyler Technologies,339.51 +UHS,Universal Health Services,231.22 +BEN,Franklin Resources,27.64 +ALGN,Align Technology,197.24 +SOLV,Solventum,81.25 +BBY,Best Buy,66.84 +REG,Regency Centers,76.49 +SWK,Stanley Black & Decker,90.31 +BF.B,Brown–Forman,30.11 +BLDR,Builders FirstSource,125.44 +HST,Host Hotels & Resorts,19.98 +AKAM,Akamai Technologies,95.00 +EG,Everest Group,331.67 +UDR,UDR Inc.,39.84 +TTD,The Trade Desk Inc.,27.19 +HRL,Hormel Foods,23.79 +DPZ,Domino's,385.50 +ZBRA,Zebra Technologies,250.71 +GNRC,Generac,214.94 +FOX,Fox Corporation (Class B),56.02 +FOXA,Fox Corporation (Class A),61.76 +GDDY,GoDaddy,91.44 +PSKY,Paramount Skydance Corp,10.96 +WYNN,Wynn Resorts,115.18 +JKHY,Jack Henry & Associates,165.63 +CPT,Camden Property Trust,111.66 +DOC,Healthpeak Properties,16.95 +SJM,J.M. Smucker Company (The),109.98 +IVZ,Invesco,26.42 +AES,AES Corporation,16.45 +IT,Gartner,160.21 +GL,Globe Life,144.35 +BAX,Baxter International,22.30 +PNW,Pinnacle West,95.57 +RVTY,Revvity,100.89 +AOS,A. O. Smith,80.03 +AIZ,Assurant,216.77 +TAP,Molson Coors Beverage Company,52.98 +NCLH,Norwegian Cruise Line Holdings,22.76 +POOL,Pool Corporation,270.70 +EPAM,EPAM Systems,180.79 +APA,APA Corporation,28.11 +TECH,Bio-Techne,63.46 +MOS,Mosaic Company (The),31.21 +BXP,BXP Inc.,61.61 +DVA,DaVita,144.55 +SWKS,Skyworks Solutions,63.62 +HSIC,Henry Schein,81.21 +CAG,Conagra Brands,19.95 +MGM,MGM Resorts,36.39 +ARE,Alexandria Real Estate Equities,53.92 +FRT,Federal Realty Investment Trust,107.14 +CPB,Campbell Soup Company,29.16 +NWSA,News Corp (Class A),23.30 +CRL,Charles River Laboratories,164.83 +MTCH,Match Group,31.27 +FDS,FactSet,193.29 +LW,Lamb Weston,50.33 +PAYC,Paycom,117.68 +MOH,Molina Healthcare,122.46 +NWS,News Corp (Class B),26.91 From f7a132fc03a4e4d1e241b75938cb3ded37ae3d55 Mon Sep 17 00:00:00 2001 From: Are Lodgaard Date: Mon, 11 May 2026 14:13:24 +0200 Subject: [PATCH 023/142] Further work on implementing the views to use BorderPane --- .../gruppe53/controller/ViewController.java | 2 +- .../no/ntnu/gruppe53/views/PortfolioView.java | 116 ++++++++++-------- 2 files changed, 68 insertions(+), 50 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java index f33de22..3c91d5a 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java @@ -29,7 +29,7 @@ public class ViewController { */ public ViewController(Stage stage) { this.stage = stage; - this.scene = new Scene(new javafx.scene.layout.StackPane(), 800, 600); + this.scene = new Scene(new javafx.scene.layout.BorderPane(), 800, 600); stage.setScene(scene); } diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index 94a7b7b..d53de1c 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -23,21 +23,21 @@ public class PortfolioView extends VBox { private final BorderPane borderPane; //Buttons - private final Button sellButton; + //private final Button sellButton; private Button marketButton; private Button portfolioButton; //Static Labels - private final Label viewLabel; + private Label viewLabel; //Dynamic Labels - private final Label playerNameLabel; - private final Label netWorthLabel; + private Label playerNameLabel; + private Label netWorthLabel; - private final ListView portfolioListView; + private ListView portfolioListView; - private final Spinner quantitySpinner; + //private final Spinner quantitySpinner; private Player player; @@ -51,14 +51,63 @@ public PortfolioView() { this.borderPane = new BorderPane(); this.borderPane.setTop(navigationBar()); + this.borderPane.setCenter(centerPane()); - setSpacing(12); - setPadding(new Insets(16)); + /* + HBox sellBar = new HBox(10); + Label quantityLabel = new Label("Quantity:"); + quantitySpinner = new Spinner<>(); + quantitySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1_000_000, 1)); + quantitySpinner.setEditable(true); + + sellButton = new Button("Sell"); + sellBar.getChildren().addAll(quantityLabel, quantitySpinner, sellButton); + + */ + + + + + + + /* + getChildren().addAll( + + viewLabel, + playerNameLabel, + netWorthLabel, + portfolioListView, + sellBar + ); + */ + + } + + private HBox navigationBar() { + HBox hBox = new HBox(); + //Sets simple styling + hBox.setPadding(new Insets(15, 12, 15, 12)); + hBox.setSpacing(10); + hBox.setStyle("-fx-background-color: #336699;"); + + marketButton = new Button("Market"); + portfolioButton = new Button("Portfolio"); + + hBox.getChildren().addAll(marketButton, portfolioButton); + + + return hBox; + } + + private VBox centerPane() { + VBox vBox = new VBox(); + vBox.setPadding(new Insets(15, 12, 15, 12)); + vBox.setSpacing(10); viewLabel = new Label("Portfolio"); viewLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); @@ -76,23 +125,11 @@ protected void updateItem(Share item, boolean empty) { } }); - HBox sellBar = new HBox(10); - Label quantityLabel = new Label("Quantity:"); - quantitySpinner = new Spinner<>(); - quantitySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1_000_000, 1)); - quantitySpinner.setEditable(true); - - sellButton = new Button("Sell"); - sellBar.getChildren().addAll(quantityLabel, quantitySpinner, sellButton); - - marketButton.setOnAction(event -> { - if (showMarketHandler != null) { - showMarketHandler.run(); - } - }); - + return vBox; + } + /*public void onSellButton(Runnable action) { sellButton.setOnAction(event -> { if (sellHandler == null) { return; @@ -106,35 +143,16 @@ protected void updateItem(Share item, boolean empty) { int quantity = quantitySpinner.getValue(); sellHandler.accept(selectedShare, quantity); }); + }*/ - getChildren().addAll( - - viewLabel, - playerNameLabel, - netWorthLabel, - portfolioListView, - sellBar - ); + public void onMarketButton(Runnable action) { + marketButton.setOnAction(event -> { + if (showMarketHandler != null) { + showMarketHandler.run(); + } + }); } -private HBox navigationBar() { - - HBox hBox = new HBox(); - - //Sets simple styling - hBox.setPadding(new Insets(15, 12, 15, 12)); - hBox.setSpacing(10); - hBox.setStyle("-fx-background-color: #336699;"); - - marketButton = new Button("Market"); - portfolioButton = new Button("Portfolio"); - - hBox.getChildren().addAll(marketButton, portfolioButton); - - - return hBox; -} - public void setShowMarketHandler(Runnable showMarketHandler) { this.showMarketHandler = showMarketHandler; } From 29c8951467f30ee3cac47945f77eecdf2aca08e4 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Mon, 11 May 2026 20:01:09 +0200 Subject: [PATCH 024/142] Further implementation of Borderpane into purchase and portfolioview --- millions/millions.iml | 3 - .../gruppe53/controller/GameController.java | 4 +- .../gruppe53/controller/ViewController.java | 2 +- .../no/ntnu/gruppe53/views/PortfolioView.java | 67 ++++++-------- .../no/ntnu/gruppe53/views/PurchaseView.java | 91 +++++++++++++++---- .../compile/default-compile/createdFiles.lst | 6 ++ .../compile/default-compile/inputFiles.lst | 40 ++++---- 7 files changed, 130 insertions(+), 83 deletions(-) diff --git a/millions/millions.iml b/millions/millions.iml index f23133a..abe781f 100644 --- a/millions/millions.iml +++ b/millions/millions.iml @@ -5,7 +5,4 @@ - - \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index e5174a6..1676a09 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -42,10 +42,10 @@ public void startNewGame() { portfolioView.setShowMarketHandler(this::showPurchaseView); portfolioView.setSellHandler(this::sellShare); - vm.addView("purchase", purchaseView); + //vm.addView("purchase", purchaseView); vm.addView("portfolio", portfolioView); - vm.switchView("purchase"); + vm.switchView("portfolio"); } private void purchaseStock(String symbol, int quantity) { diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java index 3c91d5a..0f54d78 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java @@ -56,6 +56,6 @@ public void switchView(String name) { throw new IllegalArgumentException("No views found matching: " + name); } - scene.setRoot(view); + scene.setRoot(view.getRoot()); } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index d53de1c..faefba5 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -23,7 +23,9 @@ public class PortfolioView extends VBox { private final BorderPane borderPane; //Buttons - //private final Button sellButton; + private Button sellButton; + + //NavigationBar Buttons private Button marketButton; private Button portfolioButton; @@ -34,13 +36,16 @@ public class PortfolioView extends VBox { private Label playerNameLabel; private Label netWorthLabel; - + //PortfolioListView private ListView portfolioListView; - //private final Spinner quantitySpinner; - + //Input + private Spinner quantitySpinner; + //Models private Player player; + + //Handlers private BiConsumer sellHandler; private Runnable showMarketHandler; @@ -53,37 +58,6 @@ public PortfolioView() { this.borderPane.setTop(navigationBar()); this.borderPane.setCenter(centerPane()); - - - - /* - HBox sellBar = new HBox(10); - Label quantityLabel = new Label("Quantity:"); - quantitySpinner = new Spinner<>(); - quantitySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1_000_000, 1)); - quantitySpinner.setEditable(true); - - sellButton = new Button("Sell"); - sellBar.getChildren().addAll(quantityLabel, quantitySpinner, sellButton); - - */ - - - - - - - /* - getChildren().addAll( - - viewLabel, - playerNameLabel, - netWorthLabel, - portfolioListView, - sellBar - ); - */ - } private HBox navigationBar() { @@ -125,11 +99,26 @@ protected void updateItem(Share item, boolean empty) { } }); + HBox sellBar = new HBox(10); + Label quantityLabel = new Label("Quantity:"); + quantitySpinner = new Spinner<>(); + quantitySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1_000_000, 1)); + quantitySpinner.setEditable(true); + + sellButton = new Button("Sell"); + sellBar.getChildren().addAll(quantityLabel, quantitySpinner, sellButton); + + + vBox.getChildren().addAll(viewLabel, + playerNameLabel, + netWorthLabel, + portfolioListView, + sellBar); return vBox; } - /*public void onSellButton(Runnable action) { + public void onSellButton(Runnable action) { sellButton.setOnAction(event -> { if (sellHandler == null) { return; @@ -143,7 +132,7 @@ protected void updateItem(Share item, boolean empty) { int quantity = quantitySpinner.getValue(); sellHandler.accept(selectedShare, quantity); }); - }*/ + } public void onMarketButton(Runnable action) { marketButton.setOnAction(event -> { @@ -193,4 +182,8 @@ private String formatShare(Share share) { private String formatMoney(BigDecimal money) { return money == null ? "0.00" : money.setScale(2, RoundingMode.HALF_UP).toPlainString(); } + + public BorderPane getRoot() { + return this.borderPane; + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java index d27610d..3969b88 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java @@ -12,6 +12,7 @@ import javafx.scene.control.ListView; import javafx.scene.control.Spinner; import javafx.scene.control.SpinnerValueFactory; +import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import no.ntnu.gruppe53.Exchange; @@ -19,39 +20,51 @@ import no.ntnu.gruppe53.Stock; public class PurchaseView extends VBox { - private final Label playerMoneyLabel; - private final ListView stocksListView; - private final Spinner quantitySpinner; - private final Button purchaseButton; - private final Button portfolioButton; + //Main pane of view + private BorderPane borderPane; + + //Buttons + private Button purchaseButton; + + //NavigationBar Buttons + private Button marketButton; + private Button portfolioButton; + + //Dynamic Labels + private Label playerMoneyLabel; + + + //StockListView + private ListView stocksListView; + + //Input + private Spinner quantitySpinner; + + //Models private Exchange exchange; private Player player; + + // Handlers private BiConsumer purchaseHandler; private Runnable showPortfolioHandler; + + public PurchaseView() { + + this.borderPane = new BorderPane(); + + this.borderPane.setTop(navigationBar()); + this.borderPane.setCenter(centerPane()); + + setSpacing(12); setPadding(new Insets(16)); - HBox navigationBar = new HBox(10); - Button purchaseViewButton = new Button("Market"); - portfolioButton = new Button("Portfolio"); - navigationBar.getChildren().addAll(purchaseViewButton, portfolioButton); - Label titleLabel = new Label("Stock Market"); - titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); - playerMoneyLabel = new Label("Money: $0.00"); - Label stocksTitleLabel = new Label("Available stocks:"); - stocksListView = new ListView<>(); - stocksListView.setPrefHeight(300); - - Label quantityLabel = new Label("Quantity:"); - quantitySpinner = new Spinner<>(); - quantitySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1_000_000, 1)); - quantitySpinner.setEditable(true); purchaseButton = new Button("Purchase"); purchaseButton.setOnAction(event -> { @@ -88,6 +101,44 @@ public PurchaseView() { ); } + + public HBox navigationBar() { + + HBox hBox = new HBox(); + + hBox.setPadding(new Insets(15, 12, 15, 12)); + hBox.setSpacing(10); + hBox.setStyle("-fx-background-color: #336699;"); + + marketButton = new Button("Market"); + portfolioButton = new Button("Portfolio"); + + hBox.getChildren().addAll(marketButton, portfolioButton); + + return hBox; + } + + public VBox centerPane() { + + VBox vBox = new VBox(); + + Label titleLabel = new Label("Stock Market"); + titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); + + playerMoneyLabel = new Label("Money: $0.00"); + Label stocksTitleLabel = new Label("Available stocks:"); + + stocksListView = new ListView<>(); + stocksListView.setPrefHeight(300); + + Label quantityLabel = new Label("Quantity:"); + quantitySpinner = new Spinner<>(); + quantitySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1_000_000, 1)); + quantitySpinner.setEditable(true); + + return vBox; + } + public void setPurchaseHandler(BiConsumer purchaseHandler) { this.purchaseHandler = purchaseHandler; } diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index eedb63b..cde6c91 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,8 +1,14 @@ +no/ntnu/gruppe53/TransactionArchive.class no/ntnu/gruppe53/FileHandler.class +no/ntnu/gruppe53/Exchange.class +no/ntnu/gruppe53/Sale.class no/ntnu/gruppe53/controller/ViewController.class +no/ntnu/gruppe53/Transaction.class no/ntnu/gruppe53/controller/GameController.class +no/ntnu/gruppe53/Player.class no/ntnu/gruppe53/views/PortfolioView.class no/ntnu/gruppe53/controller/StartViewController.class no/ntnu/gruppe53/views/PurchaseView.class no/ntnu/gruppe53/views/StartView.class no/ntnu/gruppe53/views/PortfolioView$1.class +no/ntnu/gruppe53/Purchase.class diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index 0a05fbe..f1579c1 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -1,20 +1,20 @@ -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/App.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Exchange.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Player.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Portfolio.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Purchase.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Sale.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/SaleCalculator.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Share.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Stock.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Transaction.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/TransactionArchive.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/TransactionCalculator.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java -/home/arelod/Documents/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/App.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Exchange.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Player.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Portfolio.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Purchase.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Sale.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/SaleCalculator.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Share.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Stock.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Transaction.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/TransactionArchive.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/TransactionCalculator.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java From 019d2ce366be36c4fde7b854dc29b9e602e9119b Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Tue, 12 May 2026 10:19:39 +0200 Subject: [PATCH 025/142] Gaaaaah --- .../no/ntnu/gruppe53/views/PurchaseView.java | 64 +++++++++++-------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java index 3969b88..a629574 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java @@ -66,39 +66,13 @@ public PurchaseView() { - purchaseButton = new Button("Purchase"); - purchaseButton.setOnAction(event -> { - if (purchaseHandler == null) { - return; - } - String selectedStock = getSelectedSymbol(); - if (selectedStock == null) { - return; - } - int quantity = quantitySpinner.getValue(); - purchaseHandler.accept(selectedStock, quantity); - }); - portfolioButton.setOnAction(event -> { - if (showPortfolioHandler != null) { - showPortfolioHandler.run(); - } - }); - getChildren().addAll( - navigationBar, - titleLabel, - playerMoneyLabel, - stocksTitleLabel, - stocksListView, - quantityLabel, - quantitySpinner, - purchaseButton - ); + } @@ -136,9 +110,45 @@ public VBox centerPane() { quantitySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1_000_000, 1)); quantitySpinner.setEditable(true); + purchaseButton = new Button("Purchase"); + + vBox.getChildren().addAll(titleLabel, + playerMoneyLabel, + stocksTitleLabel, + stocksListView, + quantityLabel, + quantitySpinner, + purchaseButton); + return vBox; } + public void onPortfolioButton(Runnable action) { + portfolioButton.setOnAction(event -> { + + action.run(); + if (showPortfolioHandler != null) { + showPortfolioHandler.run(); + } + }); + } + + public void onPurchaseButton(Runnable action) { + purchaseButton.setOnAction(event -> { + if (purchaseHandler == null) { + return; + } + + String selectedStock = getSelectedSymbol(); + if (selectedStock == null) { + return; + } + + int quantity = quantitySpinner.getValue(); + purchaseHandler.accept(selectedStock, quantity); + }); + } + public void setPurchaseHandler(BiConsumer purchaseHandler) { this.purchaseHandler = purchaseHandler; } From 458e6ea27de6cc9643f363c75e5223ed9a7614d3 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Wed, 13 May 2026 13:29:53 +0200 Subject: [PATCH 026/142] Further testing --- .../main/java/no/ntnu/gruppe53/controller/ViewController.java | 2 +- .../src/main/java/no/ntnu/gruppe53/views/PortfolioView.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java index 0f54d78..3c91d5a 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java @@ -56,6 +56,6 @@ public void switchView(String name) { throw new IllegalArgumentException("No views found matching: " + name); } - scene.setRoot(view.getRoot()); + scene.setRoot(view); } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index faefba5..cc3e33d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -16,7 +16,7 @@ import no.ntnu.gruppe53.Player; import no.ntnu.gruppe53.Share; -public class PortfolioView extends VBox { +public class PortfolioView extends BorderPane { //Main pane of view From 47850822088b3dd79b8d54831fdfb4cd93e20df4 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Thu, 14 May 2026 15:50:40 +0200 Subject: [PATCH 027/142] Fixed a regression that displayed blank when switching scenes in the new borderpane --- .../gruppe53/controller/GameController.java | 1 + .../no/ntnu/gruppe53/views/PortfolioView.java | 12 +++++----- .../no/ntnu/gruppe53/views/PurchaseView.java | 22 ++++--------------- 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index 1676a09..ec8c92c 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -44,6 +44,7 @@ public void startNewGame() { //vm.addView("purchase", purchaseView); vm.addView("portfolio", portfolioView); + vm.addView("purchase", purchaseView); vm.switchView("portfolio"); } diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index cc3e33d..0c6a186 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -20,7 +20,7 @@ public class PortfolioView extends BorderPane { //Main pane of view - private final BorderPane borderPane; + //Buttons private Button sellButton; @@ -53,10 +53,10 @@ public class PortfolioView extends BorderPane { public PortfolioView() { - this.borderPane = new BorderPane(); - this.borderPane.setTop(navigationBar()); - this.borderPane.setCenter(centerPane()); + + this.setTop(navigationBar()); + this.setCenter(centerPane()); } @@ -183,7 +183,5 @@ private String formatMoney(BigDecimal money) { return money == null ? "0.00" : money.setScale(2, RoundingMode.HALF_UP).toPlainString(); } - public BorderPane getRoot() { - return this.borderPane; - } + } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java index a629574..f5a6955 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java @@ -19,10 +19,10 @@ import no.ntnu.gruppe53.Player; import no.ntnu.gruppe53.Stock; -public class PurchaseView extends VBox { +public class PurchaseView extends BorderPane { //Main pane of view - private BorderPane borderPane; + //Buttons private Button purchaseButton; @@ -53,24 +53,10 @@ public class PurchaseView extends VBox { public PurchaseView() { - this.borderPane = new BorderPane(); - - this.borderPane.setTop(navigationBar()); - this.borderPane.setCenter(centerPane()); - - - setSpacing(12); - setPadding(new Insets(16)); - - - - - - - - + this.setTop(navigationBar()); + this.setCenter(centerPane()); } From e7a3c5f7d1e8cd80148ea24a2b4c95326eca6c24 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Thu, 14 May 2026 16:12:50 +0200 Subject: [PATCH 028/142] Began implementing a smoother button implementation, with a cleaner sepearation between view and controller --- .../gruppe53/controller/GameController.java | 51 +++++++++++++------ .../{PurchaseView.java => MarketView.java} | 18 ++----- .../no/ntnu/gruppe53/views/PortfolioView.java | 7 +-- .../compile/default-compile/createdFiles.lst | 2 +- .../compile/default-compile/inputFiles.lst | 2 +- 5 files changed, 43 insertions(+), 37 deletions(-) rename millions/src/main/java/no/ntnu/gruppe53/views/{PurchaseView.java => MarketView.java} (92%) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index ec8c92c..b23ac41 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -9,7 +9,7 @@ import no.ntnu.gruppe53.Share; import no.ntnu.gruppe53.Stock; import no.ntnu.gruppe53.views.PortfolioView; -import no.ntnu.gruppe53.views.PurchaseView; +import no.ntnu.gruppe53.views.MarketView; public class GameController { private final ViewController vm; @@ -18,7 +18,7 @@ public class GameController { private Player player; private Exchange exchange; private List stocks; - private PurchaseView purchaseView; + private MarketView marketView; private PortfolioView portfolioView; public GameController(ViewController vm) { @@ -30,23 +30,25 @@ public void startNewGame() { stocks = fileHandler.readStocksFromFile("src/main/resources", "sp500.csv"); exchange = new Exchange("Millions Exchange", stocks); - purchaseView = new PurchaseView(); - purchaseView.setPlayer(player); - purchaseView.setExchange(exchange); - purchaseView.setPurchaseHandler(this::purchaseStock); + marketView = new MarketView(); + marketView.setPlayer(player); + marketView.setExchange(exchange); + marketView.setPurchaseHandler(this::purchaseStock); portfolioView = new PortfolioView(); portfolioView.setPlayer(player); - purchaseView.setShowPortfolioHandler(this::showPortfolio); + //marketView.setShowPortfolioHandler(this::showPortfolio); portfolioView.setShowMarketHandler(this::showPurchaseView); portfolioView.setSellHandler(this::sellShare); //vm.addView("purchase", purchaseView); vm.addView("portfolio", portfolioView); - vm.addView("purchase", purchaseView); + vm.addView("market", marketView); - vm.switchView("portfolio"); + initialize(); + + vm.switchView("market"); } private void purchaseStock(String symbol, int quantity) { @@ -56,8 +58,8 @@ private void purchaseStock(String symbol, int quantity) { try { exchange.buy(symbol, BigDecimal.valueOf(quantity), player); - purchaseView.setPlayer(player); - purchaseView.refreshStocks(); + marketView.setPlayer(player); + marketView.refreshStocks(); portfolioView.setPlayer(player); } catch (RuntimeException ex) { System.out.println("Purchase failed: " + ex.getMessage()); @@ -77,8 +79,8 @@ private void sellShare(Share share, int quantity) { ); exchange.sell(shareToSell, player); - purchaseView.setPlayer(player); - purchaseView.refreshStocks(); + marketView.setPlayer(player); + marketView.refreshStocks(); portfolioView.setPlayer(player); } catch (RuntimeException ex) { System.out.println("Sell failed: " + ex.getMessage()); @@ -93,10 +95,27 @@ public void showPortfolio() { } public void showPurchaseView() { - if (purchaseView != null) { - purchaseView.setPlayer(player); - purchaseView.refreshStocks(); + if (marketView != null) { + marketView.setPlayer(player); + marketView.refreshStocks(); vm.switchView("purchase"); } } + + private void initialize() { + + //portfolio actions + portfolioView.onMarketButton(() -> { + vm.switchView("market"); + }); + + + //market actions + marketView.onPortfolioButton(() -> { + vm.switchView("portfolio"); + }); + + + + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java similarity index 92% rename from millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java rename to millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java index f5a6955..ab1047a 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java @@ -19,7 +19,7 @@ import no.ntnu.gruppe53.Player; import no.ntnu.gruppe53.Stock; -public class PurchaseView extends BorderPane { +public class MarketView extends BorderPane { //Main pane of view @@ -51,7 +51,7 @@ public class PurchaseView extends BorderPane { - public PurchaseView() { + public MarketView() { @@ -110,15 +110,10 @@ public VBox centerPane() { } public void onPortfolioButton(Runnable action) { - portfolioButton.setOnAction(event -> { - - action.run(); - if (showPortfolioHandler != null) { - showPortfolioHandler.run(); - } - }); + portfolioButton.setOnAction( e -> action.run()); } + /* public void onPurchaseButton(Runnable action) { purchaseButton.setOnAction(event -> { if (purchaseHandler == null) { @@ -133,15 +128,12 @@ public void onPurchaseButton(Runnable action) { int quantity = quantitySpinner.getValue(); purchaseHandler.accept(selectedStock, quantity); }); - } + }*/ public void setPurchaseHandler(BiConsumer purchaseHandler) { this.purchaseHandler = purchaseHandler; } - public void setShowPortfolioHandler(Runnable showPortfolioHandler) { - this.showPortfolioHandler = showPortfolioHandler; - } public void setPlayer(Player player) { this.player = player; diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index 0c6a186..724ccde 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -54,7 +54,6 @@ public class PortfolioView extends BorderPane { public PortfolioView() { - this.setTop(navigationBar()); this.setCenter(centerPane()); @@ -135,11 +134,7 @@ public void onSellButton(Runnable action) { } public void onMarketButton(Runnable action) { - marketButton.setOnAction(event -> { - if (showMarketHandler != null) { - showMarketHandler.run(); - } - }); + marketButton.setOnAction(e -> action.run()); } public void setShowMarketHandler(Runnable showMarketHandler) { diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index cde6c91..e5cc627 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -8,7 +8,7 @@ no/ntnu/gruppe53/controller/GameController.class no/ntnu/gruppe53/Player.class no/ntnu/gruppe53/views/PortfolioView.class no/ntnu/gruppe53/controller/StartViewController.class -no/ntnu/gruppe53/views/PurchaseView.class no/ntnu/gruppe53/views/StartView.class no/ntnu/gruppe53/views/PortfolioView$1.class no/ntnu/gruppe53/Purchase.class +no/ntnu/gruppe53/views/MarketView.class diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index f1579c1..47de60f 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -15,6 +15,6 @@ /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/PurchaseView.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java From c81074e493b71d53e2ad4590148318f3c47bfe62 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Thu, 14 May 2026 17:31:39 +0200 Subject: [PATCH 029/142] Reworked Purchase design and controller --- .../gruppe53/controller/GameController.java | 82 +++++++++++-------- .../no/ntnu/gruppe53/views/MarketView.java | 56 +++++++++++-- 2 files changed, 96 insertions(+), 42 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index b23ac41..502bae1 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -1,6 +1,7 @@ package no.ntnu.gruppe53.controller; import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.List; import no.ntnu.gruppe53.Exchange; @@ -33,7 +34,7 @@ public void startNewGame() { marketView = new MarketView(); marketView.setPlayer(player); marketView.setExchange(exchange); - marketView.setPurchaseHandler(this::purchaseStock); + //marketView.setPurchaseHandler(this::purchaseStock); portfolioView = new PortfolioView(); portfolioView.setPlayer(player); @@ -51,41 +52,9 @@ public void startNewGame() { vm.switchView("market"); } - private void purchaseStock(String symbol, int quantity) { - if (exchange == null || player == null || symbol == null || quantity <= 0) { - return; - } - try { - exchange.buy(symbol, BigDecimal.valueOf(quantity), player); - marketView.setPlayer(player); - marketView.refreshStocks(); - portfolioView.setPlayer(player); - } catch (RuntimeException ex) { - System.out.println("Purchase failed: " + ex.getMessage()); - } - } - - private void sellShare(Share share, int quantity) { - if (exchange == null || player == null || share == null || quantity <= 0) { - return; - } - try { - Share shareToSell = new Share( - share.getStock(), - BigDecimal.valueOf(quantity), - share.getPurchasePrice() - ); - exchange.sell(shareToSell, player); - marketView.setPlayer(player); - marketView.refreshStocks(); - portfolioView.setPlayer(player); - } catch (RuntimeException ex) { - System.out.println("Sell failed: " + ex.getMessage()); - } - } public void showPortfolio() { if (portfolioView != null) { @@ -113,9 +82,56 @@ private void initialize() { //market actions marketView.onPortfolioButton(() -> { vm.switchView("portfolio"); + portfolioView.refresh(); + }); + + marketView.onStockSelection(newValue ->{ + marketView.setSelectedStock(newValue); + }); + + marketView.onPurchaseButton(() -> { + purchaseStock(marketView.getSelectedSymbol(), marketView.getQuantityPurchase()); }); } + + private void purchaseStock(String stockSymbol, int quantity) { + if (exchange == null || player == null || stockSymbol == null || quantity <= 0) { + return; + } + + try { + exchange.buy(stockSymbol, BigDecimal.valueOf(quantity), player); + marketView.setPlayer(player); + marketView.refreshStocks(); + portfolioView.setPlayer(player); + } catch (RuntimeException ex) { + System.out.println("Purchase failed: " + ex.getMessage()); + } + } + + private void sellShare(Share share, int quantity) { + if (exchange == null || player == null || share == null || quantity <= 0) { + return; + } + + try { + Share shareToSell = new Share( + share.getStock(), + BigDecimal.valueOf(quantity), + share.getPurchasePrice() + ); + + exchange.sell(shareToSell, player); + marketView.setPlayer(player); + marketView.refreshStocks(); + portfolioView.setPlayer(player); + } catch (RuntimeException ex) { + System.out.println("Sell failed: " + ex.getMessage()); + } + } + + } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java index ab1047a..33f4b9d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java @@ -4,6 +4,7 @@ import java.math.RoundingMode; import java.util.List; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.stream.Collectors; import javafx.geometry.Insets; @@ -31,8 +32,9 @@ public class MarketView extends BorderPane { private Button marketButton; private Button portfolioButton; - //Dynamic Labels + //Dynamic Labels/Text private Label playerMoneyLabel; + private Label selectedStock; //StockListView @@ -91,6 +93,17 @@ public VBox centerPane() { stocksListView = new ListView<>(); stocksListView.setPrefHeight(300); + HBox selectedStockBox = new HBox(); + + Label selectedStockLabel = new Label("Selected Stock:"); + selectedStock = new Label(""); + + selectedStockBox.getChildren().addAll(selectedStockLabel, selectedStock); + + HBox purchaseBox = new HBox(); + + + Label quantityLabel = new Label("Quantity:"); quantitySpinner = new Spinner<>(); quantitySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1_000_000, 1)); @@ -98,13 +111,14 @@ public VBox centerPane() { purchaseButton = new Button("Purchase"); + purchaseBox.getChildren().addAll(quantityLabel, quantitySpinner, purchaseButton); + vBox.getChildren().addAll(titleLabel, playerMoneyLabel, stocksTitleLabel, stocksListView, - quantityLabel, - quantitySpinner, - purchaseButton); + selectedStockBox, + purchaseBox); return vBox; } @@ -113,6 +127,20 @@ public void onPortfolioButton(Runnable action) { portfolioButton.setOnAction( e -> action.run()); } + public void onPurchaseButton(Runnable action) { + purchaseButton.setOnAction(e -> action.run()); + } + + public void onStockSelection(Consumer action) { + stocksListView.getSelectionModel() + .selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + action.accept(newValue); + } + }); + } + /* public void onPurchaseButton(Runnable action) { purchaseButton.setOnAction(event -> { @@ -169,14 +197,24 @@ public void refreshStocks() { ); } - private String getSelectedSymbol() { - String selectedItem = stocksListView.getSelectionModel().getSelectedItem(); - if (selectedItem == null || selectedItem.isBlank()) { + public String getSelectedSymbol() { + String selected = stocksListView.getSelectionModel().getSelectedItem(); + if (selected == null || selected.isBlank()) { return null; } - int separatorIndex = selectedItem.indexOf(" - "); - return separatorIndex > 0 ? selectedItem.substring(0, separatorIndex) : null; + int indexEnd = selected.indexOf("-"); + + + return selected.substring(0,indexEnd - 1 ); + } + + public int getQuantityPurchase() { + return quantitySpinner.getValue(); + } + + public void setSelectedStock(String selectedStockString) { + selectedStock.setText(selectedStockString); } private String formatStock(Stock stock) { From 9f9a2fd867e0fc45c9b154fae1a9f01d177f586f Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Thu, 14 May 2026 22:14:22 +0200 Subject: [PATCH 030/142] Expanded on the Market, purchase information --- .../no/ntnu/gruppe53/PurchaseCalculator.java | 2 +- .../gruppe53/controller/GameController.java | 39 +++++++++++--- .../no/ntnu/gruppe53/views/MarketView.java | 53 +++++++++++++++---- 3 files changed, 76 insertions(+), 18 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java index 8620cb5..8843ec6 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java @@ -60,7 +60,7 @@ public BigDecimal calculateTax() { @Override public BigDecimal calculateTotal() { - return calculateGross().add(calculateCommission()).add(calculateTax()); + return calculateGross().add(calculateCommission()).add(calculateTax()).setScale(2); } diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index 502bae1..dc5b601 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -4,11 +4,7 @@ import java.math.RoundingMode; import java.util.List; -import no.ntnu.gruppe53.Exchange; -import no.ntnu.gruppe53.FileHandler; -import no.ntnu.gruppe53.Player; -import no.ntnu.gruppe53.Share; -import no.ntnu.gruppe53.Stock; +import no.ntnu.gruppe53.*; import no.ntnu.gruppe53.views.PortfolioView; import no.ntnu.gruppe53.views.MarketView; @@ -21,6 +17,7 @@ public class GameController { private List stocks; private MarketView marketView; private PortfolioView portfolioView; + private PurchaseCalculator purchaseCalculator; public GameController(ViewController vm) { this.vm = vm; @@ -86,13 +83,29 @@ private void initialize() { }); marketView.onStockSelection(newValue ->{ - marketView.setSelectedStock(newValue); + purchaseCalculator = new PurchaseCalculator(getSelectedShare()); + + marketView.selectedStock.setText(newValue); + marketView.gross.setText(purchaseCalculator.calculateGross().toString()); + marketView.commission.setText(purchaseCalculator.calculateCommission().toString()); + marketView.total.setText(purchaseCalculator.calculateTotal().toString()); }); marketView.onPurchaseButton(() -> { purchaseStock(marketView.getSelectedSymbol(), marketView.getQuantityPurchase()); }); + marketView.onQuantitySelect(() -> { + //if no share selected need to be implemented + purchaseCalculator = new PurchaseCalculator(getSelectedShare()); + + marketView.gross.setText(purchaseCalculator.calculateGross().toString()); + marketView.commission.setText(purchaseCalculator.calculateCommission().toString()); + marketView.total.setText(purchaseCalculator.calculateTotal().toString()); + + + }); + } @@ -134,4 +147,18 @@ private void sellShare(Share share, int quantity) { } + //Create if no share selected + private Share getSelectedShare() { + Stock stock = exchange.getStock(marketView.getSelectedSymbol()); + BigDecimal quantity = BigDecimal.valueOf(marketView.getQuantityPurchase()); + BigDecimal purchasePrice = stock.getSalesPrice(); + + Share share = new Share(stock, quantity,purchasePrice); + + + + return share; + } + + } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java index 33f4b9d..18f06f7 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java @@ -8,11 +8,7 @@ import java.util.stream.Collectors; import javafx.geometry.Insets; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.ListView; -import javafx.scene.control.Spinner; -import javafx.scene.control.SpinnerValueFactory; +import javafx.scene.control.*; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; @@ -34,8 +30,10 @@ public class MarketView extends BorderPane { //Dynamic Labels/Text private Label playerMoneyLabel; - private Label selectedStock; - + public Label selectedStock; + public TextField gross; + public TextField commission; + public TextField total; //StockListView private ListView stocksListView; @@ -109,16 +107,41 @@ public VBox centerPane() { quantitySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1_000_000, 1)); quantitySpinner.setEditable(true); + HBox calculationBox = new HBox(); + + Label grossLabel = new Label("Gross: "); + gross = new TextField(""); + gross.setEditable(false); + gross.setMaxWidth(200); + Label commissionLabel = new Label("Commission: "); + commission = new TextField(""); + commission.setEditable(false); + commission.setMaxWidth(200); + + calculationBox.getChildren().addAll(grossLabel, gross, commissionLabel, commission); + + HBox totalBox = new HBox(); + + Label totalLabel = new Label("Total: "); + total = new TextField(""); + total.setEditable(false); + total.setMaxWidth(200); + + totalBox.getChildren().addAll(totalLabel, total); + purchaseButton = new Button("Purchase"); - purchaseBox.getChildren().addAll(quantityLabel, quantitySpinner, purchaseButton); + purchaseBox.getChildren().addAll(quantityLabel, quantitySpinner); vBox.getChildren().addAll(titleLabel, playerMoneyLabel, stocksTitleLabel, stocksListView, selectedStockBox, - purchaseBox); + purchaseBox, + calculationBox, + totalBox, + purchaseButton); return vBox; } @@ -141,6 +164,12 @@ public void onStockSelection(Consumer action) { }); } + public void onQuantitySelect(Runnable action) { + quantitySpinner.valueProperty().addListener((obs, oldValue, newValue) -> { + action.run(); + }); + } + /* public void onPurchaseButton(Runnable action) { purchaseButton.setOnAction(event -> { @@ -213,8 +242,10 @@ public int getQuantityPurchase() { return quantitySpinner.getValue(); } - public void setSelectedStock(String selectedStockString) { - selectedStock.setText(selectedStockString); + + + public void setCalculation() { + } private String formatStock(Stock stock) { From 1c112b27766488b49557ecbc119d8bc965d68fb8 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Fri, 22 May 2026 10:54:55 +0200 Subject: [PATCH 031/142] added logo --- .../java/no/ntnu/gruppe53/PurchaseCalculator.java | 3 ++- .../no/ntnu/gruppe53/views/PortfolioView.java | 1 + millions/src/main/resources/Millions-logo.png | Bin 0 -> 690 bytes 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 millions/src/main/resources/Millions-logo.png diff --git a/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java index 8843ec6..752b3f0 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java @@ -1,6 +1,7 @@ package no.ntnu.gruppe53; import java.math.BigDecimal; +import java.math.RoundingMode; /* PurchaseCalculator class: @@ -60,7 +61,7 @@ public BigDecimal calculateTax() { @Override public BigDecimal calculateTotal() { - return calculateGross().add(calculateCommission()).add(calculateTax()).setScale(2); + return calculateGross().add(calculateCommission()).add(calculateTax()); } diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index 724ccde..ec2408b 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -57,6 +57,7 @@ public PortfolioView() { this.setTop(navigationBar()); this.setCenter(centerPane()); + } private HBox navigationBar() { diff --git a/millions/src/main/resources/Millions-logo.png b/millions/src/main/resources/Millions-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..328546ffc4fb002d7e98513a4b18ceac838ea928 GIT binary patch literal 690 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV5;EC`zPI?B?a#uoV2`=@84WD^MW*-0g$M-};CC<(issRZ^z%y z&3mrBO!b9Vx}Rs@feAnM&perwbc3^mH*R@>W&Y+l^(EW}X`hyF$-KIfC->p5yp%J} zMV9%K**iD)F<$%g#Ju&z(){WUiKNB7D`#+2O$w4@6J3x{?BcsRULa^wTKEMq>xEOZ zN?19>SQx8*z`1Al%Ne Date: Fri, 22 May 2026 11:55:13 +0200 Subject: [PATCH 032/142] Added initial separation of NavigationBar --- .../gruppe53/controller/GameController.java | 10 +- .../no/ntnu/gruppe53/views/MarketView.java | 15 ++- .../ntnu/gruppe53/views/NavigationView.java | 63 ++++++++++ .../no/ntnu/gruppe53/views/PortfolioView.java | 14 ++- millions/target/classes/Millions-logo.png | Bin 0 -> 690 bytes .../compile/default-compile/createdFiles.lst | 1 + .../compile/default-compile/inputFiles.lst | 1 + .../default-testCompile/createdFiles.lst | 17 +-- .../default-testCompile/inputFiles.lst | 23 ++-- .../TEST-no.ntnu.gruppe53.AppTest.xml | 81 ++++++------ .../TEST-no.ntnu.gruppe53.ExchangeTest.xml | 103 ++++++++-------- .../TEST-no.ntnu.gruppe53.FileHandlerTest.xml | 84 +++++++++++++ .../TEST-no.ntnu.gruppe53.PlayerTest.xml | 115 +++++++++--------- .../TEST-no.ntnu.gruppe53.PortfolioTest.xml | 106 ++++++++-------- ...o.ntnu.gruppe53.PurchaseCalculatorTest.xml | 85 ++++++------- .../TEST-no.ntnu.gruppe53.PurchaseTest.xml | 93 +++++++------- ...o.ntnu.gruppe53.SaleCalculatorUnitTest.xml | 87 +++++++------ .../TEST-no.ntnu.gruppe53.SaleTest.xml | 97 +++++++-------- .../TEST-no.ntnu.gruppe53.ShareTest.xml | 95 +++++++-------- .../TEST-no.ntnu.gruppe53.StockTest.xml | 111 ++++++++--------- ...o.ntnu.gruppe53.TransactionArchiveTest.xml | 109 ++++++++--------- .../no.ntnu.gruppe53.AppTest.txt | 2 +- .../no.ntnu.gruppe53.ExchangeTest.txt | 2 +- .../no.ntnu.gruppe53.FileHandlerTest.txt | 4 + .../no.ntnu.gruppe53.PlayerTest.txt | 2 +- .../no.ntnu.gruppe53.PortfolioTest.txt | 2 +- ...o.ntnu.gruppe53.PurchaseCalculatorTest.txt | 2 +- .../no.ntnu.gruppe53.PurchaseTest.txt | 2 +- ...o.ntnu.gruppe53.SaleCalculatorUnitTest.txt | 2 +- .../no.ntnu.gruppe53.SaleTest.txt | 2 +- .../no.ntnu.gruppe53.ShareTest.txt | 2 +- .../no.ntnu.gruppe53.StockTest.txt | 2 +- ...o.ntnu.gruppe53.TransactionArchiveTest.txt | 2 +- 33 files changed, 724 insertions(+), 612 deletions(-) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java create mode 100644 millions/target/classes/Millions-logo.png create mode 100644 millions/target/surefire-reports/TEST-no.ntnu.gruppe53.FileHandlerTest.xml create mode 100644 millions/target/surefire-reports/no.ntnu.gruppe53.FileHandlerTest.txt diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index dc5b601..368773e 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -5,6 +5,7 @@ import java.util.List; import no.ntnu.gruppe53.*; +import no.ntnu.gruppe53.views.NavigationView; import no.ntnu.gruppe53.views.PortfolioView; import no.ntnu.gruppe53.views.MarketView; @@ -28,6 +29,8 @@ public void startNewGame() { stocks = fileHandler.readStocksFromFile("src/main/resources", "sp500.csv"); exchange = new Exchange("Millions Exchange", stocks); + + marketView = new MarketView(); marketView.setPlayer(player); marketView.setExchange(exchange); @@ -71,15 +74,16 @@ public void showPurchaseView() { private void initialize() { //portfolio actions - portfolioView.onMarketButton(() -> { + portfolioView.getNavigationView().onMarketButton(() -> { vm.switchView("market"); + portfolioView.refresh(); }); //market actions - marketView.onPortfolioButton(() -> { + marketView.getNavigationView().onPortfolioButton(() -> { vm.switchView("portfolio"); - portfolioView.refresh(); + }); marketView.onStockSelection(newValue ->{ diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java index 18f06f7..b9b8b77 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java @@ -15,11 +15,12 @@ import no.ntnu.gruppe53.Exchange; import no.ntnu.gruppe53.Player; import no.ntnu.gruppe53.Stock; +import no.ntnu.gruppe53.controller.GameController; public class MarketView extends BorderPane { //Main pane of view - + private final NavigationView navigationView; //Buttons private Button purchaseButton; @@ -53,9 +54,9 @@ public class MarketView extends BorderPane { public MarketView() { + navigationView = new NavigationView(); - - this.setTop(navigationBar()); + this.setTop(navigationView); this.setCenter(centerPane()); @@ -146,9 +147,7 @@ public VBox centerPane() { return vBox; } - public void onPortfolioButton(Runnable action) { - portfolioButton.setOnAction( e -> action.run()); - } + public void onPurchaseButton(Runnable action) { purchaseButton.setOnAction(e -> action.run()); @@ -259,4 +258,8 @@ private String formatStock(Stock stock) { private String formatMoney(BigDecimal money) { return money == null ? "0.00" : money.setScale(2, RoundingMode.HALF_UP).toPlainString(); } + + public NavigationView getNavigationView() { + return navigationView; + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java b/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java new file mode 100644 index 0000000..7f17fad --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java @@ -0,0 +1,63 @@ +package no.ntnu.gruppe53.views; + +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; + + +/* +Navigation View: + +Yellow Color, Hex: #ffe556 RGB(255, 229, 86) +Blue Color, Hex: #00bcf0 RGB(0, 188, 240) +Black Color, Hex: #303539 RGB(48, 53, 57) + + + + */ + +public class NavigationView extends HBox { + + //Navigation Buttons + public Button marketButton; + public Button portfolioButton; + + //Dynamic Labels + public Label networthLabel; + + public NavigationView() { + + this.setPadding(new Insets(15, 12, 15, 12)); + this.setSpacing(10); + //Blue Background color + this.setStyle("-fx-background-color: #00bcf0;"); + + + marketButton = new Button("Market"); + marketButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + portfolioButton = new Button("Portfolio"); + portfolioButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + + Label networthLabelText = new Label("Net Worth:"); + + networthLabel = new Label(); + + this.getChildren().addAll(marketButton, portfolioButton, spacer, networthLabelText, networthLabel); + + } + + public void onPortfolioButton(Runnable action) { + portfolioButton.setOnAction( e -> action.run()); + } + + public void onMarketButton(Runnable action) { + marketButton.setOnAction(e -> action.run()); + } +} diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index ec2408b..86bcc9e 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -15,12 +15,13 @@ import javafx.scene.layout.VBox; import no.ntnu.gruppe53.Player; import no.ntnu.gruppe53.Share; +import no.ntnu.gruppe53.controller.GameController; public class PortfolioView extends BorderPane { //Main pane of view - + private final NavigationView navigationView; //Buttons private Button sellButton; @@ -53,8 +54,9 @@ public class PortfolioView extends BorderPane { public PortfolioView() { + navigationView = new NavigationView(); - this.setTop(navigationBar()); + this.setTop(navigationView); this.setCenter(centerPane()); @@ -134,9 +136,7 @@ public void onSellButton(Runnable action) { }); } - public void onMarketButton(Runnable action) { - marketButton.setOnAction(e -> action.run()); - } + public void setShowMarketHandler(Runnable showMarketHandler) { this.showMarketHandler = showMarketHandler; @@ -179,5 +179,7 @@ private String formatMoney(BigDecimal money) { return money == null ? "0.00" : money.setScale(2, RoundingMode.HALF_UP).toPlainString(); } - + public NavigationView getNavigationView() { + return navigationView; + } } \ No newline at end of file diff --git a/millions/target/classes/Millions-logo.png b/millions/target/classes/Millions-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..328546ffc4fb002d7e98513a4b18ceac838ea928 GIT binary patch literal 690 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV5;EC`zPI?B?a#uoV2`=@84WD^MW*-0g$M-};CC<(issRZ^z%y z&3mrBO!b9Vx}Rs@feAnM&perwbc3^mH*R@>W&Y+l^(EW}X`hyF$-KIfC->p5yp%J} zMV9%K**iD)F<$%g#Ju&z(){WUiKNB7D`#+2O$w4@6J3x{?BcsRULa^wTKEMq>xEOZ zN?19>SQx8*z`1Al%Ne - + - - - - + + + - - - - + + - - - + + - + - - + + - - - - - + + + + + - - - + + - - - - - - + + + + + + - - - - - - + + + + + + + - - - + + - - - - + + + + - + \ No newline at end of file diff --git a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.ExchangeTest.xml b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.ExchangeTest.xml index ded3e63..1c74df5 100644 --- a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.ExchangeTest.xml +++ b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.ExchangeTest.xml @@ -1,73 +1,74 @@ - + - - - - + + + - - - - + + - - - + + - + - - + + - - - - - + + + + + - - - + + - - - - - - + + + + + + - - - - - - + + + + + + + - - - + + - - - - + + + + - - + + + + + + - - - - - + + + + + \ No newline at end of file diff --git a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.FileHandlerTest.xml b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.FileHandlerTest.xml new file mode 100644 index 0000000..ff2cc4d --- /dev/null +++ b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.FileHandlerTest.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PlayerTest.xml b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PlayerTest.xml index 62bf197..a361a04 100644 --- a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PlayerTest.xml +++ b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PlayerTest.xml @@ -1,78 +1,77 @@ - + - - - - + + + - - - - + + - - - + + - + - - + + - - - - - + + + + + - - - + + - - - - - - + + + + + + - - - - - - + + + + + + + - - - + + - - - - + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PortfolioTest.xml b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PortfolioTest.xml index 106edb9..45e96d3 100644 --- a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PortfolioTest.xml +++ b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PortfolioTest.xml @@ -1,76 +1,70 @@ - + - - - - + + + - - - - + + - - - + + - - + - - + + - - - - - + + + + + - - - + + - - - - - - + + + + + + - - - - - - + + + + + + + - - - + + - - - - + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + \ No newline at end of file diff --git a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PurchaseCalculatorTest.xml b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PurchaseCalculatorTest.xml index ae7b3d2..f702ef2 100644 --- a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PurchaseCalculatorTest.xml +++ b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PurchaseCalculatorTest.xml @@ -1,65 +1,60 @@ - + - - - - + + + - - - - + + - - - + + - + - - + + - - - - - + + + + + - - - + + - - - - - - + + + + + + - - - - - - + + + + + + + - - - + + - - - - + + + + - - - + + + \ No newline at end of file diff --git a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PurchaseTest.xml b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PurchaseTest.xml index 9aee314..0eda911 100644 --- a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PurchaseTest.xml +++ b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.PurchaseTest.xml @@ -1,69 +1,64 @@ - + - - - - + + + - - - - + + - - - + + - + - - + + - - - - - + + + + + - - - + + - - - - - - + + + + + + - - - - - - + + + + + + + - - - + + - - - - + + + + - - - - - - - + + + + + + + \ No newline at end of file diff --git a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.SaleCalculatorUnitTest.xml b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.SaleCalculatorUnitTest.xml index d408003..c299189 100644 --- a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.SaleCalculatorUnitTest.xml +++ b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.SaleCalculatorUnitTest.xml @@ -1,66 +1,61 @@ - + - - - - + + + - - - - + + - - - + + - + - - + + - - - - - + + + + + - - - + + - - - - - - + + + + + + - - - - - - + + + + + + + - - - + + - - - - + + + + - - - - + + + + \ No newline at end of file diff --git a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.SaleTest.xml b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.SaleTest.xml index 2a929ae..e187242 100644 --- a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.SaleTest.xml +++ b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.SaleTest.xml @@ -1,73 +1,68 @@ - + - - - - + + + - - - - + + - - - + + - + - - + + - - - - - + + + + + - - - + + - - - - - - + + + + + + - - - - - - + + + + + + + - - - + + - - - - + + + + - - - - - - - - - + + + + + + + + + diff --git a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.ShareTest.xml b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.ShareTest.xml index 3cc3fcd..ca94615 100644 --- a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.ShareTest.xml +++ b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.ShareTest.xml @@ -1,70 +1,65 @@ - + - - - - + + + - - - - + + - - - + + - + - - + + - - - - - + + + + + - - - + + - - - - - - + + + + + + - - - - - - + + + + + + + - - - + + - - - - + + + + - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.StockTest.xml b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.StockTest.xml index b2487c3..cc79c0b 100644 --- a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.StockTest.xml +++ b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.StockTest.xml @@ -1,78 +1,73 @@ - + - - - - + + + - - - - + + - - - + + - + - - + + - - - - - + + + + + - - - + + - - - - - - + + + + + + - - - - - - + + + + + + + - - - + + - - - - + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.TransactionArchiveTest.xml b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.TransactionArchiveTest.xml index d996d81..54cc0b0 100644 --- a/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.TransactionArchiveTest.xml +++ b/millions/target/surefire-reports/TEST-no.ntnu.gruppe53.TransactionArchiveTest.xml @@ -1,77 +1,72 @@ - + - - - - + + + - - - - + + - - - + + - + - - + + - - - - - + + + + + - - - + + - - - - - - + + + + + + - - - - - - + + + + + + + - - - + + - - - - + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/millions/target/surefire-reports/no.ntnu.gruppe53.AppTest.txt b/millions/target/surefire-reports/no.ntnu.gruppe53.AppTest.txt index ebb7301..e4a8ff7 100644 --- a/millions/target/surefire-reports/no.ntnu.gruppe53.AppTest.txt +++ b/millions/target/surefire-reports/no.ntnu.gruppe53.AppTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: no.ntnu.gruppe53.AppTest ------------------------------------------------------------------------------- -Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.116 s -- in no.ntnu.gruppe53.AppTest +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.025 s -- in no.ntnu.gruppe53.AppTest diff --git a/millions/target/surefire-reports/no.ntnu.gruppe53.ExchangeTest.txt b/millions/target/surefire-reports/no.ntnu.gruppe53.ExchangeTest.txt index 5a2f11f..9b01f19 100644 --- a/millions/target/surefire-reports/no.ntnu.gruppe53.ExchangeTest.txt +++ b/millions/target/surefire-reports/no.ntnu.gruppe53.ExchangeTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: no.ntnu.gruppe53.ExchangeTest ------------------------------------------------------------------------------- -Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.169 s -- in no.ntnu.gruppe53.ExchangeTest +Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.012 s -- in no.ntnu.gruppe53.ExchangeTest diff --git a/millions/target/surefire-reports/no.ntnu.gruppe53.FileHandlerTest.txt b/millions/target/surefire-reports/no.ntnu.gruppe53.FileHandlerTest.txt new file mode 100644 index 0000000..f92dbc5 --- /dev/null +++ b/millions/target/surefire-reports/no.ntnu.gruppe53.FileHandlerTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: no.ntnu.gruppe53.FileHandlerTest +------------------------------------------------------------------------------- +Tests run: 14, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.032 s -- in no.ntnu.gruppe53.FileHandlerTest diff --git a/millions/target/surefire-reports/no.ntnu.gruppe53.PlayerTest.txt b/millions/target/surefire-reports/no.ntnu.gruppe53.PlayerTest.txt index 25e68ef..2ff981e 100644 --- a/millions/target/surefire-reports/no.ntnu.gruppe53.PlayerTest.txt +++ b/millions/target/surefire-reports/no.ntnu.gruppe53.PlayerTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: no.ntnu.gruppe53.PlayerTest ------------------------------------------------------------------------------- -Tests run: 16, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.097 s -- in no.ntnu.gruppe53.PlayerTest +Tests run: 20, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.019 s -- in no.ntnu.gruppe53.PlayerTest diff --git a/millions/target/surefire-reports/no.ntnu.gruppe53.PortfolioTest.txt b/millions/target/surefire-reports/no.ntnu.gruppe53.PortfolioTest.txt index 593c5fe..18e1fb8 100644 --- a/millions/target/surefire-reports/no.ntnu.gruppe53.PortfolioTest.txt +++ b/millions/target/surefire-reports/no.ntnu.gruppe53.PortfolioTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: no.ntnu.gruppe53.PortfolioTest ------------------------------------------------------------------------------- -Tests run: 13, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.250 s -- in no.ntnu.gruppe53.PortfolioTest +Tests run: 13, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.032 s -- in no.ntnu.gruppe53.PortfolioTest diff --git a/millions/target/surefire-reports/no.ntnu.gruppe53.PurchaseCalculatorTest.txt b/millions/target/surefire-reports/no.ntnu.gruppe53.PurchaseCalculatorTest.txt index 59e5696..c2169bb 100644 --- a/millions/target/surefire-reports/no.ntnu.gruppe53.PurchaseCalculatorTest.txt +++ b/millions/target/surefire-reports/no.ntnu.gruppe53.PurchaseCalculatorTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: no.ntnu.gruppe53.PurchaseCalculatorTest ------------------------------------------------------------------------------- -Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.018 s -- in no.ntnu.gruppe53.PurchaseCalculatorTest +Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 s -- in no.ntnu.gruppe53.PurchaseCalculatorTest diff --git a/millions/target/surefire-reports/no.ntnu.gruppe53.PurchaseTest.txt b/millions/target/surefire-reports/no.ntnu.gruppe53.PurchaseTest.txt index 5f6dc10..23f111c 100644 --- a/millions/target/surefire-reports/no.ntnu.gruppe53.PurchaseTest.txt +++ b/millions/target/surefire-reports/no.ntnu.gruppe53.PurchaseTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: no.ntnu.gruppe53.PurchaseTest ------------------------------------------------------------------------------- -Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.048 s -- in no.ntnu.gruppe53.PurchaseTest +Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s -- in no.ntnu.gruppe53.PurchaseTest diff --git a/millions/target/surefire-reports/no.ntnu.gruppe53.SaleCalculatorUnitTest.txt b/millions/target/surefire-reports/no.ntnu.gruppe53.SaleCalculatorUnitTest.txt index 5a8b46d..fd1421c 100644 --- a/millions/target/surefire-reports/no.ntnu.gruppe53.SaleCalculatorUnitTest.txt +++ b/millions/target/surefire-reports/no.ntnu.gruppe53.SaleCalculatorUnitTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: no.ntnu.gruppe53.SaleCalculatorUnitTest ------------------------------------------------------------------------------- -Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.027 s -- in no.ntnu.gruppe53.SaleCalculatorUnitTest +Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.004 s -- in no.ntnu.gruppe53.SaleCalculatorUnitTest diff --git a/millions/target/surefire-reports/no.ntnu.gruppe53.SaleTest.txt b/millions/target/surefire-reports/no.ntnu.gruppe53.SaleTest.txt index 71c28bd..67ef934 100644 --- a/millions/target/surefire-reports/no.ntnu.gruppe53.SaleTest.txt +++ b/millions/target/surefire-reports/no.ntnu.gruppe53.SaleTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: no.ntnu.gruppe53.SaleTest ------------------------------------------------------------------------------- -Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.049 s -- in no.ntnu.gruppe53.SaleTest +Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.006 s -- in no.ntnu.gruppe53.SaleTest diff --git a/millions/target/surefire-reports/no.ntnu.gruppe53.ShareTest.txt b/millions/target/surefire-reports/no.ntnu.gruppe53.ShareTest.txt index 0fb9e51..e3461bd 100644 --- a/millions/target/surefire-reports/no.ntnu.gruppe53.ShareTest.txt +++ b/millions/target/surefire-reports/no.ntnu.gruppe53.ShareTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: no.ntnu.gruppe53.ShareTest ------------------------------------------------------------------------------- -Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.075 s -- in no.ntnu.gruppe53.ShareTest +Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.008 s -- in no.ntnu.gruppe53.ShareTest diff --git a/millions/target/surefire-reports/no.ntnu.gruppe53.StockTest.txt b/millions/target/surefire-reports/no.ntnu.gruppe53.StockTest.txt index 29b16d3..b424d06 100644 --- a/millions/target/surefire-reports/no.ntnu.gruppe53.StockTest.txt +++ b/millions/target/surefire-reports/no.ntnu.gruppe53.StockTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: no.ntnu.gruppe53.StockTest ------------------------------------------------------------------------------- -Tests run: 16, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.103 s -- in no.ntnu.gruppe53.StockTest +Tests run: 16, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.013 s -- in no.ntnu.gruppe53.StockTest diff --git a/millions/target/surefire-reports/no.ntnu.gruppe53.TransactionArchiveTest.txt b/millions/target/surefire-reports/no.ntnu.gruppe53.TransactionArchiveTest.txt index 0c130d2..fb14cda 100644 --- a/millions/target/surefire-reports/no.ntnu.gruppe53.TransactionArchiveTest.txt +++ b/millions/target/surefire-reports/no.ntnu.gruppe53.TransactionArchiveTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: no.ntnu.gruppe53.TransactionArchiveTest ------------------------------------------------------------------------------- -Tests run: 15, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.077 s -- in no.ntnu.gruppe53.TransactionArchiveTest +Tests run: 15, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.010 s -- in no.ntnu.gruppe53.TransactionArchiveTest From f313472731e48a10151686e6b0f863b6ed3f22c5 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Fri, 22 May 2026 12:12:25 +0200 Subject: [PATCH 033/142] Refined the NavigationView with networth in the corner --- .../gruppe53/controller/GameController.java | 12 ++++++++++ .../ntnu/gruppe53/views/NavigationView.java | 24 +++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index 368773e..48507f1 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -48,6 +48,7 @@ public void startNewGame() { vm.addView("market", marketView); initialize(); + refreshNavigationView(); vm.switchView("market"); } @@ -77,12 +78,14 @@ private void initialize() { portfolioView.getNavigationView().onMarketButton(() -> { vm.switchView("market"); portfolioView.refresh(); + refreshNavigationView(); }); //market actions marketView.getNavigationView().onPortfolioButton(() -> { vm.switchView("portfolio"); + refreshNavigationView(); }); @@ -151,6 +154,8 @@ private void sellShare(Share share, int quantity) { } + + //Create if no share selected private Share getSelectedShare() { Stock stock = exchange.getStock(marketView.getSelectedSymbol()); @@ -164,5 +169,12 @@ private Share getSelectedShare() { return share; } + private void refreshNavigationView() { + BigDecimal netWorth = player.getNetWorth(); + + marketView.getNavigationView().setNetWorth(netWorth); + portfolioView.getNavigationView().setNetWorth(netWorth); + } + } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java b/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java index 7f17fad..27daa31 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java @@ -1,6 +1,7 @@ package no.ntnu.gruppe53.views; import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.HBox; @@ -8,6 +9,9 @@ import javafx.scene.layout.Region; import javafx.scene.layout.VBox; +import java.math.BigDecimal; +import java.math.RoundingMode; + /* Navigation View: @@ -22,6 +26,8 @@ public class NavigationView extends HBox { + + //Navigation Buttons public Button marketButton; public Button portfolioButton; @@ -45,11 +51,17 @@ public NavigationView() { Region spacer = new Region(); HBox.setHgrow(spacer, Priority.ALWAYS); - Label networthLabelText = new Label("Net Worth:"); + HBox networthBox = new HBox(); + Label networthLabelText = new Label("Net Worth: "); networthLabel = new Label(); - this.getChildren().addAll(marketButton, portfolioButton, spacer, networthLabelText, networthLabel); + networthBox.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + networthBox.setPadding(new Insets(8, 14, 8, 14)); + networthBox.setAlignment(Pos.CENTER); + networthBox.getChildren().addAll(networthLabelText, networthLabel); + + this.getChildren().addAll(marketButton, portfolioButton, spacer, networthBox); } @@ -60,4 +72,12 @@ public void onPortfolioButton(Runnable action) { public void onMarketButton(Runnable action) { marketButton.setOnAction(e -> action.run()); } + + private String formatMoney(BigDecimal money) { + return money == null ? "0.00" : money.setScale(2, RoundingMode.HALF_UP).toPlainString(); + } + + public void setNetWorth(BigDecimal netWorth) { + networthLabel.setText("$" + netWorth.setScale(2, RoundingMode.HALF_UP).toPlainString()); + } } From c44492b107ea1ce8333710abaefebd5a3d3ee898 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Fri, 22 May 2026 12:29:52 +0200 Subject: [PATCH 034/142] Added Bottombar and refined navigationview --- .../no/ntnu/gruppe53/views/BottomView.java | 32 +++++++++++++++++++ .../no/ntnu/gruppe53/views/MarketView.java | 13 ++++++++ .../ntnu/gruppe53/views/NavigationView.java | 11 +++++-- .../no/ntnu/gruppe53/views/PortfolioView.java | 9 ++++++ .../compile/default-compile/createdFiles.lst | 7 ++-- .../compile/default-compile/inputFiles.lst | 1 + 6 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/views/BottomView.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/BottomView.java b/millions/src/main/java/no/ntnu/gruppe53/views/BottomView.java new file mode 100644 index 0000000..699bbb6 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/views/BottomView.java @@ -0,0 +1,32 @@ +package no.ntnu.gruppe53.views; + +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; + +public class BottomView extends HBox { + + //Buttons + private Button advanceWeek; + + + public BottomView() { + this.setPadding(new Insets(15, 12, 15, 12)); + this.setSpacing(10); + //Blue Background color + this.setStyle("-fx-background-color: #00bcf0;" + + "-fx-border-color: #303539 transparent transparent transparent;" + + "-fx-border-width: 5 0 0 0;"); + + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + + advanceWeek = new Button("Advance Week"); + advanceWeek.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + + this.getChildren().addAll(spacer, advanceWeek); + } + +} diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java index b9b8b77..2a575f8 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java @@ -21,6 +21,7 @@ public class MarketView extends BorderPane { //Main pane of view private final NavigationView navigationView; + private final BottomView bottomView; //Buttons private Button purchaseButton; @@ -55,9 +56,11 @@ public class MarketView extends BorderPane { public MarketView() { navigationView = new NavigationView(); + bottomView = new BottomView(); this.setTop(navigationView); this.setCenter(centerPane()); + this.setBottom(bottomView); } @@ -83,6 +86,12 @@ public VBox centerPane() { VBox vBox = new VBox(); + vBox.setPadding(new Insets(15, 12, 15, 12)); + vBox.setSpacing(10); + //Blue Background color + vBox.setStyle("-fx-background-color: #00bcf0;"); + + Label titleLabel = new Label("Stock Market"); titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); @@ -262,4 +271,8 @@ private String formatMoney(BigDecimal money) { public NavigationView getNavigationView() { return navigationView; } + + public BottomView getBottomView() { + return bottomView; + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java b/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java index 27daa31..4eeba80 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java @@ -40,7 +40,9 @@ public NavigationView() { this.setPadding(new Insets(15, 12, 15, 12)); this.setSpacing(10); //Blue Background color - this.setStyle("-fx-background-color: #00bcf0;"); + this.setStyle("-fx-background-color: #00bcf0;" + + "-fx-border-color: transparent transparent #303539 transparent;" + + "-fx-border-width: 0 0 5 0;"); marketButton = new Button("Market"); @@ -56,7 +58,12 @@ public NavigationView() { Label networthLabelText = new Label("Net Worth: "); networthLabel = new Label(); - networthBox.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + networthBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); networthBox.setPadding(new Insets(8, 14, 8, 14)); networthBox.setAlignment(Pos.CENTER); networthBox.getChildren().addAll(networthLabelText, networthLabel); diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index 86bcc9e..d8a4c9d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -22,6 +22,7 @@ public class PortfolioView extends BorderPane { //Main pane of view private final NavigationView navigationView; + private final BottomView bottomView; //Buttons private Button sellButton; @@ -55,9 +56,11 @@ public class PortfolioView extends BorderPane { public PortfolioView() { navigationView = new NavigationView(); + bottomView = new BottomView(); this.setTop(navigationView); this.setCenter(centerPane()); + this.setBottom(bottomView); } @@ -84,6 +87,8 @@ private VBox centerPane() { VBox vBox = new VBox(); vBox.setPadding(new Insets(15, 12, 15, 12)); vBox.setSpacing(10); + //Blue Background color + vBox.setStyle("-fx-background-color: #00bcf0;"); viewLabel = new Label("Portfolio"); viewLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); @@ -182,4 +187,8 @@ private String formatMoney(BigDecimal money) { public NavigationView getNavigationView() { return navigationView; } + + public BottomView getBottomView() { + return bottomView; + } } \ No newline at end of file diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index 95c2d6e..5f23b89 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,15 +1,16 @@ -no/ntnu/gruppe53/TransactionArchive.class no/ntnu/gruppe53/FileHandler.class no/ntnu/gruppe53/Exchange.class -no/ntnu/gruppe53/Sale.class no/ntnu/gruppe53/controller/ViewController.class no/ntnu/gruppe53/Transaction.class no/ntnu/gruppe53/views/NavigationView.class no/ntnu/gruppe53/controller/GameController.class +no/ntnu/gruppe53/views/BottomView.class no/ntnu/gruppe53/Player.class +no/ntnu/gruppe53/views/PortfolioView$1.class +no/ntnu/gruppe53/TransactionArchive.class +no/ntnu/gruppe53/Sale.class no/ntnu/gruppe53/views/PortfolioView.class no/ntnu/gruppe53/controller/StartViewController.class no/ntnu/gruppe53/views/StartView.class -no/ntnu/gruppe53/views/PortfolioView$1.class no/ntnu/gruppe53/Purchase.class no/ntnu/gruppe53/views/MarketView.class diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index 5688d60..9a23825 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -15,6 +15,7 @@ /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/BottomView.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java From 22101f224498f31e233b1adcda4d5571cb302b8f Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Fri, 22 May 2026 12:39:24 +0200 Subject: [PATCH 035/142] Added logo to startView --- .../main/java/no/ntnu/gruppe53/views/StartView.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java b/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java index efbebf3..d8616c5 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java @@ -2,6 +2,8 @@ import javafx.geometry.Pos; import javafx.scene.control.Button; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.layout.VBox; import no.ntnu.gruppe53.controller.StartViewController; import no.ntnu.gruppe53.controller.ViewController; @@ -12,6 +14,7 @@ public class StartView extends VBox { private final Button newGameButton; private final Button quitButton; + private final Image logo; /** * Constructs the view and sets parameters to be exposed for the {@link StartViewController}. @@ -22,13 +25,20 @@ public StartView(ViewController vm) { this.setAlignment(Pos.CENTER); this.setSpacing(20); + this.setStyle("-fx-background-color: #00bcf0;"); + + logo = new Image(getClass().getClassLoader().getResourceAsStream("Millions-logo.png")); + ImageView logoView = new ImageView(logo); + logoView.setFitWidth(300); + logoView.setPreserveRatio(true); + newGameButton = new Button("New Game"); quitButton = new Button("Quit"); newGameButton.setPrefWidth(200); quitButton.setPrefWidth(200); - this.getChildren().addAll(newGameButton, quitButton); + this.getChildren().addAll(logoView, newGameButton, quitButton); } /** From bd6efecf31e59ad3f60e19142e5bc92194800a01 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Fri, 22 May 2026 12:41:32 +0200 Subject: [PATCH 036/142] Refined StartView --- millions/src/main/java/no/ntnu/gruppe53/views/StartView.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java b/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java index d8616c5..f5c30ef 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java @@ -36,7 +36,9 @@ public StartView(ViewController vm) { quitButton = new Button("Quit"); newGameButton.setPrefWidth(200); + newGameButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); quitButton.setPrefWidth(200); + quitButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); this.getChildren().addAll(logoView, newGameButton, quitButton); } From 6abc3aaf43a91321a378dad3d88fd5e36080a63a Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Fri, 22 May 2026 13:30:35 +0200 Subject: [PATCH 037/142] Removed old navigationbar from Market and portfolioview --- .../java/no/ntnu/gruppe53/views/MarketView.java | 15 --------------- .../no/ntnu/gruppe53/views/PortfolioView.java | 17 ----------------- 2 files changed, 32 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java index 2a575f8..80ef69e 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java @@ -66,21 +66,6 @@ public MarketView() { } - public HBox navigationBar() { - - HBox hBox = new HBox(); - - hBox.setPadding(new Insets(15, 12, 15, 12)); - hBox.setSpacing(10); - hBox.setStyle("-fx-background-color: #336699;"); - - marketButton = new Button("Market"); - portfolioButton = new Button("Portfolio"); - - hBox.getChildren().addAll(marketButton, portfolioButton); - - return hBox; - } public VBox centerPane() { diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index d8a4c9d..ed8d871 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -65,23 +65,6 @@ public PortfolioView() { } - private HBox navigationBar() { - - HBox hBox = new HBox(); - - //Sets simple styling - hBox.setPadding(new Insets(15, 12, 15, 12)); - hBox.setSpacing(10); - hBox.setStyle("-fx-background-color: #336699;"); - - marketButton = new Button("Market"); - portfolioButton = new Button("Portfolio"); - - hBox.getChildren().addAll(marketButton, portfolioButton); - - - return hBox; - } private VBox centerPane() { VBox vBox = new VBox(); From 242e01ff632b16e05a6d38a16d768106baa6db75 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Fri, 22 May 2026 13:43:05 +0200 Subject: [PATCH 038/142] Added money label to navigationview --- .../gruppe53/controller/GameController.java | 4 ++++ .../no/ntnu/gruppe53/views/MarketView.java | 2 +- .../ntnu/gruppe53/views/NavigationView.java | 22 ++++++++++++++++++- .../no/ntnu/gruppe53/views/PortfolioView.java | 4 ++-- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index 48507f1..6929052 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -100,6 +100,7 @@ private void initialize() { marketView.onPurchaseButton(() -> { purchaseStock(marketView.getSelectedSymbol(), marketView.getQuantityPurchase()); + refreshNavigationView(); }); marketView.onQuantitySelect(() -> { @@ -171,9 +172,12 @@ private Share getSelectedShare() { private void refreshNavigationView() { BigDecimal netWorth = player.getNetWorth(); + BigDecimal money = player.getMoney(); marketView.getNavigationView().setNetWorth(netWorth); portfolioView.getNavigationView().setNetWorth(netWorth); + marketView.getNavigationView().setMoney(money); + portfolioView.getNavigationView().setMoney(money); } diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java index 80ef69e..dee68bc 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java @@ -78,7 +78,7 @@ public VBox centerPane() { Label titleLabel = new Label("Stock Market"); - titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); + titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; "); playerMoneyLabel = new Label("Money: $0.00"); Label stocksTitleLabel = new Label("Available stocks:"); diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java b/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java index 4eeba80..ab67d18 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java @@ -34,6 +34,7 @@ public class NavigationView extends HBox { //Dynamic Labels public Label networthLabel; + public Label moneyLabel; public NavigationView() { @@ -53,6 +54,21 @@ public NavigationView() { Region spacer = new Region(); HBox.setHgrow(spacer, Priority.ALWAYS); + HBox moneyBox = new HBox(); + + Label moneyLabelText = new Label("Money: "); + moneyLabel = new Label(); + + moneyBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + moneyBox.setPadding(new Insets(8, 14, 8, 14)); + moneyBox.setAlignment(Pos.CENTER); + moneyBox.getChildren().addAll(moneyLabelText, moneyLabel); + HBox networthBox = new HBox(); Label networthLabelText = new Label("Net Worth: "); @@ -68,7 +84,7 @@ public NavigationView() { networthBox.setAlignment(Pos.CENTER); networthBox.getChildren().addAll(networthLabelText, networthLabel); - this.getChildren().addAll(marketButton, portfolioButton, spacer, networthBox); + this.getChildren().addAll(marketButton, portfolioButton, spacer, moneyBox, networthBox); } @@ -87,4 +103,8 @@ private String formatMoney(BigDecimal money) { public void setNetWorth(BigDecimal netWorth) { networthLabel.setText("$" + netWorth.setScale(2, RoundingMode.HALF_UP).toPlainString()); } + + public void setMoney(BigDecimal money) { + moneyLabel.setText("$" + money.setScale(2, RoundingMode.HALF_UP).toPlainString()); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index ed8d871..831189d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -71,10 +71,10 @@ private VBox centerPane() { vBox.setPadding(new Insets(15, 12, 15, 12)); vBox.setSpacing(10); //Blue Background color - vBox.setStyle("-fx-background-color: #00bcf0;"); + vBox.setStyle("-fx-background-color: #00bcf0; "); viewLabel = new Label("Portfolio"); - viewLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); + viewLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: #303539;"); playerNameLabel = new Label("Player: -"); netWorthLabel = new Label("Net worth: $0.00"); From a73b92f44984fb3802a6d525709b8f2925ce98aa Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Fri, 22 May 2026 14:13:14 +0200 Subject: [PATCH 039/142] Rewritten the portfolioview to list grouped shares and not transactions --- .../gruppe53/controller/GameController.java | 72 ++++++++++++++----- .../no/ntnu/gruppe53/views/BottomView.java | 16 +++++ .../no/ntnu/gruppe53/views/MarketView.java | 11 +++ .../no/ntnu/gruppe53/views/PortfolioView.java | 23 ++++-- 4 files changed, 99 insertions(+), 23 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index 6929052..ff1d17e 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -2,7 +2,9 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import no.ntnu.gruppe53.*; import no.ntnu.gruppe53.views.NavigationView; @@ -34,14 +36,12 @@ public void startNewGame() { marketView = new MarketView(); marketView.setPlayer(player); marketView.setExchange(exchange); - //marketView.setPurchaseHandler(this::purchaseStock); + portfolioView = new PortfolioView(); - portfolioView.setPlayer(player); - //marketView.setShowPortfolioHandler(this::showPortfolio); - portfolioView.setShowMarketHandler(this::showPurchaseView); - portfolioView.setSellHandler(this::sellShare); + + //vm.addView("purchase", purchaseView); vm.addView("portfolio", portfolioView); @@ -49,6 +49,7 @@ public void startNewGame() { initialize(); refreshNavigationView(); + refreshPortfolioList(); vm.switchView("market"); } @@ -57,38 +58,45 @@ public void startNewGame() { - public void showPortfolio() { - if (portfolioView != null) { - portfolioView.setPlayer(player); - vm.switchView("portfolio"); - } - } - public void showPurchaseView() { - if (marketView != null) { - marketView.setPlayer(player); - marketView.refreshStocks(); - vm.switchView("purchase"); - } - } private void initialize() { //portfolio actions portfolioView.getNavigationView().onMarketButton(() -> { vm.switchView("market"); - portfolioView.refresh(); + refreshNavigationView(); }); + /* + put advance week code here + */ + + portfolioView.getBottomView().onAdvanceWeek(() -> { + // advance week code here + + }); + //market actions marketView.getNavigationView().onPortfolioButton(() -> { vm.switchView("portfolio"); refreshNavigationView(); + refreshPortfolioList(); + + }); + + /* + put advance week code here + */ + marketView.getBottomView().onAdvanceWeek(() -> { + // advance week code here }); + + marketView.onStockSelection(newValue ->{ purchaseCalculator = new PurchaseCalculator(getSelectedShare()); @@ -170,6 +178,32 @@ private Share getSelectedShare() { return share; } + private void refreshPortfolioList() { + portfolioView.getPortfolioListView().getItems().clear(); + + Map groupedShares = new LinkedHashMap<>(); + + for (Share share : player.getPortfolio().getShares()) { + String symbol = share.getStock().getSymbol(); + + if (!groupedShares.containsKey(symbol)) { + groupedShares.put(symbol, share); + } else { + Share existingShare = groupedShares.get(symbol); + + Share combinedShare = new Share( + existingShare.getStock(), + existingShare.getQuantity().add(share.getQuantity()), + existingShare.getPurchasePrice() + ); + + groupedShares.put(symbol, combinedShare); + } + + } + portfolioView.getPortfolioListView().getItems().setAll(groupedShares.values()); + } + private void refreshNavigationView() { BigDecimal netWorth = player.getNetWorth(); BigDecimal money = player.getMoney(); diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/BottomView.java b/millions/src/main/java/no/ntnu/gruppe53/views/BottomView.java index 699bbb6..0debe47 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/BottomView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/BottomView.java @@ -6,6 +6,18 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.Region; + +/* +Bottom View: + +Yellow Color, Hex: #ffe556 RGB(255, 229, 86) +Blue Color, Hex: #00bcf0 RGB(0, 188, 240) +Black Color, Hex: #303539 RGB(48, 53, 57) + + + + */ + public class BottomView extends HBox { //Buttons @@ -29,4 +41,8 @@ public BottomView() { this.getChildren().addAll(spacer, advanceWeek); } + public void onAdvanceWeek(Runnable action) { + advanceWeek.setOnAction(e -> action.run()); + } + } diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java index dee68bc..11a24e1 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java @@ -17,6 +17,17 @@ import no.ntnu.gruppe53.Stock; import no.ntnu.gruppe53.controller.GameController; +/* +Market View: + +Yellow Color, Hex: #ffe556 RGB(255, 229, 86) +Blue Color, Hex: #00bcf0 RGB(0, 188, 240) +Black Color, Hex: #303539 RGB(48, 53, 57) + + + + */ + public class MarketView extends BorderPane { //Main pane of view diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index 831189d..92b08d5 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -17,6 +17,17 @@ import no.ntnu.gruppe53.Share; import no.ntnu.gruppe53.controller.GameController; +/* +Portfolio View: + +Yellow Color, Hex: #ffe556 RGB(255, 229, 86) +Blue Color, Hex: #00bcf0 RGB(0, 188, 240) +Black Color, Hex: #303539 RGB(48, 53, 57) + + + + */ + public class PortfolioView extends BorderPane { @@ -136,9 +147,9 @@ public void setSellHandler(BiConsumer sellHandler) { public void setPlayer(Player player) { this.player = player; - refresh(); - } + } + /* public void refresh() { if (player == null) { playerNameLabel.setText("Player: -"); @@ -150,7 +161,7 @@ public void refresh() { playerNameLabel.setText("Player: " + player.getName()); netWorthLabel.setText("Net worth: $" + formatMoney(player.getNetWorth())); portfolioListView.getItems().setAll(player.getPortfolio().getShares()); - } + }*/ private String formatShare(Share share) { return share.getStock().getSymbol() @@ -158,7 +169,7 @@ private String formatShare(Share share) { + share.getStock().getCompany() + " | Qty: " + share.getQuantity() - + " | Buy price: $" + + " | Current Market Price: $" + formatMoney(share.getPurchasePrice()); } @@ -167,6 +178,10 @@ private String formatMoney(BigDecimal money) { return money == null ? "0.00" : money.setScale(2, RoundingMode.HALF_UP).toPlainString(); } + public ListView getPortfolioListView() { + return portfolioListView; + } + public NavigationView getNavigationView() { return navigationView; } From 9116b2361f92fd7818f6aa09d08b601de772d04f Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Fri, 22 May 2026 16:50:12 +0200 Subject: [PATCH 040/142] Further refinement of the css --- .../gruppe53/controller/GameController.java | 54 +++-- .../no/ntnu/gruppe53/views/MarketView.java | 90 +++++--- .../ntnu/gruppe53/views/NavigationView.java | 24 ++- .../no/ntnu/gruppe53/views/PortfolioView.java | 199 +++++++++++++----- millions/src/main/resources/Scroll-dot.png | Bin 0 -> 448 bytes millions/target/classes/Millions-logo.svg | 18 ++ millions/target/classes/Scroll-dot.png | Bin 0 -> 448 bytes 7 files changed, 281 insertions(+), 104 deletions(-) create mode 100644 millions/src/main/resources/Scroll-dot.png create mode 100644 millions/target/classes/Millions-logo.svg create mode 100644 millions/target/classes/Scroll-dot.png diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index ff1d17e..aeb0a10 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -1,13 +1,11 @@ package no.ntnu.gruppe53.controller; import java.math.BigDecimal; -import java.math.RoundingMode; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import no.ntnu.gruppe53.*; -import no.ntnu.gruppe53.views.NavigationView; import no.ntnu.gruppe53.views.PortfolioView; import no.ntnu.gruppe53.views.MarketView; @@ -21,6 +19,7 @@ public class GameController { private MarketView marketView; private PortfolioView portfolioView; private PurchaseCalculator purchaseCalculator; + private SaleCalculator saleCalculator; public GameController(ViewController vm) { this.vm = vm; @@ -34,7 +33,6 @@ public void startNewGame() { marketView = new MarketView(); - marketView.setPlayer(player); marketView.setExchange(exchange); @@ -62,6 +60,9 @@ public void startNewGame() { private void initialize() { + portfolioView.getNavigationView().setPlayerLabel(player.getName()); + marketView.getNavigationView().setPlayerLabel(player.getName()); + //portfolio actions portfolioView.getNavigationView().onMarketButton(() -> { vm.switchView("market"); @@ -71,6 +72,7 @@ private void initialize() { /* put advance week code here + */ portfolioView.getBottomView().onAdvanceWeek(() -> { @@ -78,6 +80,22 @@ private void initialize() { }); + portfolioView.onShareSelection(newValue -> { + saleCalculator = new SaleCalculator(newValue); + + portfolioView.setSelectedShare(newValue); + portfolioView.setQuantitySpinner(newValue.getQuantity().intValue()); + portfolioView.setGross(saleCalculator.calculateGross().toString()); + portfolioView.setCommission(saleCalculator.calculateCommission().toString()); + portfolioView.setTax(saleCalculator.calculateTax().toString()); + portfolioView.setTotal(saleCalculator.calculateTotal().toString()); + }); + + portfolioView.onSellButton(() -> { + + + }); + //market actions marketView.getNavigationView().onPortfolioButton(() -> { @@ -98,7 +116,7 @@ private void initialize() { marketView.onStockSelection(newValue ->{ - purchaseCalculator = new PurchaseCalculator(getSelectedShare()); + purchaseCalculator = new PurchaseCalculator(getSelectedStock()); marketView.selectedStock.setText(newValue); marketView.gross.setText(purchaseCalculator.calculateGross().toString()); @@ -113,7 +131,7 @@ private void initialize() { marketView.onQuantitySelect(() -> { //if no share selected need to be implemented - purchaseCalculator = new PurchaseCalculator(getSelectedShare()); + purchaseCalculator = new PurchaseCalculator(getSelectedStock()); marketView.gross.setText(purchaseCalculator.calculateGross().toString()); marketView.commission.setText(purchaseCalculator.calculateCommission().toString()); @@ -133,51 +151,41 @@ private void purchaseStock(String stockSymbol, int quantity) { try { exchange.buy(stockSymbol, BigDecimal.valueOf(quantity), player); - marketView.setPlayer(player); marketView.refreshStocks(); - portfolioView.setPlayer(player); } catch (RuntimeException ex) { System.out.println("Purchase failed: " + ex.getMessage()); } } - private void sellShare(Share share, int quantity) { - if (exchange == null || player == null || share == null || quantity <= 0) { + private void sellShares(int quantity) { + if (exchange == null || player == null || stocks == null || quantity == 0) { return; } try { - Share shareToSell = new Share( - share.getStock(), - BigDecimal.valueOf(quantity), - share.getPurchasePrice() - ); - - exchange.sell(shareToSell, player); - marketView.setPlayer(player); - marketView.refreshStocks(); - portfolioView.setPlayer(player); + //exchange.sell() } catch (RuntimeException ex) { - System.out.println("Sell failed: " + ex.getMessage()); + System.out.println("Sale failed: " + ex.getMessage()); } } - //Create if no share selected - private Share getSelectedShare() { + private Share getSelectedStock() { Stock stock = exchange.getStock(marketView.getSelectedSymbol()); BigDecimal quantity = BigDecimal.valueOf(marketView.getQuantityPurchase()); BigDecimal purchasePrice = stock.getSalesPrice(); - Share share = new Share(stock, quantity,purchasePrice); + Share share = new Share(stock, quantity, purchasePrice); return share; } + + private void refreshPortfolioList() { portfolioView.getPortfolioListView().getItems().clear(); diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java index 11a24e1..f3c9fbc 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java @@ -8,9 +8,11 @@ import java.util.stream.Collectors; import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.control.*; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import no.ntnu.gruppe53.Exchange; import no.ntnu.gruppe53.Player; @@ -37,12 +39,9 @@ public class MarketView extends BorderPane { //Buttons private Button purchaseButton; - //NavigationBar Buttons - private Button marketButton; - private Button portfolioButton; + //Dynamic Labels/Text - private Label playerMoneyLabel; public Label selectedStock; public TextField gross; public TextField commission; @@ -91,11 +90,37 @@ public VBox centerPane() { Label titleLabel = new Label("Stock Market"); titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; "); - playerMoneyLabel = new Label("Money: $0.00"); - Label stocksTitleLabel = new Label("Available stocks:"); + VBox titleBox = new VBox(titleLabel); + titleBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + titleBox.setAlignment(Pos.CENTER); + titleBox.setPadding(new Insets(4)); + titleBox.setMaxWidth(200); stocksListView = new ListView<>(); - stocksListView.setPrefHeight(300); + stocksListView.setMinHeight(0); + stocksListView.setMaxHeight(Double.MAX_VALUE); + stocksListView.setStyle("-fx-background-color: transparent;"); + + + VBox stockListBox = new VBox(stocksListView); + stockListBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + stockListBox.setPadding(new Insets(4)); + stockListBox.setMinHeight(0); + stockListBox.setMaxHeight(Double.MAX_VALUE); + + // Ensures the stocklist grows to fit the available screenspace + VBox.setVgrow(stocksListView, Priority.ALWAYS); + VBox.setVgrow(stockListBox, Priority.ALWAYS); HBox selectedStockBox = new HBox(); @@ -104,7 +129,7 @@ public VBox centerPane() { selectedStockBox.getChildren().addAll(selectedStockLabel, selectedStock); - HBox purchaseBox = new HBox(); + HBox quantityBox = new HBox(); @@ -113,14 +138,16 @@ public VBox centerPane() { quantitySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1_000_000, 1)); quantitySpinner.setEditable(true); + quantityBox.getChildren().addAll(quantityLabel, quantitySpinner); + HBox calculationBox = new HBox(); Label grossLabel = new Label("Gross: "); - gross = new TextField(""); + gross = new TextField(); gross.setEditable(false); gross.setMaxWidth(200); Label commissionLabel = new Label("Commission: "); - commission = new TextField(""); + commission = new TextField(); commission.setEditable(false); commission.setMaxWidth(200); @@ -137,17 +164,25 @@ public VBox centerPane() { purchaseButton = new Button("Purchase"); - purchaseBox.getChildren().addAll(quantityLabel, quantitySpinner); + VBox purchaseBox = new VBox(5); + + purchaseBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + purchaseBox.setPadding(new Insets(8, 14, 8, 14)); + purchaseBox.setAlignment(Pos.CENTER_LEFT); + + purchaseBox.getChildren().addAll(selectedStockBox, quantityBox, calculationBox, totalBox, purchaseButton); - vBox.getChildren().addAll(titleLabel, - playerMoneyLabel, - stocksTitleLabel, - stocksListView, - selectedStockBox, - purchaseBox, - calculationBox, - totalBox, - purchaseButton); + + + vBox.getChildren().addAll(titleBox, + stockListBox, + purchaseBox); return vBox; } @@ -174,6 +209,8 @@ public void onQuantitySelect(Runnable action) { }); } + + /* public void onPurchaseButton(Runnable action) { purchaseButton.setOnAction(event -> { @@ -196,25 +233,14 @@ public void setPurchaseHandler(BiConsumer purchaseHandler) { } - public void setPlayer(Player player) { - this.player = player; - - if (player == null) { - playerMoneyLabel.setText("Money: $0.00"); - return; - } - playerMoneyLabel.setText("Money: $" + formatMoney(player.getMoney())); - } public void setExchange(Exchange exchange) { this.exchange = exchange; refreshStocks(); } - public void refreshPlayer() { - setPlayer(player); - } + public void refreshStocks() { if (exchange == null) { diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java b/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java index ab67d18..1aa6fe5 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java @@ -33,6 +33,7 @@ public class NavigationView extends HBox { public Button portfolioButton; //Dynamic Labels + public Label playerLabel; public Label networthLabel; public Label moneyLabel; @@ -54,6 +55,23 @@ public NavigationView() { Region spacer = new Region(); HBox.setHgrow(spacer, Priority.ALWAYS); + HBox playerBox = new HBox(); + + Label playerLabelText = new Label("Player: "); + playerLabel = new Label(); + + playerBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + playerBox.setPadding(new Insets(8, 14, 8, 14)); + playerBox.setAlignment(Pos.CENTER); + + playerBox.getChildren().addAll(playerLabelText, playerLabel); + HBox moneyBox = new HBox(); Label moneyLabelText = new Label("Money: "); @@ -84,7 +102,7 @@ public NavigationView() { networthBox.setAlignment(Pos.CENTER); networthBox.getChildren().addAll(networthLabelText, networthLabel); - this.getChildren().addAll(marketButton, portfolioButton, spacer, moneyBox, networthBox); + this.getChildren().addAll(marketButton, portfolioButton, spacer, playerBox, moneyBox, networthBox); } @@ -107,4 +125,8 @@ public void setNetWorth(BigDecimal netWorth) { public void setMoney(BigDecimal money) { moneyLabel.setText("$" + money.setScale(2, RoundingMode.HALF_UP).toPlainString()); } + + public void setPlayerLabel(String player) { + playerLabel.setText(player); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java index 92b08d5..314f798 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java @@ -3,19 +3,17 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.function.BiConsumer; +import java.util.function.Consumer; import javafx.geometry.Insets; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.ListView; -import javafx.scene.control.Spinner; -import javafx.scene.control.SpinnerValueFactory; +import javafx.geometry.Pos; +import javafx.scene.control.*; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import no.ntnu.gruppe53.Player; import no.ntnu.gruppe53.Share; -import no.ntnu.gruppe53.controller.GameController; /* Portfolio View: @@ -43,11 +41,14 @@ public class PortfolioView extends BorderPane { private Button portfolioButton; //Static Labels - private Label viewLabel; + private Label titleLabel; //Dynamic Labels - private Label playerNameLabel; - private Label netWorthLabel; + private Label selectedShare; + private TextField gross; + private TextField commission; + private TextField tax; + private TextField total; //PortfolioListView private ListView portfolioListView; @@ -84,14 +85,23 @@ private VBox centerPane() { //Blue Background color vBox.setStyle("-fx-background-color: #00bcf0; "); - viewLabel = new Label("Portfolio"); - viewLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: #303539;"); + titleLabel = new Label("Portfolio"); + titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: #303539;"); - playerNameLabel = new Label("Player: -"); - netWorthLabel = new Label("Net worth: $0.00"); + VBox titleBox = new VBox(titleLabel); + titleBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + titleBox.setAlignment(Pos.CENTER); + titleBox.setPadding(new Insets(4)); + titleBox.setMaxWidth(200); portfolioListView = new ListView<>(); - portfolioListView.setPrefHeight(300); + portfolioListView.setMinHeight(0); + portfolioListView.setMaxHeight(Double.MAX_VALUE); portfolioListView.setCellFactory(list -> new javafx.scene.control.ListCell<>() { @Override protected void updateItem(Share item, boolean empty) { @@ -100,40 +110,95 @@ protected void updateItem(Share item, boolean empty) { } }); - HBox sellBar = new HBox(10); + VBox portfolioListBox = new VBox(portfolioListView); + portfolioListBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + portfolioListBox.setPadding(new Insets(4)); + portfolioListBox.setMinHeight(0); + portfolioListBox.setMaxHeight(Double.MAX_VALUE); + + // Ensures the portfoliolist grows to fit the available screenspace + VBox.setVgrow(portfolioListView, Priority.ALWAYS); + VBox.setVgrow(portfolioListBox, Priority.ALWAYS); + + + HBox selectedShareBox = new HBox(); + + Label selectedShareLabel = new Label("Selected Share: "); + selectedShare = new Label(); + + selectedShareBox.getChildren().addAll(selectedShareLabel, selectedShare); + + + + HBox quantityBox = new HBox(10); Label quantityLabel = new Label("Quantity:"); quantitySpinner = new Spinner<>(); quantitySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1_000_000, 1)); quantitySpinner.setEditable(true); + quantityBox.getChildren().addAll(quantityLabel, quantitySpinner); + + HBox calculationBox = new HBox(); + + Label grossLabel = new Label("Gross: "); + gross = new TextField(); + gross.setEditable(false); + gross.setMaxWidth(200); + Label commissionLabel = new Label("Commission: "); + commission = new TextField(); + commission.setEditable(false); + commission.setMaxWidth(200); + Label taxLabel = new Label("Tax: "); + tax = new TextField(); + tax.setEditable(false); + tax.setMaxWidth(200); + + calculationBox.getChildren().addAll(grossLabel, gross, commissionLabel, commission, taxLabel, tax); + + + + HBox totalBox = new HBox(); + + Label totalLabel = new Label("Total: "); + total = new TextField(); + total.setEditable(false); + total.setMaxWidth(200); + + totalBox.getChildren().addAll(totalLabel, total); + sellButton = new Button("Sell"); - sellBar.getChildren().addAll(quantityLabel, quantitySpinner, sellButton); + VBox saleBox = new VBox(5); + + saleBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + saleBox.setPadding(new Insets(8, 14, 8, 14)); + saleBox.setAlignment(Pos.CENTER_LEFT); + + saleBox.getChildren().addAll(selectedShareBox, quantityBox, calculationBox, totalBox, sellButton); - vBox.getChildren().addAll(viewLabel, - playerNameLabel, - netWorthLabel, - portfolioListView, - sellBar); + + vBox.getChildren().addAll(titleBox, + portfolioListBox, + saleBox); return vBox; } public void onSellButton(Runnable action) { - sellButton.setOnAction(event -> { - if (sellHandler == null) { - return; - } + sellButton.setOnAction(e -> action.run()); + } - Share selectedShare = portfolioListView.getSelectionModel().getSelectedItem(); - if (selectedShare == null) { - return; - } - int quantity = quantitySpinner.getValue(); - sellHandler.accept(selectedShare, quantity); - }); - } @@ -145,23 +210,8 @@ public void setSellHandler(BiConsumer sellHandler) { this.sellHandler = sellHandler; } - public void setPlayer(Player player) { - this.player = player; - } - /* - public void refresh() { - if (player == null) { - playerNameLabel.setText("Player: -"); - netWorthLabel.setText("Net worth: $0.00"); - portfolioListView.getItems().clear(); - return; - } - playerNameLabel.setText("Player: " + player.getName()); - netWorthLabel.setText("Net worth: $" + formatMoney(player.getNetWorth())); - portfolioListView.getItems().setAll(player.getPortfolio().getShares()); - }*/ private String formatShare(Share share) { return share.getStock().getSymbol() @@ -178,6 +228,35 @@ private String formatMoney(BigDecimal money) { return money == null ? "0.00" : money.setScale(2, RoundingMode.HALF_UP).toPlainString(); } + public String getSelectedStockName() { + Share selected = portfolioListView.getSelectionModel().getSelectedItem(); + if (selected == null || selected == null) { + return null; + } + + return selected.getStock().toString(); + } + + public void onShareSelection(Consumer action) { + portfolioListView.getSelectionModel() + .selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + action.accept(newValue); + } + }); + } + + public void onQuantitySelect(Runnable action) { + quantitySpinner.valueProperty().addListener((obs, oldValue, newValue) -> { + action.run(); + }); + } + + public void onSellButtonClick(Runnable action) { + sellButton.setOnAction(e -> action.run()); + } + public ListView getPortfolioListView() { return portfolioListView; } @@ -189,4 +268,28 @@ public NavigationView getNavigationView() { public BottomView getBottomView() { return bottomView; } + + public void setQuantitySpinner(int quantitySpinner) { + this.quantitySpinner.getValueFactory().setValue(quantitySpinner); + } + + public void setSelectedShare(Share share) { + this.selectedShare.setText(share.getStock().getSymbol() + " | "+ share.getStock().getCompany()); + } + + public void setGross(String gross) { + this.gross.setText(gross); + } + + public void setCommission(String commission) { + this.commission.setText(commission); + } + + public void setTax(String tax) { + this.tax.setText(tax); + } + + public void setTotal(String total) { + this.total.setText(total); + } } \ No newline at end of file diff --git a/millions/src/main/resources/Scroll-dot.png b/millions/src/main/resources/Scroll-dot.png new file mode 100644 index 0000000000000000000000000000000000000000..eaae1d9279f8c07a1ea6ba6c92538fbdee5ab7e6 GIT binary patch literal 448 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU~KnvaSW-L^Y+R{-@^eS4j09b zNWGaLqPfY{EtQ*#>xN=;3)4==009=Ueb1+C)A{?p{QrDLpl&F5Ab)%9-Uib*{mWmc zpO2qEzv%zty0@%-k;}K2;KZZi44q=Bm41-5#$K6}p zKXQLRe);RukAlnF+drqq&7W`fxBm`9;p}zQg{O4`EdCl#WJud5?wI<+cp*dDJ>?lQ ze`>WH{%K@9S@NHcPQy{5L+(otGQ)xpNPjrLmoZr1ed`_J2>~EsPgg&ebxsLQ00JJQ Axc~qF literal 0 HcmV?d00001 diff --git a/millions/target/classes/Millions-logo.svg b/millions/target/classes/Millions-logo.svg new file mode 100644 index 0000000..8652e5e --- /dev/null +++ b/millions/target/classes/Millions-logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/millions/target/classes/Scroll-dot.png b/millions/target/classes/Scroll-dot.png new file mode 100644 index 0000000000000000000000000000000000000000..eaae1d9279f8c07a1ea6ba6c92538fbdee5ab7e6 GIT binary patch literal 448 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU~KnvaSW-L^Y+R{-@^eS4j09b zNWGaLqPfY{EtQ*#>xN=;3)4==009=Ueb1+C)A{?p{QrDLpl&F5Ab)%9-Uib*{mWmc zpO2qEzv%zty0@%-k;}K2;KZZi44q=Bm41-5#$K6}p zKXQLRe);RukAlnF+drqq&7W`fxBm`9;p}zQg{O4`EdCl#WJud5?wI<+cp*dDJ>?lQ ze`>WH{%K@9S@NHcPQy{5L+(otGQ)xpNPjrLmoZr1ed`_J2>~EsPgg&ebxsLQ00JJQ Axc~qF literal 0 HcmV?d00001 From 87906122a50641ab3f34ba8f964bd1d2d950a566 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 09:22:38 +0200 Subject: [PATCH 041/142] Updated package structure Put model classes into a "model" package folder, put service classes into a "service" package folder, and renamed "views" package folder to view. --- millions/src/main/java/no/ntnu/gruppe53/App.java | 2 +- .../ntnu/gruppe53/controller/GameController.java | 15 +++++++-------- .../gruppe53/controller/StartViewController.java | 2 +- .../no/ntnu/gruppe53/{ => model}/Exchange.java | 2 +- .../java/no/ntnu/gruppe53/{ => model}/Player.java | 2 +- .../no/ntnu/gruppe53/{ => model}/Portfolio.java | 2 +- .../no/ntnu/gruppe53/{ => model}/Purchase.java | 2 +- .../gruppe53/{ => model}/PurchaseCalculator.java | 2 +- .../java/no/ntnu/gruppe53/{ => model}/Sale.java | 2 +- .../ntnu/gruppe53/{ => model}/SaleCalculator.java | 2 +- .../java/no/ntnu/gruppe53/{ => model}/Share.java | 2 +- .../java/no/ntnu/gruppe53/{ => model}/Stock.java | 2 +- .../no/ntnu/gruppe53/{ => model}/Transaction.java | 2 +- .../gruppe53/{ => model}/TransactionArchive.java | 2 +- .../{ => model}/TransactionCalculator.java | 2 +- .../no/ntnu/gruppe53/{ => view}/FileHandler.java | 4 +++- .../ntnu/gruppe53/{views => view}/MarketView.java | 8 ++++---- .../gruppe53/{views => view}/PortfolioView.java | 6 +++--- .../ntnu/gruppe53/{views => view}/StartView.java | 2 +- .../test/java/no/ntnu/gruppe53/ExchangeTest.java | 5 ++++- .../java/no/ntnu/gruppe53/FileHandlerTest.java | 2 ++ .../test/java/no/ntnu/gruppe53/PlayerTest.java | 1 + .../test/java/no/ntnu/gruppe53/PortfolioTest.java | 4 ++++ .../no/ntnu/gruppe53/PurchaseCalculatorTest.java | 3 +++ .../test/java/no/ntnu/gruppe53/PurchaseTest.java | 1 + .../no/ntnu/gruppe53/SaleCalculatorUnitTest.java | 3 +++ .../src/test/java/no/ntnu/gruppe53/SaleTest.java | 1 + .../src/test/java/no/ntnu/gruppe53/ShareTest.java | 2 ++ .../src/test/java/no/ntnu/gruppe53/StockTest.java | 1 + .../no/ntnu/gruppe53/TransactionArchiveTest.java | 1 + 30 files changed, 55 insertions(+), 32 deletions(-) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Exchange.java (99%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Player.java (99%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Portfolio.java (99%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Purchase.java (98%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/PurchaseCalculator.java (98%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Sale.java (98%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/SaleCalculator.java (98%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Share.java (98%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Stock.java (99%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Transaction.java (98%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/TransactionArchive.java (99%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/TransactionCalculator.java (86%) rename millions/src/main/java/no/ntnu/gruppe53/{ => view}/FileHandler.java (98%) rename millions/src/main/java/no/ntnu/gruppe53/{views => view}/MarketView.java (97%) rename millions/src/main/java/no/ntnu/gruppe53/{views => view}/PortfolioView.java (97%) rename millions/src/main/java/no/ntnu/gruppe53/{views => view}/StartView.java (97%) diff --git a/millions/src/main/java/no/ntnu/gruppe53/App.java b/millions/src/main/java/no/ntnu/gruppe53/App.java index f3ec24a..6e3fa61 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/App.java +++ b/millions/src/main/java/no/ntnu/gruppe53/App.java @@ -4,7 +4,7 @@ import javafx.stage.Stage; import no.ntnu.gruppe53.controller.StartViewController; import no.ntnu.gruppe53.controller.ViewController; -import no.ntnu.gruppe53.views.StartView; +import no.ntnu.gruppe53.view.StartView; public class App extends Application { diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index 502bae1..c403958 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -1,16 +1,15 @@ package no.ntnu.gruppe53.controller; import java.math.BigDecimal; -import java.math.RoundingMode; import java.util.List; -import no.ntnu.gruppe53.Exchange; -import no.ntnu.gruppe53.FileHandler; -import no.ntnu.gruppe53.Player; -import no.ntnu.gruppe53.Share; -import no.ntnu.gruppe53.Stock; -import no.ntnu.gruppe53.views.PortfolioView; -import no.ntnu.gruppe53.views.MarketView; +import no.ntnu.gruppe53.model.Exchange; +import no.ntnu.gruppe53.view.FileHandler; +import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; +import no.ntnu.gruppe53.view.PortfolioView; +import no.ntnu.gruppe53.view.MarketView; public class GameController { private final ViewController vm; diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java index acf3d43..a588e20 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java @@ -1,6 +1,6 @@ package no.ntnu.gruppe53.controller; -import no.ntnu.gruppe53.views.StartView; +import no.ntnu.gruppe53.view.StartView; import javafx.application.Platform; /** diff --git a/millions/src/main/java/no/ntnu/gruppe53/Exchange.java b/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java similarity index 99% rename from millions/src/main/java/no/ntnu/gruppe53/Exchange.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java index 31fc20f..731f770 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Exchange.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; import java.math.RoundingMode; diff --git a/millions/src/main/java/no/ntnu/gruppe53/Player.java b/millions/src/main/java/no/ntnu/gruppe53/model/Player.java similarity index 99% rename from millions/src/main/java/no/ntnu/gruppe53/Player.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Player.java index f10e8a3..84840dd 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Player.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Player.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; import java.math.RoundingMode; diff --git a/millions/src/main/java/no/ntnu/gruppe53/Portfolio.java b/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java similarity index 99% rename from millions/src/main/java/no/ntnu/gruppe53/Portfolio.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java index 2f40774..7744c57 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Portfolio.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; import java.util.ArrayList; diff --git a/millions/src/main/java/no/ntnu/gruppe53/Purchase.java b/millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java similarity index 98% rename from millions/src/main/java/no/ntnu/gruppe53/Purchase.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java index 0b622e2..dbc9a5d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Purchase.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; diff --git a/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java similarity index 98% rename from millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java rename to millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java index 8620cb5..0f983ad 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; diff --git a/millions/src/main/java/no/ntnu/gruppe53/Sale.java b/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java similarity index 98% rename from millions/src/main/java/no/ntnu/gruppe53/Sale.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Sale.java index 00787d0..d8a6c36 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Sale.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; /** * Represents a sale {@link Transaction} where a {@link Player} sells diff --git a/millions/src/main/java/no/ntnu/gruppe53/SaleCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java similarity index 98% rename from millions/src/main/java/no/ntnu/gruppe53/SaleCalculator.java rename to millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java index b3b8345..cdc8371 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/SaleCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; diff --git a/millions/src/main/java/no/ntnu/gruppe53/Share.java b/millions/src/main/java/no/ntnu/gruppe53/model/Share.java similarity index 98% rename from millions/src/main/java/no/ntnu/gruppe53/Share.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Share.java index 6e3e21e..f2ec731 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Share.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Share.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; diff --git a/millions/src/main/java/no/ntnu/gruppe53/Stock.java b/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java similarity index 99% rename from millions/src/main/java/no/ntnu/gruppe53/Stock.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Stock.java index 71d93f4..5d06d45 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Stock.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; import java.util.ArrayList; diff --git a/millions/src/main/java/no/ntnu/gruppe53/Transaction.java b/millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java similarity index 98% rename from millions/src/main/java/no/ntnu/gruppe53/Transaction.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java index 5ba478c..bb661c0 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Transaction.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; /** * Represents a financial transaction of a {@link Share}. diff --git a/millions/src/main/java/no/ntnu/gruppe53/TransactionArchive.java b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java similarity index 99% rename from millions/src/main/java/no/ntnu/gruppe53/TransactionArchive.java rename to millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java index a6ca20d..1dc25ba 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/TransactionArchive.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.util.ArrayList; import java.util.HashSet; diff --git a/millions/src/main/java/no/ntnu/gruppe53/TransactionCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java similarity index 86% rename from millions/src/main/java/no/ntnu/gruppe53/TransactionCalculator.java rename to millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java index 0ad9274..96ef413 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/TransactionCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; diff --git a/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java b/millions/src/main/java/no/ntnu/gruppe53/view/FileHandler.java similarity index 98% rename from millions/src/main/java/no/ntnu/gruppe53/FileHandler.java rename to millions/src/main/java/no/ntnu/gruppe53/view/FileHandler.java index 13e26a6..f1d9ea2 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/FileHandler.java @@ -1,4 +1,6 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.view; + +import no.ntnu.gruppe53.model.Stock; import java.io.BufferedReader; import java.io.BufferedWriter; diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java similarity index 97% rename from millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java rename to millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java index 33f4b9d..959ffe6 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53.views; +package no.ntnu.gruppe53.view; import java.math.BigDecimal; import java.math.RoundingMode; @@ -16,9 +16,9 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; -import no.ntnu.gruppe53.Exchange; -import no.ntnu.gruppe53.Player; -import no.ntnu.gruppe53.Stock; +import no.ntnu.gruppe53.model.Exchange; +import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.model.Stock; public class MarketView extends BorderPane { diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java similarity index 97% rename from millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java rename to millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java index 724ccde..928a00f 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53.views; +package no.ntnu.gruppe53.view; import java.math.BigDecimal; import java.math.RoundingMode; @@ -13,8 +13,8 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; -import no.ntnu.gruppe53.Player; -import no.ntnu.gruppe53.Share; +import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.model.Share; public class PortfolioView extends BorderPane { diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java b/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java similarity index 97% rename from millions/src/main/java/no/ntnu/gruppe53/views/StartView.java rename to millions/src/main/java/no/ntnu/gruppe53/view/StartView.java index efbebf3..e47f07b 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53.views; +package no.ntnu.gruppe53.view; import javafx.geometry.Pos; import javafx.scene.control.Button; diff --git a/millions/src/test/java/no/ntnu/gruppe53/ExchangeTest.java b/millions/src/test/java/no/ntnu/gruppe53/ExchangeTest.java index 7167bd1..f4b43ef 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/ExchangeTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/ExchangeTest.java @@ -1,13 +1,16 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.Exchange; +import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.util.ArrayList; -import java.util.*; import static org.junit.jupiter.api.Assertions.*; diff --git a/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java b/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java index 8b860b9..c09fdc9 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java @@ -1,5 +1,7 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.Stock; +import no.ntnu.gruppe53.view.FileHandler; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/millions/src/test/java/no/ntnu/gruppe53/PlayerTest.java b/millions/src/test/java/no/ntnu/gruppe53/PlayerTest.java index 8e10787..845fb75 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/PlayerTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/PlayerTest.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.*; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/PortfolioTest.java b/millions/src/test/java/no/ntnu/gruppe53/PortfolioTest.java index 8534868..badccdf 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/PortfolioTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/PortfolioTest.java @@ -1,5 +1,9 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.Portfolio; +import no.ntnu.gruppe53.model.SaleCalculator; +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/PurchaseCalculatorTest.java b/millions/src/test/java/no/ntnu/gruppe53/PurchaseCalculatorTest.java index 961cc9f..0dba3f2 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/PurchaseCalculatorTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/PurchaseCalculatorTest.java @@ -1,5 +1,8 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.PurchaseCalculator; +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/millions/src/test/java/no/ntnu/gruppe53/PurchaseTest.java b/millions/src/test/java/no/ntnu/gruppe53/PurchaseTest.java index c6b5ba5..72816d8 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/PurchaseTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/PurchaseTest.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.*; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/SaleCalculatorUnitTest.java b/millions/src/test/java/no/ntnu/gruppe53/SaleCalculatorUnitTest.java index 6434eff..7b4ea9d 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/SaleCalculatorUnitTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/SaleCalculatorUnitTest.java @@ -1,5 +1,8 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.SaleCalculator; +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/millions/src/test/java/no/ntnu/gruppe53/SaleTest.java b/millions/src/test/java/no/ntnu/gruppe53/SaleTest.java index 2478d80..d7820fe 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/SaleTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/SaleTest.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.*; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/ShareTest.java b/millions/src/test/java/no/ntnu/gruppe53/ShareTest.java index 8e88b3b..ffb5e0f 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/ShareTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/ShareTest.java @@ -1,5 +1,7 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/StockTest.java b/millions/src/test/java/no/ntnu/gruppe53/StockTest.java index c9abf4c..69b4a15 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/StockTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/StockTest.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/TransactionArchiveTest.java b/millions/src/test/java/no/ntnu/gruppe53/TransactionArchiveTest.java index 8912ab3..39234d3 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/TransactionArchiveTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/TransactionArchiveTest.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.*; import org.junit.jupiter.api.Test; import java.math.BigDecimal; From 18ec4e778eb198be3db8bbf6e9e962378a9158c9 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 09:26:20 +0200 Subject: [PATCH 042/142] Updated Exchange Added Observables to the model for javafx. Added JavaDoc. Minor code fixes. --- .../java/no/ntnu/gruppe53/model/Exchange.java | 152 +++++++++++------- 1 file changed, 97 insertions(+), 55 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java b/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java index 731f770..8a5684a 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java @@ -1,16 +1,34 @@ package no.ntnu.gruppe53.model; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; import java.util.stream.Collectors; +/** + * Represents an exchange/market containing {@link Stock}s and allows a {@link Player} to buy {@link Share}s + * of said stocks. + *

Contains method to advance the week of the market, finding stocks, gainers, and losers on the exchange.

+ */ public class Exchange { private final String name; private int week; private final Map stockMap; private final Random random; + private final ObservableList stocks = FXCollections.observableArrayList(); + /** + * Sets the name of the exchange and the list of stocks to populate it. + *

Validates that the values are proper before creating the exchange.

+ * + * @param name the name of the exchange + * @param stocks the list of stocks to be tradeable on the exchange + * @throws IllegalArgumentException if exchange is null or blank, if list of stocks is null, or if + * the list of stocks contains a null or duplicate symbol stock + */ public Exchange(String name, List stocks) { if (name == null || name.isBlank()) { throw new IllegalArgumentException("Exchange name cannot be null or blank"); @@ -35,26 +53,45 @@ public Exchange(String name, List stocks) { } stockMap.put(stock.getSymbol(), stock); + this.stocks.add(stock); } - } + /** + * Returns the name of the exchange + * @return the name of the exchange + */ public String getName() { - return this.name; + return name; } + /** + * Returns the current week of the exchange + * + * @return the current week of the exchange + */ public int getWeek() { - return this.week; + return week; } - - // Returns Boolean value of if the exchange has the stock. + /** + * Checks whether a stock with the given symbol/ticker exists in the exchange. + *

Returns {@code true} if it does,v{@code false} otherwise.

+ * + * @param symbol the symbol/ticker of the stock + * @return {@code true} if the exchange contains the stock, otherwise {@code false} + */ public boolean hasStock(String symbol) { - return stockMap.containsKey(symbol); } - // Returns stock that corresponds to given symbol + /** + * Returns the stock object matching the given symbol/ticker. + * + * @param symbol the stock symbol/ticker + * @return the stock object matching the symbol/ticker + * @throws IllegalArgumentException if search symbol is null or symbol is not in stockMap + */ public Stock getStock(String symbol) { if (symbol == null || !stockMap.containsKey(symbol)) { throw new IllegalArgumentException("Stock not found: " + symbol); @@ -62,63 +99,78 @@ public Stock getStock(String symbol) { return stockMap.get(symbol); } + /** + * Returns an {@link ObservableList} of the list of stocks for JavaFX to observe. + * + * @return an observable list of the stocks in the exchange + */ + public ObservableList getObservableStocks() { + return stocks; + } + + /** + * Returns a list of stocks matching the search term in either the symbol/ticker or company name. + * + * @param searchTerm the string to search for + * @return a list of stocks matching the search term + */ public List findStocks(String searchTerm) { - //Wait for merging of other branches before implementation - if (searchTerm == null) { - return Collections.emptyList(); - } + if (searchTerm == null || searchTerm.isBlank()) return Collections.emptyList(); String search = searchTerm.toLowerCase(); - - return stockMap.values().stream().filter(stock -> - stock.getSymbol().toLowerCase().contains(search) || - stock.getCompany().toLowerCase().contains(search)).collect(Collectors.toList()); - + return stockMap.values().stream() + .filter(stock -> stock.getSymbol().toLowerCase().contains(search) || + stock.getCompany().toLowerCase().contains(search)) + .collect(Collectors.toList()); } + /** + * Represents a player buying a share on the exchange. + *

Creates a {@link Purchase} {@link Transaction} for the + * purchase.

+ * + * @param symbol the symbol/ticker of the stock + * @param quantity the quantity of shares to buy for said stock + * @param player the player making the purchase + * @return a purchase transaction containing all details of the purchase + * @throws IllegalArgumentException if player or quantity is null + */ public Transaction buy(String symbol, BigDecimal quantity, Player player) { - //Wait for merging of other branches before implementation - - if (player == null) { - throw new IllegalArgumentException("Player cannot be null"); - } - - if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) { + if (player == null) throw new IllegalArgumentException("Player cannot be null"); + if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) throw new IllegalArgumentException("Quantity must be positive"); - } Stock stock = getStock(symbol); - Share share = new Share(stock, quantity, stock.getSalesPrice()); - Transaction transaction = new Purchase(share, week); - transaction.commit(player); return transaction; - } + /** + * Represents a player selling a share on the exchange. + *

Creates a {@link Sale} transaction for the sale.

+ * + * @param share the share to be sold + * @param player the player selling the share + * @return a sale transaction containing all details of the sale + * @throws IllegalArgumentException if player or share is null + */ public Transaction sell(Share share, Player player) { - //Wait for merging of other branches before implementation - - - - if (player == null) { - throw new IllegalArgumentException("Player cannot be null"); - } - - if (share == null) { - throw new IllegalArgumentException("Share cannot be null"); - } + if (player == null) throw new IllegalArgumentException("Player cannot be null"); + if (share == null) throw new IllegalArgumentException("Share cannot be null"); Transaction transaction = new Sale(share, week); - transaction.commit(player); - return transaction; } + /** + * Advances the timeline of the exchange by 1 week. + *

Uses a randomized value for the increase and decrease of a stocks sales price, simulating market + * fluctuations on a weekly basis.

+ */ public void advance() { this.week++; @@ -126,13 +178,8 @@ public void advance() { double positivePriceChange = 0.1; for (Stock stock : stockMap.values()) { - BigDecimal currentPrice = stock.getSalesPrice(); - - // Bruh java gjør random shit rart as - double randomPriceMultiplier = negativePriceChange + - (positivePriceChange * random.nextDouble()); - + double randomPriceMultiplier = negativePriceChange + (positivePriceChange * random.nextDouble()); BigDecimal bdRandomPriceMultiplier = BigDecimal.valueOf(1 + randomPriceMultiplier); BigDecimal changedPrice = currentPrice.multiply(bdRandomPriceMultiplier) @@ -147,7 +194,7 @@ public void advance() { } /** - * Sorts the stocks in an ascending order and returns a list of stocks. + * Sorts the stocks in descending order and returns a list of stocks. * * @param entries amount of entries in the list to display * @param comparator the comparator used to compare the stocks @@ -155,10 +202,7 @@ public void advance() { * @throws IllegalArgumentException if the amount of entries is negative */ private List getTopStocks(int entries, Comparator comparator) { - if (entries < 0) { - throw new IllegalArgumentException("Amount of entries in list cannot be negative."); - } - + if (entries < 0) throw new IllegalArgumentException("Amount of entries in list cannot be negative."); return stockMap.values().stream() .sorted(comparator) .limit(entries) @@ -186,6 +230,4 @@ public List getGainers(int entries) { public List getLosers(int entries) { return getTopStocks(entries, Comparator.comparing(Stock::getLatestPriceChange)); } - - -} +} \ No newline at end of file From 03b867d36b379310ddc88a19fd6ace2de22f6e0b Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 09:34:49 +0200 Subject: [PATCH 043/142] Updated Stock Added Observables for salesPrice and a getter to fetch it for JavaFX. Changed the logic for getLatestPriceChange. --- .../java/no/ntnu/gruppe53/model/Stock.java | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java b/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java index 5d06d45..6e3ac66 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java @@ -1,5 +1,8 @@ package no.ntnu.gruppe53.model; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; @@ -21,12 +24,13 @@ * instantiated at construction. *

*/ - public class Stock { private final String symbol; private final String company; private final List prices; + private final ObjectProperty salesPrice; + /** * Creates a stock with an already defined sales price. * @@ -41,11 +45,9 @@ public Stock(String symbol, String company, BigDecimal salesPrice) { if (symbol == null || symbol.isBlank()) { throw new IllegalArgumentException("Symbol cannot be null or empty."); } - if (company == null || company.isBlank()) { throw new IllegalArgumentException("Company cannot be null or empty."); } - if (salesPrice == null || salesPrice.signum() <= 0) { throw new IllegalArgumentException("SalesPrice cannot be null, negative, or zero."); } @@ -54,6 +56,8 @@ public Stock(String symbol, String company, BigDecimal salesPrice) { this.company = company; this.prices = new ArrayList<>(); this.prices.add(salesPrice); + + this.salesPrice = new SimpleObjectProperty<>(salesPrice); } /** @@ -62,7 +66,7 @@ public Stock(String symbol, String company, BigDecimal salesPrice) { * @return the stock's ticker symbol */ public String getSymbol() { - return this.symbol; + return symbol; } /** @@ -71,7 +75,7 @@ public String getSymbol() { * @return the full name of the stock's company */ public String getCompany() { - return this.company; + return company; } /** @@ -82,7 +86,7 @@ public String getCompany() { * @return the current sale price of the stock */ public BigDecimal getSalesPrice() { - return this.prices.get(prices.size() - 1); + return salesPrice.get(); } /** @@ -102,6 +106,17 @@ public void addNewSalesPrice(BigDecimal price) { throw new IllegalArgumentException("Price cannot be null, negative, or zero."); } this.prices.add(price); + this.salesPrice.set(price); + } + + + /** + * Returns an {@link ObjectProperty} of the sales price. + * + * @return an observable of the sales price + */ + public ObjectProperty salesPriceProperty() { + return salesPrice; } /** @@ -110,7 +125,7 @@ public void addNewSalesPrice(BigDecimal price) { * @return a list of all the stock's sales prices */ public List getHistoricalPrices() { - return this.prices; + return prices; } /** @@ -119,7 +134,7 @@ public List getHistoricalPrices() { * @return the stock's highest sales price */ public BigDecimal getHighestPrice() { - return Collections.max(this.prices); + return Collections.max(prices); } /** @@ -128,7 +143,7 @@ public BigDecimal getHighestPrice() { * @return the stock's lowest sales price */ public BigDecimal getLowestPrice() { - return Collections.min(this.prices); + return Collections.min(prices); } /** @@ -138,11 +153,10 @@ public BigDecimal getLowestPrice() { * @return the net difference in sales price between the last and second last week */ public BigDecimal getLatestPriceChange() { - if (!(this.prices.size() < 2)) { - return this.prices.getLast().subtract(this.prices.get(this.prices.size() - 2)); - } - else { + if (prices.size() >= 2) { + return prices.getLast().subtract(prices.get(prices.size() - 2)); + } else { return BigDecimal.ZERO; } } -} +} \ No newline at end of file From 112a19ddb3a7e925991adada6295bce3c8a6f5ec Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 09:37:49 +0200 Subject: [PATCH 044/142] Updated Portfolio Added Observables for shares and net worth and methods to update and fetch them. Minor edits to removeShare. --- .../no/ntnu/gruppe53/model/Portfolio.java | 76 ++++++++++++------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java b/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java index 7744c57..1164f6b 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java @@ -3,6 +3,10 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; /** * Represents the portfolio of the Player, containing information @@ -13,20 +17,20 @@ *

* *

- * Shares are associated with unique Transaction, however, multiple + * Shares are associated with a unique Transaction, however, multiple * shares from the same stock can exist in the portfolio. *

*/ - public class Portfolio { - private final List shares; + private final ObservableList shares; + private final ObjectProperty netWorthProperty = + new SimpleObjectProperty<>(BigDecimal.ZERO); /** * Creates an empty portfolio for the player. */ - public Portfolio() { - this.shares = new ArrayList<>(); + this.shares = FXCollections.observableArrayList(); } /** @@ -41,7 +45,6 @@ public Portfolio() { * {@code false} otherwise * @throws IllegalArgumentException if {@code share} is {@code null} */ - public boolean addShare(Share share) { if (share == null) { throw new IllegalArgumentException("Share cannot be null"); @@ -49,7 +52,11 @@ public boolean addShare(Share share) { this.shares.add(share); - return this.getShares().getLast().equals(share); + share.getStock().salesPriceProperty() + .subscribe(price -> updateNetWorth()); + + updateNetWorth(); + return true; } /** @@ -65,17 +72,17 @@ public boolean addShare(Share share) { * @throws IllegalArgumentException if {@code share} is {@code null} */ public boolean removeShare(Share share) { - if (share == null) { + if (share == null) { throw new IllegalArgumentException("Share cannot be null"); } - for (Share s : this.shares) { - if (s.equals(share)) { - this.shares.remove(s); - return true; - } + boolean removed = this.shares.removeIf(s -> s.equals(share)); + + if (removed) { + updateNetWorth(); } - return false; + + return removed; } /** @@ -83,7 +90,7 @@ public boolean removeShare(Share share) { * * @return a list of the shares in this portfolio */ - public List getShares() { + public ObservableList getShares() { return this.shares; } @@ -98,7 +105,7 @@ public List getShares() { * @throws IllegalArgumentException if {@code symbol} is {@code null} or blank */ public List getShares(String symbol) { - if (symbol == null || symbol.isBlank()) { + if (symbol == null || symbol.isBlank()) { throw new IllegalArgumentException("Symbol cannot be null or blank"); } @@ -119,27 +126,40 @@ public List getShares(String symbol) { * @return {@code true} if the player's portfolio contains the share * {@code false} otherwise */ - public boolean contains(Share share) { - for (Share s : this.shares) { - if (s.equals(share)) { - return true; - } - } - return false; + return shares.stream().anyMatch(s -> s.equals(share)); } /** * Calculates the net total value of the shares in the portfolio. - *

Calculations are done using a {@link SaleCalculator}.

+ *

+ * Portfolio value is based on current market price, not transaction rules. + *

* - * @return the total sale value of the portfolio + * @return the total market value of the portfolio */ public BigDecimal getNetWorth() { - return this.shares.stream() - .map(share -> new SaleCalculator(share).calculateTotal()) + .map(share -> + share.getStock().getSalesPrice() + .multiply(share.getQuantity()) + ) .reduce(BigDecimal.ZERO, BigDecimal::add); } -} + /** + * Returns an {@link ObjectProperty} of the net worth of the portfolio + * + * @return an observable of the portfolio's net worth + */ + public ObjectProperty netWorthProperty() { + return netWorthProperty; + } + + /** + * Mirrors the portfolio's net worth to the observable. + */ + public void updateNetWorth() { + netWorthProperty.set(getNetWorth()); + } +} \ No newline at end of file From ae9a6b0ceccca24fe0fc94d0e814cac4cab86402 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 09:39:39 +0200 Subject: [PATCH 045/142] Updated Player Added Observables for money and net worth with getters. --- .../java/no/ntnu/gruppe53/model/Player.java | 111 +++++++++++------- 1 file changed, 67 insertions(+), 44 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Player.java b/millions/src/main/java/no/ntnu/gruppe53/model/Player.java index 84840dd..7c2610d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Player.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Player.java @@ -2,6 +2,8 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; /** * Represents a participant in the trading game. @@ -16,7 +18,6 @@ * lose funds through transactions. *

*/ - public class Player { private final String name; private final BigDecimal startingMoney; @@ -24,8 +25,12 @@ public class Player { private final Portfolio portfolio; private final TransactionArchive transactionArchive; + private final ObjectProperty moneyProperty = new SimpleObjectProperty<>(); + private final ObjectProperty netWorthProperty = new SimpleObjectProperty<>(); + /** * Creates a player with a given starting balance. + *

Adds a listener to the netWorth property to notify of changes in the player's net worth.

* * @param name the player's name; must not be {@code null} or blank * @param startingMoney the initial monetary balance of the @@ -34,30 +39,49 @@ public class Player { * blank, or if {@code startingMoney} * is {@code null}, negative, or 0 */ - public Player(String name, BigDecimal startingMoney) { if (name == null || name.isBlank()) { - throw new IllegalArgumentException("Player name must not be " + - "null or empty."); + throw new IllegalArgumentException("Player name must not be null or empty."); } if (startingMoney == null || startingMoney.signum() <= 0 ) { - throw new IllegalArgumentException("Starting money must not be " + - "null, negative, nor 0."); + throw new IllegalArgumentException("Starting money must not be null, negative, nor 0."); } + this.name = name; this.startingMoney = startingMoney; this.money = startingMoney; + this.moneyProperty.set(startingMoney); this.portfolio = new Portfolio(); this.transactionArchive = new TransactionArchive(); + + portfolio.netWorthProperty().addListener( + (obs, oldVal, newVal) -> updateNetWorth()); + updateNetWorth(); + } + + /** + * Returns an {@link ObjectProperty} of the player's net worth. + * + * @return an observable of the player's net worth + */ + public ObjectProperty getNetWorthProperty() { + return netWorthProperty; } + /** + * Mirrors the change in the player's net worth to the observable property. + */ + public void updateNetWorth() { + netWorthProperty.set(getNetWorth()); + } + + /** * Returns the full name of the player. * * @return the player's name */ - public String getName() { return this.name; } @@ -67,7 +91,6 @@ public String getName() { * * @return the player's current balance. */ - public BigDecimal getMoney() { return this.money; } @@ -82,17 +105,12 @@ public BigDecimal getMoney() { * @throws IllegalArgumentException if {@code amount] is {@code null} * or not positive */ - public void addMoney(BigDecimal amount) { - if (amount == null) { - throw new IllegalArgumentException("Amount must not be null."); - } - - if (amount.signum() <= 0) { - throw new IllegalArgumentException("Amount must be positive."); - } - + if (amount == null) throw new IllegalArgumentException("Amount must not be null."); + if (amount.signum() <= 0) throw new IllegalArgumentException("Amount must be positive."); this.money = this.money.add(amount); + this.moneyProperty.set(this.money); + updateNetWorth(); } /** @@ -106,29 +124,30 @@ public void addMoney(BigDecimal amount) { * @throws IllegalArgumentException if the amount is not positive, {@code null} * , or insufficient funds are available */ - public void withdrawMoney(BigDecimal amount) { - if (amount == null) { - throw new IllegalArgumentException("Amount must not be null."); - } - - if (amount.signum() <= 0) { - throw new IllegalArgumentException("Amount must be positive."); - } + if (amount == null) throw new IllegalArgumentException("Amount must not be null."); + if (amount.signum() <= 0) throw new IllegalArgumentException("Amount must be positive."); + if (this.money.subtract(amount).signum() < 0) throw new IllegalArgumentException("Insufficient funds."); + this.money = this.money.subtract(amount); + this.moneyProperty.set(this.money); + updateNetWorth(); + } - if (this.money.subtract(amount).signum() < 0) { - throw new IllegalArgumentException("Insufficient funds."); - } - this.money = this.money.subtract(amount); + /** + * Returns an observable of the player's current monetary funds + * @return an observable of the player's current money + */ + public ObjectProperty getMoneyProperty() { + return moneyProperty; } + /** * Return's the player's portfolio of shares. * * @return the player's portfolio */ - public Portfolio getPortfolio() { return this.portfolio; } @@ -138,28 +157,32 @@ public Portfolio getPortfolio() { * * @return the player's transaction archive */ - public TransactionArchive getTransactionArchive() { return this.transactionArchive; } + /** + * Returns the player's current net worth. + *

The net worth is the sum of the player's current money and all the value of their shares in their + * portfolio.

+ * + * @return the player's current net worth + */ public BigDecimal getNetWorth() { - return this.money.add(this.portfolio.getNetWorth()); + return money.add(portfolio.getNetWorth()); } + /** + * Returns the current status/title of the player, representing their achievements. + * + * @param exchange the exchange the player is trading at + * @return a string representing the players current status + */ public String getStatus(Exchange exchange) { String status = "Novice"; - - if ((getNetWorth().divide(this.startingMoney, RoundingMode.HALF_EVEN).compareTo(new BigDecimal("1.20")) - >= 0) && exchange.getWeek() >= 10) { - status = "Investor"; - } - - if ((getNetWorth().divide(this.startingMoney, RoundingMode.HALF_EVEN).compareTo(new BigDecimal("2")) - >= 0) && exchange.getWeek() >= 20) { - status = "Speculator"; - } - + BigDecimal ratio = getNetWorth().divide(this.startingMoney, RoundingMode.HALF_EVEN); + if (ratio.compareTo(new BigDecimal("1.20")) >= 0 && exchange.getWeek() >= 10) status = "Investor"; + if (ratio.compareTo(new BigDecimal("2")) >= 0 && exchange.getWeek() >= 20) status = "Speculator"; return status; } -} +} \ No newline at end of file From b4304f252e8aac24cd19f6df7208a434cf7f3ed4 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 09:42:01 +0200 Subject: [PATCH 046/142] Updated PurchaseCalculator Added JavaDoc. --- .../gruppe53/model/PurchaseCalculator.java | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java index 0f983ad..37fe66a 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java @@ -2,28 +2,19 @@ import java.math.BigDecimal; -/* -PurchaseCalculator class: - -Implements from TransactionCalculator - -Which contains: -1. Constructor which passes the values of a Share object - to its local variables. -2. calculateGross() function to calculate the gross value -3. calculateCommission() function to calculate the commission value -4. calculateTax() function which does nothing as there is no tax -5. calculateTotal() function which calculates the total purchase price - by adding all the calculations together. - +/** + * Calculates the costs associated with a {@link Purchase} of a given amount of {@link Share}s on the + * {@link Exchange}. */ public class PurchaseCalculator implements TransactionCalculator{ - private final BigDecimal purchasePrice; - private final BigDecimal quantity; - - - //Takes a Share object to pass its Purchase and Quantity Values to - //Its local private variables + private final BigDecimal purchasePrice; + private final BigDecimal quantity; + + /** + * Sets the purchase price and quantity based on data from the given share + * + * @param share the share to calculate purchase costs for + */ public PurchaseCalculator(Share share) { this.purchasePrice = share.getPurchasePrice(); @@ -31,32 +22,45 @@ public PurchaseCalculator(Share share) { } - // Multiplies quantity with the purchase price to get the gross value + /** + * Calculates the gross purchase price of the given share. + * + * @return the gross associated cost of purchasing the share + */ @Override public BigDecimal calculateGross(){ return this.quantity.multiply(this.purchasePrice); } - //Takes the gross value calculated in calculateGross() - //And multiplies it with the 0.05% commission on purchase to - //get the total value of the commission + /** + * Calculates the commission of the exchange for purchasing a share. + *

Commission is set to a fixed 0.5% of gross purchase price.

+ * + * @return the commission taken by the exchange + */ @Override public BigDecimal calculateCommission() { - //0.5% commission on purchase BigDecimal commission = new BigDecimal("0.005"); return calculateGross().multiply(commission); } - //No tax on purchase + /** + * Calculates the tax for a purchase. + *

For a purchase, there is no tax.

+ * + * @return the tax for a purchase, in this case zero + */ @Override public BigDecimal calculateTax() { return new BigDecimal(0); } - - //Calculates the total purchase price by adding - // Gross + Commission + Tax + /** + * Calculates the total costs associated with the purchase of a share + * + * @return the net total cost of purchasing a share + */ @Override public BigDecimal calculateTotal() { From fd03be4f27a2de3c93804a5da70ac677ec27bac5 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 09:44:44 +0200 Subject: [PATCH 047/142] Updated Sale Changed commit to throw IllegalStateException if player does not own the share. --- .../main/java/no/ntnu/gruppe53/model/Sale.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java b/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java index d8a6c36..2c72306 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java @@ -38,6 +38,7 @@ public Sale(Share share, int week) { *

* @param player the player performing the transaction; must not be {@code null} * @throws IllegalArgumentException if {@code player} is {@code null} + * @throws IllegalStateException if trying to sell a share not in the player's portfolio */ @Override @@ -46,12 +47,16 @@ public void commit(Player player) { throw new IllegalArgumentException("Player cannot be null"); } - if (!this.isCommitted() && (player.getPortfolio().contains(this.getShare())) - ) { - player.addMoney(this.getCalculator().calculateTotal()); - player.getPortfolio().removeShare(this.getShare()); - player.getTransactionArchive().add(this); - super.commit(player); + if (this.isCommitted()) { + throw new IllegalStateException("Transaction has already been committed."); } + if (!player.getPortfolio().contains(this.getShare())) { + throw new IllegalStateException("You do not own this share in your portfolio."); + } + player.addMoney(this.getCalculator().calculateTotal()); + player.getPortfolio().removeShare(this.getShare()); + player.getTransactionArchive().add(this); + super.commit(player); + } } From 8bc07e839efc43c99ba11194d12c77d4f4586863 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 09:45:16 +0200 Subject: [PATCH 048/142] Updated Sale Changed commit to throw IllegalStateException if player does not own the share. A --- millions/src/main/java/no/ntnu/gruppe53/model/Sale.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java b/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java index 2c72306..732ec1f 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java @@ -38,7 +38,8 @@ public Sale(Share share, int week) { *

* @param player the player performing the transaction; must not be {@code null} * @throws IllegalArgumentException if {@code player} is {@code null} - * @throws IllegalStateException if trying to sell a share not in the player's portfolio + * @throws IllegalStateException if trying to sell a share not in the player's portfolio or if transactions + * has already been committed. */ @Override From 0180fdf0129be11cd9c54fe59ff002e08e83acaf Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 09:54:26 +0200 Subject: [PATCH 049/142] Updates SaleCalculator Edited tax calculation logic and added JavaDoc. --- .../ntnu/gruppe53/model/SaleCalculator.java | 96 +++++++++++-------- 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java index cdc8371..872aa07 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java @@ -1,80 +1,92 @@ package no.ntnu.gruppe53.model; import java.math.BigDecimal; +import java.math.RoundingMode; -/* -SaleCalculator class: - -Implements from Interface TransactionCalculator - -Which contains: -1. Constructor which passes the values of a Share object - to its local variables. -2. calculateGross() function to calculate the gross value -3. calculateCommision() function to calculate the commission value -4. calculateTax() function which calculates the 30% tax on winnings -5. calculateTotal() function which calculates the total sales price - by subtracting commission and tax from the gross value - +/** + * Calculates the costs associated with the {@link Sale} of a {@link Share} on the {@link Exchange}, + * and the net total gain of selling said share. */ +public class SaleCalculator implements TransactionCalculator { - - -public class SaleCalculator implements TransactionCalculator{ private final BigDecimal purchasePrice; private final BigDecimal salesPrice; private final BigDecimal quantity; - //Takes a Share object to pass its Quantity, Purchase- and SalesPrice Values to - //Its local private variables + /** + * Sets the quantity, purchase and sales price based on the given share. + * + * @param share the share to calculate the costs and net total of a sale + */ public SaleCalculator(Share share) { this.purchasePrice = share.getPurchasePrice(); this.quantity = share.getQuantity(); this.salesPrice = share.getStock().getSalesPrice(); } - - //Multiplies quntity with the sales price to get the gross value + /** + * Calculates the gross sales price of the given share. + * + * @return the gross associated gain from selling the share + */ @Override public BigDecimal calculateGross() { - - return this.quantity.multiply(this.salesPrice); + return quantity.multiply(salesPrice) + .setScale(2, RoundingMode.HALF_UP); } - //Takes the gross value calculated in calculateGross() - //And multiplies it with the 0.1% commission on sale to - //get the total value of the commission + /** + * Calculates the exchange's commission for selling the share. + *

The commission is set to a fixed 1% of the gross total sale value.

+ * + * @return the commission taken by the exchange + */ @Override public BigDecimal calculateCommission() { - //0.1% commission - BigDecimal commision = new BigDecimal("0.01"); + BigDecimal commissionRate = new BigDecimal("0.01"); - return calculateGross().multiply(commision); + return calculateGross() + .multiply(commissionRate) + .setScale(2, RoundingMode.HALF_UP); } - - //Calculates the tax on winnings, winnings are defined as - //Gross - commission - (purchaseprice * quantity) - //Tax is therefore calculated as winnings * tax(30%) + /** + * Calculates the taxable profit from the sale. + *

Only calculates tax for profitable sales.

> + * + * @return the tax amount + */ @Override public BigDecimal calculateTax() { + BigDecimal taxRate = new BigDecimal("0.30"); - BigDecimal tax = new BigDecimal("0.30"); + BigDecimal originalCost = purchasePrice.multiply(quantity); - // (Gross - commission - (Purchase price - quantity)) * 30% tax - return calculateGross().subtract(calculateCommission()) - .subtract(this.purchasePrice.multiply(this.quantity)).multiply(tax); + BigDecimal profit = calculateGross() + .subtract(calculateCommission()) + .subtract(originalCost); + if (profit.compareTo(BigDecimal.ZERO) <= 0) { + return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); + } + + return profit.multiply(taxRate) + .setScale(2, RoundingMode.HALF_UP); } - //Calculates the total sales price by subtracting - //Gross - Commission - Tax + /** + * Returns the net total gain for selling the share. + * + * @return the net total winnings of selling the share + */ @Override public BigDecimal calculateTotal() { - return calculateGross().subtract(calculateCommission()).subtract(calculateTax()); - + return calculateGross() + .subtract(calculateCommission()) + .subtract(calculateTax()) + .setScale(2, RoundingMode.HALF_UP); } -} +} \ No newline at end of file From d772585008f7fa94fa228d15661d8c5a822c0499 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 09:57:37 +0200 Subject: [PATCH 050/142] Updated TransactionArchive Made archive Observable. --- .../no/ntnu/gruppe53/model/TransactionArchive.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java index 1dc25ba..70bba67 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java @@ -1,5 +1,8 @@ package no.ntnu.gruppe53.model; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -14,14 +17,14 @@ */ public class TransactionArchive { - private final List transactions; + private final ObservableList transactions; /** * Creates an empty transaction archive. */ public TransactionArchive() { - this.transactions = new ArrayList<>(); + this.transactions = FXCollections.observableArrayList(); } /** @@ -60,6 +63,10 @@ public boolean isEmpty() { * @return the list of transactions */ + public ObservableList getObservableTransactions() { + return this.transactions; + } + public List getTransactions() { return this.transactions; } From 13a105e04bb2c3ddecd5e4229cb882fe65c7f86a Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 09:58:33 +0200 Subject: [PATCH 051/142] Updated TransactionCalculator Added JavaDoc. --- .../ntnu/gruppe53/model/TransactionCalculator.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java index 96ef413..511cdfc 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java @@ -2,10 +2,14 @@ import java.math.BigDecimal; +/** + * Represents the necessary calculations to perform a transaction. + *

{@link Purchase} and {@link Sale} are examples of implementations of a transaction.

+ */ public interface TransactionCalculator { - BigDecimal calculateGross(); - BigDecimal calculateCommission(); - BigDecimal calculateTax(); - BigDecimal calculateTotal(); + BigDecimal calculateGross(); + BigDecimal calculateCommission(); + BigDecimal calculateTax(); + BigDecimal calculateTotal(); } From 3e8be93965e52ecec50f9984e2ac136eb3883376 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:00:40 +0200 Subject: [PATCH 052/142] Refactor of test classes Refactored into package structure that reflects structure of main code. --- .../java/no/ntnu/gruppe53/{ => model}/ExchangeTest.java | 6 +----- .../test/java/no/ntnu/gruppe53/{ => model}/PlayerTest.java | 3 +-- .../java/no/ntnu/gruppe53/{ => model}/PortfolioTest.java | 6 +----- .../ntnu/gruppe53/{ => model}/PurchaseCalculatorTest.java | 5 +---- .../java/no/ntnu/gruppe53/{ => model}/PurchaseTest.java | 3 +-- .../ntnu/gruppe53/{ => model}/SaleCalculatorUnitTest.java | 5 +---- .../test/java/no/ntnu/gruppe53/{ => model}/SaleTest.java | 3 +-- .../test/java/no/ntnu/gruppe53/{ => model}/ShareTest.java | 4 +--- .../test/java/no/ntnu/gruppe53/{ => model}/StockTest.java | 3 +-- .../ntnu/gruppe53/{ => model}/TransactionArchiveTest.java | 3 +-- .../no/ntnu/gruppe53/{ => service}/FileHandlerTest.java | 2 +- 11 files changed, 11 insertions(+), 32 deletions(-) rename millions/src/test/java/no/ntnu/gruppe53/{ => model}/ExchangeTest.java (97%) rename millions/src/test/java/no/ntnu/gruppe53/{ => model}/PlayerTest.java (99%) rename millions/src/test/java/no/ntnu/gruppe53/{ => model}/PortfolioTest.java (98%) rename millions/src/test/java/no/ntnu/gruppe53/{ => model}/PurchaseCalculatorTest.java (90%) rename millions/src/test/java/no/ntnu/gruppe53/{ => model}/PurchaseTest.java (98%) rename millions/src/test/java/no/ntnu/gruppe53/{ => model}/SaleCalculatorUnitTest.java (92%) rename millions/src/test/java/no/ntnu/gruppe53/{ => model}/SaleTest.java (99%) rename millions/src/test/java/no/ntnu/gruppe53/{ => model}/ShareTest.java (97%) rename millions/src/test/java/no/ntnu/gruppe53/{ => model}/StockTest.java (99%) rename millions/src/test/java/no/ntnu/gruppe53/{ => model}/TransactionArchiveTest.java (99%) rename millions/src/test/java/no/ntnu/gruppe53/{ => service}/FileHandlerTest.java (99%) diff --git a/millions/src/test/java/no/ntnu/gruppe53/ExchangeTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/ExchangeTest.java similarity index 97% rename from millions/src/test/java/no/ntnu/gruppe53/ExchangeTest.java rename to millions/src/test/java/no/ntnu/gruppe53/model/ExchangeTest.java index f4b43ef..d1adf0f 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/ExchangeTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/ExchangeTest.java @@ -1,10 +1,6 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; -import no.ntnu.gruppe53.model.Exchange; -import no.ntnu.gruppe53.model.Player; -import no.ntnu.gruppe53.model.Share; -import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/millions/src/test/java/no/ntnu/gruppe53/PlayerTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/PlayerTest.java similarity index 99% rename from millions/src/test/java/no/ntnu/gruppe53/PlayerTest.java rename to millions/src/test/java/no/ntnu/gruppe53/model/PlayerTest.java index 845fb75..60b8ac2 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/PlayerTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/PlayerTest.java @@ -1,6 +1,5 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; -import no.ntnu.gruppe53.model.*; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/PortfolioTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/PortfolioTest.java similarity index 98% rename from millions/src/test/java/no/ntnu/gruppe53/PortfolioTest.java rename to millions/src/test/java/no/ntnu/gruppe53/model/PortfolioTest.java index badccdf..81cc15e 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/PortfolioTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/PortfolioTest.java @@ -1,9 +1,5 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; -import no.ntnu.gruppe53.model.Portfolio; -import no.ntnu.gruppe53.model.SaleCalculator; -import no.ntnu.gruppe53.model.Share; -import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/PurchaseCalculatorTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/PurchaseCalculatorTest.java similarity index 90% rename from millions/src/test/java/no/ntnu/gruppe53/PurchaseCalculatorTest.java rename to millions/src/test/java/no/ntnu/gruppe53/model/PurchaseCalculatorTest.java index 0dba3f2..ab6da19 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/PurchaseCalculatorTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/PurchaseCalculatorTest.java @@ -1,8 +1,5 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; -import no.ntnu.gruppe53.model.PurchaseCalculator; -import no.ntnu.gruppe53.model.Share; -import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/millions/src/test/java/no/ntnu/gruppe53/PurchaseTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/PurchaseTest.java similarity index 98% rename from millions/src/test/java/no/ntnu/gruppe53/PurchaseTest.java rename to millions/src/test/java/no/ntnu/gruppe53/model/PurchaseTest.java index 72816d8..f235b53 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/PurchaseTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/PurchaseTest.java @@ -1,6 +1,5 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; -import no.ntnu.gruppe53.model.*; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/SaleCalculatorUnitTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/SaleCalculatorUnitTest.java similarity index 92% rename from millions/src/test/java/no/ntnu/gruppe53/SaleCalculatorUnitTest.java rename to millions/src/test/java/no/ntnu/gruppe53/model/SaleCalculatorUnitTest.java index 7b4ea9d..7c67885 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/SaleCalculatorUnitTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/SaleCalculatorUnitTest.java @@ -1,8 +1,5 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; -import no.ntnu.gruppe53.model.SaleCalculator; -import no.ntnu.gruppe53.model.Share; -import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/millions/src/test/java/no/ntnu/gruppe53/SaleTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/SaleTest.java similarity index 99% rename from millions/src/test/java/no/ntnu/gruppe53/SaleTest.java rename to millions/src/test/java/no/ntnu/gruppe53/model/SaleTest.java index d7820fe..86a38c5 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/SaleTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/SaleTest.java @@ -1,6 +1,5 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; -import no.ntnu.gruppe53.model.*; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/ShareTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/ShareTest.java similarity index 97% rename from millions/src/test/java/no/ntnu/gruppe53/ShareTest.java rename to millions/src/test/java/no/ntnu/gruppe53/model/ShareTest.java index ffb5e0f..125762a 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/ShareTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/ShareTest.java @@ -1,7 +1,5 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; -import no.ntnu.gruppe53.model.Share; -import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/StockTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/StockTest.java similarity index 99% rename from millions/src/test/java/no/ntnu/gruppe53/StockTest.java rename to millions/src/test/java/no/ntnu/gruppe53/model/StockTest.java index 69b4a15..38c96cb 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/StockTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/StockTest.java @@ -1,6 +1,5 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; -import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/TransactionArchiveTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/TransactionArchiveTest.java similarity index 99% rename from millions/src/test/java/no/ntnu/gruppe53/TransactionArchiveTest.java rename to millions/src/test/java/no/ntnu/gruppe53/model/TransactionArchiveTest.java index 39234d3..cb39777 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/TransactionArchiveTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/TransactionArchiveTest.java @@ -1,6 +1,5 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; -import no.ntnu.gruppe53.model.*; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java b/millions/src/test/java/no/ntnu/gruppe53/service/FileHandlerTest.java similarity index 99% rename from millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java rename to millions/src/test/java/no/ntnu/gruppe53/service/FileHandlerTest.java index c09fdc9..55e6133 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/service/FileHandlerTest.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.service; import no.ntnu.gruppe53.model.Stock; import no.ntnu.gruppe53.view.FileHandler; From 12d837b7f154d33d86da383f681f05e32ec249cb Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:02:17 +0200 Subject: [PATCH 053/142] Updated ExchangeTest Added tests for coverage. --- .../no/ntnu/gruppe53/model/ExchangeTest.java | 141 +++++++++++++++++- 1 file changed, 140 insertions(+), 1 deletion(-) diff --git a/millions/src/test/java/no/ntnu/gruppe53/model/ExchangeTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/ExchangeTest.java index d1adf0f..128badd 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/model/ExchangeTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/ExchangeTest.java @@ -1,16 +1,18 @@ package no.ntnu.gruppe53.model; +import javafx.collections.ObservableList; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; -@DisplayName("Exhange Test") +@DisplayName("Exchange Test") public class ExchangeTest { private Stock appleStock; @@ -204,4 +206,141 @@ void getTopStocksShouldThrowIllegalArgumentExceptionsIfEntriesIsNegative() { } + @Test + void nullExchangeNameShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new Exchange(null, stocks), + "Exchange name should not be allowed to be null."); + } + + @Test + void blankExchangeNameShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new Exchange(" ", stocks), + "Exchange name should not be allowed to be blank."); + } + + @Test + void nullStockListShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new Exchange("Exchange", null), + "Stocklist should not be allowed to be null."); + } + + @Test + void nullStockInListShouldThrowIllegalArgumentException() { + stocks.add(null); + + assertThrows(IllegalArgumentException.class, () -> new Exchange("Exchange", stocks), + "Exchange should not be allowed to contain a null value for a stock."); + } + + @Test + void duplicateStockSymbolInExchangeShouldThrowIllegalArgumentException() { + stocks.add(new Stock("AAPL", "Eple", new BigDecimal("50"))); + + assertThrows(IllegalArgumentException.class, () -> new Exchange("Exchange", stocks), + "Duplicate stock symbol should not be allowed"); + } + + @Test + void getNameShouldReturnExchangeName() { + assertEquals("Nasdaq", exchange.getName(), + "Exchange name should be correct."); + } + + @Test + void getStockShouldReturnStock() { + assertEquals(microStock, exchange.getStock("MSFT"), + "Should return the correct stock based on the ticker symbol."); + } + + @Test + void getStockForNullStockShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> exchange.getStock(null), + "Searching using null stock should throw an exception."); + } + + @Test + void getStockForStockNotInExchangeShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> exchange.getStock("AAAA"), + "Searching for stock not in exchange should throw an exception."); + } + + @Test + void getObservableListShouldReturnAnObservableList() { + assertInstanceOf(ObservableList.class, exchange.getObservableStocks(), + "Returned object should be an observableList."); + } + + @Test + void findStocksForNullStringShouldGiveEmptyList() { + List listStocks = exchange.findStocks(null); + + assertTrue(listStocks.isEmpty(), "List should be empty."); + } + + @Test + void findStocksForBlankStringShouldGiveEmptyList() { + List listStocks = exchange.findStocks(" "); + + assertTrue(listStocks.isEmpty(), "List should be empty."); + } + + @Test + void findStocksShouldReturnRelevantStock() { + List listStocks = exchange.findStocks("MSFT"); + + assertEquals(listStocks.getFirst(), microStock, "Should return the correct stock."); + } + + @Test + void findStocksShouldReturnStocksThatContainString() { + List listStocks = exchange.findStocks("T"); + + assertEquals(2, listStocks.size(), "Should return correct amount of stocks."); + assertEquals(listStocks.getFirst(), microStock, "Should return the correct first stock."); + assertEquals(listStocks.get(1), ntnuStock, "Should return the correct second stock."); + } + + @Test + void buyWithNullPlayerShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> exchange.buy( + "AAPL", new BigDecimal("1"), null), + "Should throw if player is null."); + } + + @Test + void buyWithNullQuantityShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> exchange.buy( + "AAPL", null, testPlayer), + "Should throw if quantity is null."); + } + + @Test + void buyWithZeroQuantityShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> exchange.buy( + "AAPL", BigDecimal.ZERO, testPlayer), + "Should throw if quantity is zero."); + } + + @Test + void buyWithNegativeQuantityShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> exchange.buy( + "AAPL", new BigDecimal("-1"), testPlayer), + "Should throw if quantity is negative."); + } + + @Test + void SellWithNullPlayerShouldThrowIllegalArgumentException() { + testPlayer.getPortfolio().addShare(appleShare); + + assertThrows(IllegalArgumentException.class, () -> exchange.sell(appleShare, null), + "Should throw if player is null."); + } + + + @Test + void sellWithNullShareShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> exchange.sell(null,testPlayer), + "Should throw if share is null."); + } + } From 2b4146e3ba6e9c4f2e266822328657d1b5b77397 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:03:24 +0200 Subject: [PATCH 054/142] Updated PlayerTest Added tests for coverage. --- .../no/ntnu/gruppe53/model/PlayerTest.java | 165 +++++++++++++++++- 1 file changed, 160 insertions(+), 5 deletions(-) diff --git a/millions/src/test/java/no/ntnu/gruppe53/model/PlayerTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/PlayerTest.java index 60b8ac2..49fa6fa 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/model/PlayerTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/PlayerTest.java @@ -4,6 +4,8 @@ import java.math.BigDecimal; import java.util.ArrayList; +import java.util.List; +import javafx.beans.property.ObjectProperty; import static org.junit.jupiter.api.Assertions.*; @@ -335,13 +337,13 @@ void week1PlayerShouldHaveStatusNovice() { @Test void week10OrMorePlayerWithOver20PercentGainShouldHaveStatusInvestor() { String name = "Bob"; - var startingMoney = new BigDecimal("2000"); + var startingMoney = new BigDecimal("100"); var testPlayer = new Player(name, startingMoney); String symbol1 = "AAPL"; String company1 = "Apple Inc."; - BigDecimal price1 = new BigDecimal("100"); + BigDecimal price1 = new BigDecimal("40"); Stock testStock1 = new Stock(symbol1,company1, price1); @@ -354,7 +356,6 @@ void week10OrMorePlayerWithOver20PercentGainShouldHaveStatusInvestor() { Exchange exchangeTest = new Exchange("test", stockList); - purchasePrice1 = new BigDecimal("3000"); Share testShare1 = new Share(testStock1, quantity1, purchasePrice1); testPlayer.getPortfolio().addShare(testShare1); @@ -365,8 +366,6 @@ void week10OrMorePlayerWithOver20PercentGainShouldHaveStatusInvestor() { assertEquals("Investor", testPlayer.getStatus(exchangeTest), "Since gain is over 20% and " + "week is 10, player should get status 'investor'."); - assertEquals("Investor", testPlayer.getStatus(exchangeTest), "Since gain is under 100% and " + - "week is 20, player should get status 'investor' and not 'speculator'."); } @Test @@ -443,4 +442,160 @@ void week20OrMorePlayerWithTwiceNetGainShouldHaveStatusSpeculator() { assertEquals("Speculator", testPlayer.getStatus(exchangeTest), "Should be Speculator as" + " the player has earned more than twice the starting money and the week is over 20."); } + + @Test + void Week20OrMorePlayerWithNoGainShouldStillHaveNoviceStatus() { + String symbol1 = "AAPL"; + String company1 = "Apple Inc."; + BigDecimal price1 = new BigDecimal("100"); + String name = "Bob"; + var startingMoney = new BigDecimal("2000"); + + Stock testStock1 = new Stock(symbol1,company1, price1); + List stockList = List.of(testStock1); + Exchange exchangeTest = new Exchange("test", stockList); + System.out.println(exchangeTest.getWeek()); + + for (int i = 0; i < 11; i++) { + exchangeTest.advance(); + } + + var testPlayer = new Player(name, startingMoney); + System.out.println(exchangeTest.getWeek()); + + assertEquals("Novice", testPlayer.getStatus(exchangeTest), "Should be Novice"); + } + + @Test + void lessThanWeek10WeekPlayerWithTwiceGainShouldHaveStatusNovice() { + String name = "Bob"; + var startingMoney = new BigDecimal("2000"); + + var testPlayer = new Player(name, startingMoney); + + String symbol1 = "AAPL"; + String company1 = "Apple Inc."; + BigDecimal price1 = new BigDecimal("100"); + + Stock testStock1 = new Stock(symbol1,company1, price1); + + BigDecimal quantity1 = new BigDecimal("10"); + var purchasePrice1 = testStock1.getSalesPrice(); + + Share testShare1 = new Share(testStock1, quantity1, purchasePrice1); + + String symbol2 = "MSFT"; + String company2 = "Microsoft Corporation"; + BigDecimal price2 = new BigDecimal("87"); + + Stock testStock2 = new Stock(symbol2, company2, price2); + + BigDecimal quantity2 = new BigDecimal("3"); + var purchasePrice2 = testStock1.getSalesPrice(); + + Share testShare2 = new Share(testStock2, quantity2, purchasePrice2); + + String symbol3 = "GOOG"; + String company3 = "Alphabet Inc."; + BigDecimal price3 = new BigDecimal("75"); + + Stock testStock3 = new Stock(symbol3, company3, price3); + + BigDecimal quantity3 = new BigDecimal("7"); + var purchasePrice3 = testStock3.getSalesPrice(); + + Share testShare3 = new Share(testStock3, quantity3, purchasePrice3); + + Portfolio testPortfolio = new Portfolio(); + testPortfolio.addShare(testShare1); + testPortfolio.addShare(testShare2); + testPortfolio.addShare(testShare3); + + testPlayer.getPortfolio().addShare(testShare1); + testPlayer.getPortfolio().addShare(testShare2); + testPlayer.getPortfolio().addShare(testShare3); + + for (int i = 0; i < 200; i++) { + testPlayer.getPortfolio().addShare(testShare3); + } + + var stockList = new ArrayList(); + + stockList.add(testStock1); + stockList.add(testStock2); + stockList.add(testStock3); + + Exchange exchangeTest = new Exchange("test", stockList); + + for (int i = 0; i < 5; i++) { + exchangeTest.advance(); + } + + assertEquals("Novice", testPlayer.getStatus(exchangeTest), + "Should be novice."); + } + + @Test + void week10OrMorePlayerWithLessThan20PercentGainShouldHaveStatusNovice() { + String name = "Bob"; + BigDecimal startingMoney = new BigDecimal("2000"); + + Player testPlayer = new Player(name, startingMoney); + + String symbol = "AAPL"; + String company = "Apple Inc."; + BigDecimal price = new BigDecimal("100"); + + Stock stock = new Stock(symbol, company, price); + + BigDecimal quantity = new BigDecimal("2"); + + Share share = new Share(stock, quantity, stock.getSalesPrice()); + + testPlayer.getPortfolio().addShare(share); + + Exchange exchange = new Exchange("test", List.of(stock)); + + for (int i = 0; i < 10; i++) { + exchange.advance(); + } + + assertEquals("Novice", testPlayer.getStatus(exchange)); + } + + @Test + void getNetWorthPropertyShouldReturnObservable() { + String name = "Bob"; + var startingMoney = new BigDecimal("2000"); + + String symbol = "AAPL"; + String company = "Apple Inc."; + BigDecimal price = new BigDecimal("100"); + + Stock stock = new Stock(symbol, company, price); + + BigDecimal quantity = new BigDecimal("2"); + Share share = new Share(stock, quantity, stock.getSalesPrice()); + Player testPlayer = new Player(name, startingMoney); + + testPlayer.getPortfolio().addShare(share); + + assertInstanceOf(ObjectProperty.class, testPlayer.getNetWorthProperty(), + "Should be Observable"); + assertEquals(testPlayer.getNetWorth(), testPlayer.getNetWorthProperty().getValue(), + "Should have correct net worth value."); + } + + @Test + void getMoneyPropertyShouldReturnObservable() { + String name = "Bob"; + var startingMoney = new BigDecimal("2000"); + + Player testPlayer = new Player(name, startingMoney); + + assertInstanceOf(ObjectProperty.class, testPlayer.getMoneyProperty(), + "Should be Observable."); + assertEquals(testPlayer.getMoney(), testPlayer.getMoneyProperty().getValue(), + "Should have correct money value."); + } } \ No newline at end of file From a34ddf70a5e11a65f131cf5abdad232fb39330ff Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:04:54 +0200 Subject: [PATCH 055/142] Updated PortfolioTest Fixed test for net worth. --- .../no/ntnu/gruppe53/model/PortfolioTest.java | 53 ++++--------------- 1 file changed, 10 insertions(+), 43 deletions(-) diff --git a/millions/src/test/java/no/ntnu/gruppe53/model/PortfolioTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/PortfolioTest.java index 81cc15e..9549487 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/model/PortfolioTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/PortfolioTest.java @@ -295,54 +295,21 @@ void containsShouldReturnFalseIfPortfolioDoesNotContainShare() { } @Test - void getNetWorthShouldReturnCorrectSaleValue() { - String symbol1 = "AAPL"; - String company1 = "Apple Inc."; - BigDecimal price1 = new BigDecimal("100"); - - Stock testStock1 = new Stock(symbol1,company1, price1); - - BigDecimal quantity1 = new BigDecimal("10"); - var purchasePrice1 = testStock1.getSalesPrice(); - - Share testShare1 = new Share(testStock1, quantity1, purchasePrice1); + void getNetWorthShouldReturnMarketValue() { - String symbol2 = "MSFT"; - String company2 = "Microsoft Corporation"; - BigDecimal price2 = new BigDecimal("87"); + Stock stock = new Stock("AAPL", "Apple Inc.", new BigDecimal("100")); - Stock testStock2 = new Stock(symbol2, company2, price2); + Share share = new Share(stock, new BigDecimal("10"), stock.getSalesPrice()); - BigDecimal quantity2 = new BigDecimal("3"); - var purchasePrice2 = testStock1.getSalesPrice(); - - Share testShare2 = new Share(testStock2, quantity2, purchasePrice2); - - String symbol3 = "GOOG"; - String company3 = "Alphabet Inc."; - BigDecimal price3 = new BigDecimal("75"); - - Stock testStock3 = new Stock(symbol3, company3, price3); - - BigDecimal quantity3 = new BigDecimal("7"); - var purchasePrice3 = testStock3.getSalesPrice(); - - Share testShare3 = new Share(testStock3, quantity3, purchasePrice3); - - Portfolio testPortfolio = new Portfolio(); - - testPortfolio.addShare(testShare1); - testPortfolio.addShare(testShare2); - testPortfolio.addShare(testShare3); + Portfolio portfolio = new Portfolio(); + portfolio.addShare(share); - var calcWorth = BigDecimal.ZERO; - calcWorth = calcWorth.add(new SaleCalculator(testShare1).calculateTotal()); - calcWorth = calcWorth.add(new SaleCalculator(testShare2).calculateTotal()); - calcWorth = calcWorth.add(new SaleCalculator(testShare3).calculateTotal()); + BigDecimal expected = stock.getSalesPrice() + .multiply(share.getQuantity()); - assertEquals(0, testPortfolio.getNetWorth().compareTo(calcWorth), - "Calculated net worth of the portfolio should equal the " + - "som of all sales total values."); + assertEquals(0, + portfolio.getNetWorth().compareTo(expected), + "Net worth should equal market value of shares."); } @Test From a7a3dc21cef904b2932aa7b4144c85d2dd1d77f3 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:14:47 +0200 Subject: [PATCH 056/142] Updated SaleCalculatorUnitTest.java Renamed to SaleCalculatorTest to match name format. --- .../{SaleCalculatorUnitTest.java => SaleCalculatorTest.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename millions/src/test/java/no/ntnu/gruppe53/model/{SaleCalculatorUnitTest.java => SaleCalculatorTest.java} (97%) diff --git a/millions/src/test/java/no/ntnu/gruppe53/model/SaleCalculatorUnitTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/SaleCalculatorTest.java similarity index 97% rename from millions/src/test/java/no/ntnu/gruppe53/model/SaleCalculatorUnitTest.java rename to millions/src/test/java/no/ntnu/gruppe53/model/SaleCalculatorTest.java index 7c67885..d5cc6b1 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/model/SaleCalculatorUnitTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/SaleCalculatorTest.java @@ -11,7 +11,7 @@ //The need to setScale(2) at all the BigDecimals is to ensure that assertEquals works @DisplayName("Sale Calculator Test") -public class SaleCalculatorUnitTest { +public class SaleCalculatorTest { private Stock testStock = new Stock("AAPL", "Apple", new BigDecimal(100)); From ff8774c3616f25057320277a4052786650bed3be Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:15:57 +0200 Subject: [PATCH 057/142] Updated SaleTest AssertThrows for IllegalStateException. --- .../test/java/no/ntnu/gruppe53/model/SaleTest.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/millions/src/test/java/no/ntnu/gruppe53/model/SaleTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/SaleTest.java index 86a38c5..3343078 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/model/SaleTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/SaleTest.java @@ -140,7 +140,9 @@ void saleShouldNotBeCommittedIfShareDoesNotExistInPortfolio() { var testSale = new Sale(testShare1, 1); - testSale.commit(testPlayer); + assertThrows(IllegalStateException.class, () -> { + testSale.commit(testPlayer); + }, "Sale should not be possible to commit as the player does not own the share."); assertEquals(0, startCash.compareTo(testPlayer.getMoney()), "Money should remain unchanged."); @@ -170,11 +172,10 @@ void saleShouldNotBeCommittedIfAlreadyCommited() { testSale.commit(testPlayer1); assertTrue(testSale.isCommitted(), "Sale should be committed."); - testSale.commit(testPlayer2); - - System.out.println(testPlayer1.getMoney()); - assertEquals(0, new BigDecimal("1093").compareTo(testPlayer1.getMoney()), + assertThrows(IllegalStateException.class, () -> testSale.commit(testPlayer2), + "Sale should not be committed a second time."); + assertEquals(0, new BigDecimal("1090").compareTo(testPlayer1.getMoney()), "Player 1 should have correct balance after sale."); assertEquals(0,startCash.compareTo(testPlayer2.getMoney()), "Money should not change for player 2."); @@ -204,8 +205,9 @@ void saleShouldNotBePossibleToCommitTwice() { testSale.commit(testPlayer); assertTrue(testSale.isCommitted(), "Sale should be committed."); BigDecimal cashAfterSale = testPlayer.getMoney(); - testSale.commit(testPlayer); + assertThrows(IllegalStateException.class, () -> testSale.commit(testPlayer), + "Sale should not be committed a second time."); assertEquals(0, cashAfterSale.compareTo(testPlayer.getMoney()), "Money should remain unchanged after second commit."); assertTrue(testSale.isCommitted(), "Sale should still be committed."); From 4687be87fa5811df62772f434e52afef4aee68af Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:17:42 +0200 Subject: [PATCH 058/142] Updated StockTest Fixed test for priceChange --- .../java/no/ntnu/gruppe53/model/StockTest.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/millions/src/test/java/no/ntnu/gruppe53/model/StockTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/StockTest.java index 38c96cb..b2ae160 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/model/StockTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/StockTest.java @@ -225,7 +225,7 @@ void getLowestPriceShouldReturnTheLowestPrice() { } @Test - void getLatestPriceChange() { + void getLatestPriceChangeShouldBeCorrect() { String symbol = "AAPL"; String company = "Apple Inc."; BigDecimal price1 = new BigDecimal("100"); @@ -239,8 +239,20 @@ void getLatestPriceChange() { testStock.addNewSalesPrice(price4); assertEquals(0, testStock.getLatestPriceChange().compareTo( - price4.subtract(price3)), + price4.subtract(price3)), "The price difference should be equal to the last value" + " subtracted from the second last value (i.e. around '-25'."); } + + @Test + void getLatestPriceChangeForPriceHistoryLessThan2ShouldGiveZero() { + String symbol = "AAPL"; + String company = "Apple Inc."; + BigDecimal price = new BigDecimal("100"); + + Stock testStock = new Stock(symbol,company, price); + + assertEquals(BigDecimal.ZERO, testStock.getLatestPriceChange(), + "Price change is zero for only 1 registered price."); + } } \ No newline at end of file From 73a7c6e3b366d7d86421e8958046f7edbcc69233 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:18:39 +0200 Subject: [PATCH 059/142] Updated TransactionArchiveTest Added test for Observable. --- .../no/ntnu/gruppe53/model/TransactionArchiveTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/millions/src/test/java/no/ntnu/gruppe53/model/TransactionArchiveTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/TransactionArchiveTest.java index cb39777..663babe 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/model/TransactionArchiveTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/TransactionArchiveTest.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53.model; +import javafx.collections.ObservableList; import org.junit.jupiter.api.Test; import java.math.BigDecimal; @@ -468,4 +469,12 @@ void countDistinctWeeksShouldNotCountDuplicateWeekNumbers() { assertEquals(2, transactionArchive.countDistinctWeeks(), "Returned size of HashSet should be 2."); } + + @Test + void getObservableTransactionsShouldBeObservable() { + var transactionArchive = new TransactionArchive(); + + assertInstanceOf(ObservableList.class, transactionArchive.getObservableTransactions(), + "Should be Observable."); + } } \ No newline at end of file From 0158fc395409c860e8cb95c9447db15ad2931d26 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:21:08 +0200 Subject: [PATCH 060/142] Added FormatBigDecimal Added helper class to format string of big decimals for JavaFX. --- .../gruppe53/service/FormatBigDecimal.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 millions/src/test/java/no/ntnu/gruppe53/service/FormatBigDecimal.java diff --git a/millions/src/test/java/no/ntnu/gruppe53/service/FormatBigDecimal.java b/millions/src/test/java/no/ntnu/gruppe53/service/FormatBigDecimal.java new file mode 100644 index 0000000..5db1ea5 --- /dev/null +++ b/millions/src/test/java/no/ntnu/gruppe53/service/FormatBigDecimal.java @@ -0,0 +1,20 @@ +package no.ntnu.gruppe53.service; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * Service/helper class that formats a {@link BigDecimal} to a string with 2 decimal accuracy. + */ +public class FormatBigDecimal { + /** + * Static method used to format a Big Decimal to a string representation with 2 decimals accuracy. + *

Returns "0.00" if the BigDecimal is null.

+ * + * @param number the BigDecimal to be formated + * @return the string representation of the BigDecimal with 2 decimals accuracy. + */ + public static String formatNumber(BigDecimal number) { + return number == null ? "0.00" : number.setScale(2, RoundingMode.HALF_UP).toPlainString(); + } +} From 3957248e72337120e653a5953534940ad5894a0f Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:23:26 +0200 Subject: [PATCH 061/142] Updated FormatBigDecimal Placed file in test accidentally. Moved back to correct Service package in main. --- .../java/no/ntnu/gruppe53/service/FormatBigDecimal.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename millions/src/{test => main}/java/no/ntnu/gruppe53/service/FormatBigDecimal.java (100%) diff --git a/millions/src/test/java/no/ntnu/gruppe53/service/FormatBigDecimal.java b/millions/src/main/java/no/ntnu/gruppe53/service/FormatBigDecimal.java similarity index 100% rename from millions/src/test/java/no/ntnu/gruppe53/service/FormatBigDecimal.java rename to millions/src/main/java/no/ntnu/gruppe53/service/FormatBigDecimal.java From c425ba0e01d6df14f62382554ae50366e6eb9881 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:25:05 +0200 Subject: [PATCH 062/142] Added back FileHandler --- .../no/ntnu/gruppe53/service/FileHandler.java | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java b/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java new file mode 100644 index 0000000..2aa8262 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java @@ -0,0 +1,142 @@ +package no.ntnu.gruppe53.service; + +import no.ntnu.gruppe53.model.Stock; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +/** + * Handles reading and writing of .csv files for stocks. + */ +public class FileHandler { + + + /** + * Writes a list of {@link Stock}s to a CSV file in UTF-8 encoding. + * + * @param stockList the list of stocks to be written + * @param path directory path for the file + * @param filename name of the file (without or with .csv extension) + * @throws IllegalArgumentException if stockList, path, or filename is null, or if stockList is empty + */ + public static void writeStocksToFile(List stockList, String path, String filename) { + if (stockList == null) throw new IllegalArgumentException("StockList cannot be null"); + if (path == null || path.isBlank()) throw new IllegalArgumentException("Path cannot be null or blank"); + if (filename == null || filename.isBlank()) throw new IllegalArgumentException( + "Filename cannot be null or blank"); + + + path = path.trim(); + filename = filename.trim(); + String filenameWithExtension = filename.endsWith(".csv") ? filename : filename + ".csv"; + + Path filePath = Paths.get(path, filenameWithExtension); + + Path dirPath = filePath.getParent(); + if (!Files.exists(dirPath)) { + try { + Files.createDirectories(dirPath); + } catch (IOException e) { + System.out.println("ERROR: Could not create directories."); + e.printStackTrace(); + return; + } + } + + try (BufferedWriter writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8)) { + writer.write("#Ticker,Name,Price"); + writer.newLine(); + + for (Stock stock : stockList) { + if (stock == null) continue; + String line = String.join(",", + stock.getSymbol(), + stock.getCompany(), + stock.getSalesPrice().toString()); + writer.write(line); + writer.newLine(); + } + + } catch (IOException e) { + System.out.println("ERROR: Something went wrong while writing the CSV file."); + e.printStackTrace(); + } + } + + /** + * Reads a CSV file and converts it to a list of {@link Stock} objects. + * + * @param path directory path of the CSV file + * @param filename CSV file name (with or without .csv extension) + * @return list of Stock objects + * @throws IllegalArgumentException if path or filename is null or blank + */ + public static List readStocksFromFile(String path, String filename) { + List stocks = new ArrayList<>(); + if (path == null || path.isBlank()) { + throw new IllegalArgumentException("Path cannot be null or blank."); + } + if (filename == null || filename.isBlank()) { + throw new IllegalArgumentException("Filename cannot be null or blank."); + } + + path = path.trim(); + filename = filename.trim(); + String filenameWithExtension = filename.endsWith(".csv") ? filename : filename + ".csv"; + + Path filePath = Paths.get(path, filenameWithExtension); + + if (!Files.exists(filePath)) { + System.out.println("ERROR: File not found: " + filePath.toAbsolutePath()); + return stocks; + } + + try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) { + String line; + + while ((line = reader.readLine()) != null) { + line = line.trim(); + + if (line.isBlank() || line.startsWith("#")) continue; + + String[] values = line.split(",", -1); + if (values.length != 3) { + System.out.println("ERROR: Bad line in CSV (expected 3 values): " + line); + continue; + } + + String symbol = values[0].trim(); + String name = values[1].trim(); + BigDecimal price; + + try { + price = new BigDecimal(values[2].trim()); + } catch (NumberFormatException e) { + System.out.println("ERROR: Price is not a valid number: " + values[2]); + continue; + } + + if (symbol.isBlank() || name.isBlank() || price.compareTo(BigDecimal.ZERO) <= 0) { + System.out.println("ERROR: Invalid stock data, skipping: " + line); + continue; + } + + stocks.add(new Stock(symbol, name, price)); + } + + } catch (IOException e) { + System.out.println("ERROR: Something went wrong while reading the CSV file."); + e.printStackTrace(); + } + + return stocks; + } +} \ No newline at end of file From 281a20e9b294c9189697fc06a6a73c1ca70346e6 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:26:14 +0200 Subject: [PATCH 063/142] Added FormatBigDecimalTest Tests for FormatBigDecimal. --- .../service/FormatBigDecimalTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 millions/src/test/java/no/ntnu/gruppe53/service/FormatBigDecimalTest.java diff --git a/millions/src/test/java/no/ntnu/gruppe53/service/FormatBigDecimalTest.java b/millions/src/test/java/no/ntnu/gruppe53/service/FormatBigDecimalTest.java new file mode 100644 index 0000000..9997a01 --- /dev/null +++ b/millions/src/test/java/no/ntnu/gruppe53/service/FormatBigDecimalTest.java @@ -0,0 +1,28 @@ +package no.ntnu.gruppe53.service; + +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static org.junit.jupiter.api.Assertions.*; + +class FormatBigDecimalTest { + @Test + void shouldReturnZeroIfBigDecimalIsNull() { + assertEquals("0.00", FormatBigDecimal.formatNumber(null), + "Null should be formatted as '0.00'."); + } + + @Test + void numberShouldOnlyHaveTwoDecimals() { + assertEquals("2.20", FormatBigDecimal.formatNumber(new BigDecimal("2.2000000000001")), + "Number should only have two decimals."); + } + + @Test + void numberShouldRoundUp() { + assertEquals("32.20", FormatBigDecimal.formatNumber(new BigDecimal("32.1967")), + "Number should round up."); + } + +} \ No newline at end of file From fdadcafb2420402775edd3879dfbd7d416499220 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Sat, 23 May 2026 10:26:33 +0200 Subject: [PATCH 064/142] Moved the refreshMarketList function to gamecontroller --- .../gruppe53/controller/GameController.java | 53 ++++++++++++++-- .../no/ntnu/gruppe53/views/MarketView.java | 63 ++----------------- 2 files changed, 54 insertions(+), 62 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index aeb0a10..de83e3f 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -1,9 +1,11 @@ package no.ntnu.gruppe53.controller; import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import no.ntnu.gruppe53.*; import no.ntnu.gruppe53.views.PortfolioView; @@ -33,11 +35,11 @@ public void startNewGame() { marketView = new MarketView(); - marketView.setExchange(exchange); + refreshMarketList(); portfolioView = new PortfolioView(); - + refreshPortfolioList(); @@ -93,7 +95,7 @@ private void initialize() { portfolioView.onSellButton(() -> { - + //sell actions }); @@ -151,12 +153,16 @@ private void purchaseStock(String stockSymbol, int quantity) { try { exchange.buy(stockSymbol, BigDecimal.valueOf(quantity), player); - marketView.refreshStocks(); + refreshMarketList(); } catch (RuntimeException ex) { System.out.println("Purchase failed: " + ex.getMessage()); } } + /* + Need to figure out how to do sales, because of how the portfolio + groups shasres together how to sell is a bit difficult to figure out. + */ private void sellShares(int quantity) { if (exchange == null || player == null || stocks == null || quantity == 0) { return; @@ -186,6 +192,17 @@ private Share getSelectedStock() { + /* + Populates the portfoliolist with the player owned shares. + However because of how shares are handled, populating the list + with the raw portfolio of the player, it would in practice be + populated by all transactions. + E.g. if you bought TSL shares twice in different transactions + the list would be populated by two different TSL shares. + + Therefore, this functions groups the shares together before displaying + in the for loop of the function. + */ private void refreshPortfolioList() { portfolioView.getPortfolioListView().getItems().clear(); @@ -212,6 +229,20 @@ private void refreshPortfolioList() { portfolioView.getPortfolioListView().getItems().setAll(groupedShares.values()); } + public void refreshMarketList() { + if (exchange == null) { + marketView.getStocksListView().getItems().clear(); + return; + } + + List stocks = exchange.findStocks(""); + marketView.getStocksListView().getItems().setAll( + stocks.stream() + .map(this::formatStock) + .collect(Collectors.toList()) + ); + } + private void refreshNavigationView() { BigDecimal netWorth = player.getNetWorth(); BigDecimal money = player.getMoney(); @@ -223,4 +254,18 @@ private void refreshNavigationView() { } + + private String formatStock(Stock stock) { + return stock.getSymbol() + + " - " + + stock.getCompany() + + " | Price: $" + + formatMoney(stock.getSalesPrice()); + } + + private String formatMoney(BigDecimal money) { + return money == null ? "0.00" : money.setScale(2, RoundingMode.HALF_UP).toPlainString(); + } + + } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java index f3c9fbc..95305e3 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java @@ -209,53 +209,6 @@ public void onQuantitySelect(Runnable action) { }); } - - - /* - public void onPurchaseButton(Runnable action) { - purchaseButton.setOnAction(event -> { - if (purchaseHandler == null) { - return; - } - - String selectedStock = getSelectedSymbol(); - if (selectedStock == null) { - return; - } - - int quantity = quantitySpinner.getValue(); - purchaseHandler.accept(selectedStock, quantity); - }); - }*/ - - public void setPurchaseHandler(BiConsumer purchaseHandler) { - this.purchaseHandler = purchaseHandler; - } - - - - - public void setExchange(Exchange exchange) { - this.exchange = exchange; - refreshStocks(); - } - - - - public void refreshStocks() { - if (exchange == null) { - stocksListView.getItems().clear(); - return; - } - - List stocks = exchange.findStocks(""); - stocksListView.getItems().setAll( - stocks.stream() - .map(this::formatStock) - .collect(Collectors.toList()) - ); - } - public String getSelectedSymbol() { String selected = stocksListView.getSelectionModel().getSelectedItem(); if (selected == null || selected.isBlank()) { @@ -272,23 +225,17 @@ public int getQuantityPurchase() { return quantitySpinner.getValue(); } - + public ListView getStocksListView() { + return stocksListView; + } public void setCalculation() { } - private String formatStock(Stock stock) { - return stock.getSymbol() - + " - " - + stock.getCompany() - + " | Price: $" - + formatMoney(stock.getSalesPrice()); - } - private String formatMoney(BigDecimal money) { - return money == null ? "0.00" : money.setScale(2, RoundingMode.HALF_UP).toPlainString(); - } + + public NavigationView getNavigationView() { return navigationView; From d9f247ca4105b948d199015e7827b5373817b4d1 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:28:27 +0200 Subject: [PATCH 065/142] Deleted extra FileHandler in view. --- .../no/ntnu/gruppe53/view/FileHandler.java | 141 ------------------ 1 file changed, 141 deletions(-) delete mode 100644 millions/src/main/java/no/ntnu/gruppe53/view/FileHandler.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/FileHandler.java b/millions/src/main/java/no/ntnu/gruppe53/view/FileHandler.java deleted file mode 100644 index f1d9ea2..0000000 --- a/millions/src/main/java/no/ntnu/gruppe53/view/FileHandler.java +++ /dev/null @@ -1,141 +0,0 @@ -package no.ntnu.gruppe53.view; - -import no.ntnu.gruppe53.model.Stock; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.IOException; -import java.math.BigDecimal; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; - -/** - * Handles reading and writing of .csv files for stocks. - */ -public class FileHandler { - - - /** - * Writes a list of {@link Stock}s to a CSV file in UTF-8 encoding. - * - * @param stockList the list of stocks to be written - * @param path directory path for the file - * @param filename name of the file (without or with .csv extension) - * @throws IllegalArgumentException if stockList, path, or filename is null, or if stockList is empty - */ - public static void writeStocksToFile(List stockList, String path, String filename) { - if (stockList == null) throw new IllegalArgumentException("StockList cannot be null"); - if (path == null || path.isBlank()) throw new IllegalArgumentException("Path cannot be null or blank"); - if (filename == null || filename.isBlank()) throw new IllegalArgumentException( - "Filename cannot be null or blank"); - - - path = path.trim(); - filename = filename.trim(); - String filenameWithExtension = filename.endsWith(".csv") ? filename : filename + ".csv"; - - Path filePath = Paths.get(path, filenameWithExtension); - - Path dirPath = filePath.getParent(); - if (!Files.exists(dirPath)) { - try { - Files.createDirectories(dirPath); - } catch (IOException e) { - System.out.println("ERROR: Could not create directories."); - e.printStackTrace(); - return; - } - } - - try (BufferedWriter writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8)) { - writer.write("#Ticker,Name,Price"); - writer.newLine(); - - for (Stock stock : stockList) { - if (stock == null) continue; - String line = String.join(",", - stock.getSymbol(), - stock.getCompany(), - stock.getSalesPrice().toString()); - writer.write(line); - writer.newLine(); - } - - } catch (IOException e) { - System.out.println("ERROR: Something went wrong while writing the CSV file."); - e.printStackTrace(); - } - } - - /** - * Reads a CSV file and converts it to a list of {@link Stock} objects. - * - * @param path directory path of the CSV file - * @param filename CSV file name (with or without .csv extension) - * @return list of Stock objects - */ - public static List readStocksFromFile(String path, String filename) { - List stocks = new ArrayList<>(); - if (path == null || path.isBlank()) { - throw new IllegalArgumentException("Path cannot be null or blank."); - } - if (filename == null || filename.isBlank()) { - throw new IllegalArgumentException("Filename cannot be null or blank."); - } - - path = path.trim(); - filename = filename.trim(); - String filenameWithExtension = filename.endsWith(".csv") ? filename : filename + ".csv"; - - Path filePath = Paths.get(path, filenameWithExtension); - - if (!Files.exists(filePath)) { - System.out.println("ERROR: File not found: " + filePath.toAbsolutePath()); - return stocks; - } - - try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) { - String line; - - while ((line = reader.readLine()) != null) { - line = line.trim(); - - if (line.isBlank() || line.startsWith("#")) continue; - - String[] values = line.split(",", -1); - if (values.length != 3) { - System.out.println("ERROR: Bad line in CSV (expected 3 values): " + line); - continue; - } - - String symbol = values[0].trim(); - String name = values[1].trim(); - BigDecimal price; - - try { - price = new BigDecimal(values[2].trim()); - } catch (NumberFormatException e) { - System.out.println("ERROR: Price is not a valid number: " + values[2]); - continue; - } - - if (symbol.isBlank() || name.isBlank() || price.compareTo(BigDecimal.ZERO) <= 0) { - System.out.println("ERROR: Invalid stock data, skipping: " + line); - continue; - } - - stocks.add(new Stock(symbol, name, price)); - } - - } catch (IOException e) { - System.out.println("ERROR: Something went wrong while reading the CSV file."); - e.printStackTrace(); - } - - return stocks; - } -} \ No newline at end of file From 8c540b037e486823bdc63708521a1cf9db7e9412 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:29:07 +0200 Subject: [PATCH 066/142] Added StockLineChartService Added helper class for getting X and Y data for stock line chart. --- .../service/StockLineChartService.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java b/millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java new file mode 100644 index 0000000..28076e3 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java @@ -0,0 +1,72 @@ +package no.ntnu.gruppe53.service; + +import javafx.scene.chart.XYChart; +import no.ntnu.gruppe53.model.Exchange; +import no.ntnu.gruppe53.model.Stock; +import no.ntnu.gruppe53.view.StockLineChartView; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * Builds the {@link XYChart.Series} values for the {@link StockLineChartView}. + */ + +public class StockLineChartService { + + /** + * Builds the {@code XYChart.Series} for the {@code LineChart} of a single {@link Stock}. + *

Returns the {@code XYChart.Series}.

+ * + * @param stock the stock whose price values will be shown in the LineChart + * @param exchange the exchange containing the stock whose week number will be shown in the LineChart + * @return the {@code XYChart.Series} with the y-values from the price history of the stock, + * and the x-values from the week number of the exchange + * @throws IllegalArgumentException if there are more x-values than y-values or vice versa + */ + + public static XYChart.Series buildSeries(Stock stock, Exchange exchange) { + XYChart.Series series = new XYChart.Series<>(); + series.setName(stock.getSymbol()); + + // Check for 1-1 ratio of price and week list size for plotting + if (stock.getHistoricalPrices().size() != exchange.getWeek()) { + throw new IllegalArgumentException("Invalid Data: mismatch between prices and weeks. " + + "Should have 1-1 ratio."); + } + + int week = 1; + + for (BigDecimal price : stock.getHistoricalPrices()) { + series.getData().add( + new XYChart.Data<>(week, price.doubleValue())); + week++; + } + + return series; + } + + /** + * Builds the {@code XYChart.Series} for the {@code LineChart} of a list of {@code Stock}s. + *

Returns a list of the {@code XYChart.Series} for each stock.

+ * + * @param stocks the list of stocks whose price values will be shown in the LineChart + * @param exchange the exchange containing the stock whose week number will be shown in the LineChart + * @return the list of {@code XYChart.Series} with the y-values from the price history of the stock, + * and the x-values from the week number of the exchange + */ + + public static List> buildSeries(List stocks, Exchange exchange) { + List> seriesList = new ArrayList<>(); + + for (Stock stock : stocks) { + XYChart.Series series = + buildSeries(stock, exchange); + + seriesList.add(series); + } + + return seriesList; + } +} \ No newline at end of file From 8e6a304ab6f902aeff5a978c081248a308bfc3b9 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:30:17 +0200 Subject: [PATCH 067/142] Added StockLineChartServiceTest Unit tests for StockLineChartService. --- .../service/StockLineChartServiceTest.java | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 millions/src/test/java/no/ntnu/gruppe53/service/StockLineChartServiceTest.java diff --git a/millions/src/test/java/no/ntnu/gruppe53/service/StockLineChartServiceTest.java b/millions/src/test/java/no/ntnu/gruppe53/service/StockLineChartServiceTest.java new file mode 100644 index 0000000..9c94dad --- /dev/null +++ b/millions/src/test/java/no/ntnu/gruppe53/service/StockLineChartServiceTest.java @@ -0,0 +1,182 @@ +package no.ntnu.gruppe53.service; +import no.ntnu.gruppe53.model.Exchange; +import no.ntnu.gruppe53.model.Stock; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.*; + +class StockLineChartServiceTest { + private Stock appleStock; + + private Stock microStock; + + private Stock ntnuStock; + + private ArrayList stocks; + + private Exchange exchange; + + @BeforeEach + void setUp() { + appleStock = new Stock("AAPL", "Apple", new BigDecimal("100")); + microStock = new Stock("MSFT", "Microsoft", new BigDecimal("50")); + ntnuStock = new Stock("NTNU", "Ntnu", new BigDecimal("1000")); + + stocks = new ArrayList<>(); + stocks.add(appleStock); + stocks.add(microStock); + stocks.add(ntnuStock); + + exchange = new Exchange("Nasdaq", stocks); + } + + @Test + void shouldThrowExceptionIfPriceAndWeekIsNotOneToOne() { + exchange.advance(); + + appleStock.addNewSalesPrice(new BigDecimal("50")); + + assertThrows(IllegalArgumentException.class, () -> + StockLineChartService.buildSeries(appleStock, exchange), + "Stock should not be allowed to have more values than weeks in exchange for plotting chart."); + } + + @Test + void buildSeriesShouldHaveCorrectWeekForOneStock() { + for (int i = 0; i < 9; i++) { + exchange.advance(); + } + + var series = StockLineChartService.buildSeries(appleStock, exchange); + double week = series.getData().stream().mapToDouble(data -> data.getXValue() + .doubleValue()).max().orElse(0); + + + assertEquals(exchange.getWeek(), week, "Max week number should be correct."); + } + + @Test + void buildSeriesShouldHaveCorrectWeekForMultipleStocks() { + for (int i = 0; i < 4; i++) { + exchange.advance(); + } + + var series = StockLineChartService.buildSeries(stocks, exchange); + double week = exchange.getWeek(); + double weekFirstStock = series.getFirst().getData().stream() + .mapToDouble(data -> data.getXValue().doubleValue()) + .max().orElse(0); + double weekSecondStock = series.get(1).getData().stream() + .mapToDouble(data -> data.getXValue().doubleValue()) + .max().orElse(0); + double weekLastStock = series.getLast().getData().stream() + .mapToDouble(data -> data.getXValue().doubleValue()) + .max().orElse(0); + + assertEquals(week, weekFirstStock, "Week value for first stock in series should be correct."); + assertEquals(week, weekSecondStock, "Week value for second stock in series should be correct."); + assertEquals(week, weekLastStock, "Week value for last stock in series should be correct."); + } + + @Test + void buildSeriesShouldHaveCorrectPricesForOneStock() { + exchange.advance(); + + var series = StockLineChartService.buildSeries(appleStock, exchange); + + double firstPrice = appleStock.getHistoricalPrices().getFirst().doubleValue(); + double secondPrice = exchange.getStock(appleStock.getSymbol()).getSalesPrice().doubleValue(); + double firstPriceSeries = series.getData().stream().mapToDouble( + data -> data.getYValue().doubleValue()).findFirst().orElse(0); + double secondPriceSeries = series.getData().stream().mapToDouble( + data -> data.getYValue().doubleValue()).skip(1). + findFirst().orElse(0); + + assertEquals(firstPrice, firstPriceSeries, "First price should be the same."); + assertEquals(secondPrice, secondPriceSeries, "Second price should be the same."); + } + + @Test + void buildSeriesShouldHaveCorrectPricesForMultipleStocks() { + exchange.advance(); + + var series = StockLineChartService.buildSeries(stocks, exchange); + + // First stock prices + double firstPriceFirstStock = exchange.getStock(appleStock.getSymbol()) + .getHistoricalPrices().getFirst().doubleValue(); + double firstPriceFirstStockSeries = series.getFirst().getData().stream() + .mapToDouble(data -> data.getYValue().doubleValue()) + .findFirst().orElse(0); + double lastPriceFirstStock = exchange.getStock(appleStock.getSymbol()).getSalesPrice().doubleValue(); + double lastPriceFirstStockSeries =series.getFirst().getData().stream() + .mapToDouble(data -> data.getYValue().doubleValue()) + .skip(1).findFirst().orElse(0); + + // Second stock prices + double firstPriceSecondStock = exchange.getStock(microStock.getSymbol()) + .getHistoricalPrices().getFirst().doubleValue(); + double firstPriceSecondStockSeries = series.get(1).getData().stream() + .mapToDouble(data -> data.getYValue().doubleValue()) + .findFirst().orElse(0); + double lastPriceSecondStock = exchange.getStock(microStock.getSymbol()).getSalesPrice().doubleValue(); + double lastPriceSecondStockSeries =series.get(1).getData().stream() + .mapToDouble(data -> data.getYValue().doubleValue()) + .skip(1).findFirst().orElse(0); + + // Last stock prices + double firstPriceLastStock = exchange.getStock(ntnuStock.getSymbol()) + .getHistoricalPrices().getFirst().doubleValue(); + double firstPriceLastStockSeries = series.getLast().getData().stream() + .mapToDouble(data -> data.getYValue().doubleValue()) + .findFirst().orElse(0); + double lastPriceLastStock = exchange.getStock(ntnuStock.getSymbol()).getSalesPrice().doubleValue(); + double lastPriceLastStockSeries =series.getLast().getData().stream() + .mapToDouble(data -> data.getYValue().doubleValue()) + .skip(1).findFirst().orElse(0); + + assertEquals(firstPriceFirstStock, firstPriceFirstStockSeries, + "First price for first stock should be correct."); + assertEquals(lastPriceFirstStock, lastPriceFirstStockSeries, + "Last price for first stock should be correct."); + assertEquals(firstPriceSecondStock, firstPriceSecondStockSeries, + "First price for second stock should be correct."); + assertEquals(lastPriceSecondStock, lastPriceSecondStockSeries, + "Last price for second stock should be correct."); + assertEquals(firstPriceLastStock, firstPriceLastStockSeries, + "First price for last stock should be correct."); + assertEquals(lastPriceLastStock, lastPriceLastStockSeries, + "Last price for last stock should be correct."); + } + + @Test + void buildSeriesShouldHaveCorrectSymbolForOneStock() { + var series = StockLineChartService.buildSeries(appleStock, exchange); + + String name = appleStock.getSymbol(); + String nameSeries = series.getName(); + + assertEquals(name, nameSeries, "Series should have correct stock symbol."); + } + + @Test + void buildSeriesShouldHaveCorrectSymbolForMultipleStocks() { + var series = StockLineChartService.buildSeries(stocks, exchange); + + String firstStockName = appleStock.getSymbol(); + String firstStockNameSeries = series.getFirst().getName(); + String secondStockName = microStock.getSymbol(); + String secondStockNameSeries = series.get(1).getName(); + String lastStockName = ntnuStock.getSymbol(); + String lastStockNameSeries = series.getLast().getName(); + + assertEquals(firstStockName, firstStockNameSeries, "First stock name should be correct."); + assertEquals(secondStockName, secondStockNameSeries, "Second stock name should be correct."); + assertEquals(lastStockName, lastStockNameSeries, "Last stock name should be correct."); + } +} \ No newline at end of file From bfab3989523c45fd358d263a453f6620b9fd68cd Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:31:52 +0200 Subject: [PATCH 068/142] Added StockLineChartView Represents a graph for stock price - time data. --- .../gruppe53/view/StockLineChartView.java | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/view/StockLineChartView.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/StockLineChartView.java b/millions/src/main/java/no/ntnu/gruppe53/view/StockLineChartView.java new file mode 100644 index 0000000..adb5dc5 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/view/StockLineChartView.java @@ -0,0 +1,126 @@ +package no.ntnu.gruppe53.view; +import javafx.geometry.Side; +import javafx.scene.chart.LineChart; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import no.ntnu.gruppe53.model.Stock; + +import java.util.List; + +/** + * Represents a {@link LineChart} view of a {@link Stock}. + *

Y-axis: Price as a {@code double} value. Represented by a {@link NumberAxis}

+ *

X-axis: Week as an {@code int} value. Represented by a {@code NumberAxis}

+ */ + +public class StockLineChartView { + private final NumberAxis xAxis = new NumberAxis(); + private final NumberAxis yAxis = new NumberAxis(); + + private final LineChart chart; + + /** + * Sets the settings for the {@code LineChart}. + *

Sets autoscale, stepsize and manual values for the range x- and y-axis, as well + * as the labels.

+ *

Initializes the LineChart.

+ */ + + public StockLineChartView() { + xAxis.setAutoRanging(false); + yAxis.setAutoRanging(true); + yAxis.setForceZeroInRange(false); + + xAxis.setLowerBound(1); + + xAxis.setLabel("Week"); + yAxis.setLabel("Price"); + + chart = new LineChart<>(xAxis, yAxis); + } + + /** + * Returns the constructed LineChart. + * + * @return the current LineChart + */ + public LineChart getChart() { + return chart; + } + + /** + * Calculates the max x-axis value for a {@link XYChart.Series}. + * + * @param series the XYChart series to determine max x-axis value from + * @return the maximum x-axis value in the XYChart.Series + */ + + private double calculateXAxisMax(XYChart.Series series) { + return series.getData().stream() + .mapToDouble(data -> data.getXValue() + .doubleValue()) + .max(). + orElse(0); + } + + /** + * Calculates the max x-axis value for a {@code List}. + * + * @param seriesList the list of XYChart.Series to determine max x-axis value from + * @return the maximum x-axis value in the List of XYChart.Series + */ + + private double calculateXAxisMax(List> seriesList) { + double xMax = 0; + + for (XYChart.Series s : seriesList) { + double tempMax = calculateXAxisMax(s); + if (tempMax > xMax) { + xMax = tempMax; + } + } + + return xMax; + } + + /** + * Sets the {@code XYChart.Series} containing the x- and y-values for the {@code LineChart}. + * + * @param series the XYChart.Series containing the x- and y-values for the LineChart + * @param title the name of the company the stock represents + */ + + public void setSeries(XYChart.Series series, String title) { + chart.getData().clear(); + + chart.setTitle(title); + chart.setLegendSide(Side.RIGHT); + + double xMax = calculateXAxisMax(series); + xAxis.setUpperBound(xMax); + + chart.getData().add(series); + } + + /** + * Sets the {@code XYChart.Series} containing the x- and y-values for the {@code LineChart}. + *

Here the series values are given as a list of {@code XYChart.Series} objects.

+ * + * @param seriesList the list of XYChart.Series containing the x- and y-values for each stock + */ + + public void setSeries(List> seriesList) { + chart.getData().clear(); + + chart.setTitle("Price History"); + chart.setLegendSide(Side.RIGHT); + + double xMax = calculateXAxisMax(seriesList); + + if (xMax != 0) { + xAxis.setUpperBound(xMax); + } + + chart.getData().addAll(seriesList); + } +} \ No newline at end of file From 02e4b1f442b359b9b2cadeff123525ea3e0f8008 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:33:22 +0200 Subject: [PATCH 069/142] Added CSVChooserView Handles choosing a .csv file. --- .../no/ntnu/gruppe53/view/CSVChooserView.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java b/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java new file mode 100644 index 0000000..092e53f --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java @@ -0,0 +1,49 @@ +package no.ntnu.gruppe53.view; + +import javafx.scene.control.Alert; +import javafx.stage.FileChooser; +import javafx.stage.Stage; + +import java.io.File; + +/** + * Represents an {@link Alert} and {@link FileChooser} for informing user of .csv requirement and picking said file. + */ +public class CSVChooserView { + private final FileChooser fileChooser = new FileChooser(); + Alert newGameAlert = new Alert(Alert.AlertType.INFORMATION); + + /** + * Returns the dialog for the alert and file chooser and exposes it to controllers. + * + * @param stage the stage for the dialog to be shown + * @return the dialog for the alert and file chooser + */ + public File getDialog(Stage stage) { + // Notifies the user about .csv selection + newGameAlert.setTitle("Please read:"); + newGameAlert.setHeaderText("CSV File Required"); + newGameAlert.setContentText("Please select a .csv file of the stocks to be used for the exchange " + + " in the next window.\n\nIf none is selected (or if .csv file is empty/malformed), a default file will be used." + + "\n\nThe file needs to be formated as: StockTicker,CorporationName,PriceTypeDouble." + + "\n\nExample: NVDA,Nvidia,191.27" + "\n\n'#' signifies comments, and along with lines not formatted " + + "correctly, will be ignored."); + + // Sets the dimensions of the alert to show all text, and as a band-aid, be resizable + newGameAlert.setResizable(true); + + newGameAlert.getDialogPane().setPrefWidth(450); + newGameAlert.getDialogPane().setPrefHeight(350); + + newGameAlert.showAndWait(); + + // File chooser to choose .csv file + fileChooser.setTitle("Select CSV File"); + + // Limits to .csv file only + fileChooser.getExtensionFilters().add( + new FileChooser.ExtensionFilter("CSV Files", "*.csv") + ); + + return fileChooser.showOpenDialog(stage); } +} \ No newline at end of file From f3dcc57c4d708c0a047309c2ac08edd00f410caf Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:34:17 +0200 Subject: [PATCH 070/142] Added FooterBar Represents the footer of main view. --- .../java/no/ntnu/gruppe53/view/FooterBar.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java new file mode 100644 index 0000000..8c1728d --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java @@ -0,0 +1,75 @@ +package no.ntnu.gruppe53.view; + +import java.math.RoundingMode; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.util.Subscription; +import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.controller.GameController; +import no.ntnu.gruppe53.model.Exchange; + +/** + * A {@link HBox} view representing the footer/bottom to be used in a {@link BorderPane} layout. + */ +public class FooterBar extends HBox { + private final Label netWorthLabel; + private final Button advanceWeekButton; + private Subscription netWorthSubscription; + + /** + * Constructs the layout of the view and its components. + */ + public FooterBar() { + this.setSpacing(10); + this.setPadding(new Insets(15, 12, 15, 12)); + this.setStyle("-fx-background-color: #336699;"); + + netWorthLabel = new Label("Net Worth: $0.00"); + netWorthLabel.setStyle("-fx-text-fill: #FFFFFF;"); + + advanceWeekButton = new Button("Advance Week"); + + // Spacing between buttons + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + + this.getChildren().addAll(netWorthLabel, spacer, advanceWeekButton); + } + + /** + * Binds a subscription/observer to a {@link Player}. + *

It specifically listens to the players money and net worth.

+ * + * @param player the player object to be observed + */ + public void bindPlayer(Player player) { + if (netWorthSubscription != null) { + netWorthSubscription.unsubscribe(); + } + + if (player != null) { + netWorthSubscription = player.getNetWorthProperty().subscribe(newVal -> { + if (newVal != null) { + netWorthLabel.setText("Net Worth: $" + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + netWorthLabel.setText("Net Worth: $0.00"); + } + }); + } + } + + /** + * Exposes the advanceWeekButton to {@link GameController} so it can + * advance the week of the {@link Exchange}. + * + * @param action the action to be performed when clicking the button + */ + public void onAdvanceButtonClick(Runnable action) { + advanceWeekButton.setOnAction(e -> action.run()); + } +} From 790e77b7328beba232eb565092c548c5a7158964 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:35:14 +0200 Subject: [PATCH 071/142] Added NavigationBar Represents the top bar of the main view. --- .../no/ntnu/gruppe53/view/NavigationBar.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java new file mode 100644 index 0000000..436a44b --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java @@ -0,0 +1,61 @@ +package no.ntnu.gruppe53.view; + +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.layout.HBox; + +/** + * Represents the top bar of the views. + *

Provides the user with the ability to switch between views on the fly by + * clicking on the buttons representing the desired view.

+ *

The {@link no.ntnu.gruppe53.controller.GameController} handles + * the actions for the buttons.

+ */ +public class NavigationBar extends HBox { + + private final Button marketButton; + private final Button portfolioButton; + private final Button historyButton; + + /** + * Constructs the navigation bar and its components. + */ + public NavigationBar() { + this.setSpacing(10); + this.setPadding(new Insets(15, 12, 15, 12)); + this.setStyle("-fx-background-color: #336699;"); + + this.marketButton = new Button("Market"); + this.portfolioButton = new Button("Portfolio"); + this.historyButton = new Button("History"); + + this.getChildren().addAll(marketButton, portfolioButton, historyButton); + } + + /** + * Exposes the marketButton to the GameController. + * + * @param action the action to be run when clicking the button + */ + public void onMarketButtonClick(Runnable action) { + marketButton.setOnAction(e -> action.run()); + } + + /** + * Exposes the portfolioButton to the GameController. + * + * @param action the action to be run when clicking the button + */ + public void onPortfolioButtonClick(Runnable action) { + portfolioButton.setOnAction(e -> action.run()); + } + + /** + * Exposes the historyButton to the GameController. + * + * @param action the action to be run when clicking the button + */ + public void onHistoryButtonClick(Runnable action) { + historyButton.setOnAction(e -> action.run()); + } +} From 98fa0333d286a8b247f1d9fe21fb818135db5671 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:36:18 +0200 Subject: [PATCH 072/142] Added PlayerNameChooserView Lets a player choose their name. --- .../gruppe53/view/PlayerNameChooserView.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java new file mode 100644 index 0000000..b844df9 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java @@ -0,0 +1,97 @@ +package no.ntnu.gruppe53.view; + +import no.ntnu.gruppe53.model.Player; + +import javafx.geometry.Insets; +import javafx.scene.control.*; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +import java.util.Optional; + +/** + * Represents a prompt for the {@link Player}'s name + */ +public class PlayerNameChooserView { + Dialog dialog = new Dialog<>(); + + /** + * Returns the dialog where the player can input a name of maximum 50 characters. + *

A {@link TextFormatter} is used to set the maximum amount + * of characters for the player name.

+ * + * @return the dialog for player name input + */ + public Optional getDialog() { + // Sets dialog title and header text + dialog.setTitle("Player Name"); + dialog.setHeaderText(null); + + // Sets the dimensions for the dialog pane + dialog.setResizable(true); + + dialog.getDialogPane().setPrefWidth(320); + dialog.getDialogPane().setPrefHeight(50); + + ButtonType confirmButton = + new ButtonType("Confirm", ButtonBar.ButtonData.OK_DONE); + + dialog.getDialogPane().getButtonTypes().add(confirmButton); + + TextField nameField = new TextField(); + + nameField.setEditable(true); + + int maxLength = 50; + + // Prevents player name over 50 characters + + TextFormatter formatter = new TextFormatter<>(change -> { + + String newText = change.getControlNewText(); + + if (newText.length() <= maxLength) { + return change; + } + + return null; + + }); + + nameField.setTextFormatter(formatter); + + // Adds a counter for max characters + Label counterLabel = new Label("0 / 50"); + + nameField.textProperty().addListener((obs, oldVal, newVal) -> { + counterLabel.setText(newVal.length() + " / 50"); + }); + + Label infoLabel = new Label( + "Please enter your player name:" + ); + + VBox layout = new VBox(15); + + HBox hBox = new HBox(15); + + layout.setPadding(new Insets(10)); + + hBox.getChildren().addAll(nameField, counterLabel); + + layout.getChildren().addAll(infoLabel, hBox); + + dialog.getDialogPane().setContent(layout); + + dialog.setResultConverter(button -> { + if (button == confirmButton) { + + return nameField.getText(); + } + + return null; + }); + + return dialog.showAndWait(); + } +} \ No newline at end of file From 9b668a74dc3c9fe2be8365ee400febba29fbad2a Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:37:17 +0200 Subject: [PATCH 073/142] Added PlayerStartingMoneyChooserView Lets the player choose their starting money. --- .../view/PlayerStartingMoneyChooserView.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java new file mode 100644 index 0000000..82b0c50 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java @@ -0,0 +1,105 @@ +package no.ntnu.gruppe53.view; + +import no.ntnu.gruppe53.model.Player; + +import javafx.geometry.Insets; +import javafx.scene.control.*; +import javafx.scene.layout.VBox; + +import java.math.BigDecimal; +import java.util.Optional; + +/** + * Represents the prompt for the {@link Player}'s starting money. + */ +public class PlayerStartingMoneyChooserView { + Dialog dialog = new Dialog<>(); + + /** + * Returns the dialog for the player's starting money. + *

Uses a {@link TextFormatter} and listener for value integrity.

+ *

Uses a {@link SpinnerValueFactory} to set min, max, default, + * and step size values for the spinner.

+ * @return the dialog for player's starting money + */ + public Optional getDialog() { + + // Sets dialog title and header text + dialog.setTitle("Select starting money:"); + dialog.setHeaderText(null); + + // Sets the dimensions for the dialog pane + dialog.setResizable(true); + + dialog.getDialogPane().setPrefWidth(320); + dialog.getDialogPane().setPrefHeight(50); + + ButtonType confirmButton = + new ButtonType("Confirm", ButtonBar.ButtonData.OK_DONE); + + dialog.getDialogPane().getButtonTypes().add(confirmButton); + + Spinner spinner = new Spinner<>(); + + // Sets allowed values and step size for the spinner + SpinnerValueFactory.DoubleSpinnerValueFactory valueFactory = + new SpinnerValueFactory.DoubleSpinnerValueFactory( + 1000.0, // min + 1000000.0, // max + 5000.0, // initial value + 100.0 // step-size + ); + + spinner.setValueFactory(valueFactory); + + spinner.setEditable(true); + + // Formats the text to only allow numbers, and doubles preceded by a number + TextFormatter formatter = new TextFormatter<>(change -> { + + String newText = change.getControlNewText(); + + if (newText.matches("\\d+(\\.\\d*)?")) { + + return change; + } + + return null; + }); + + spinner.focusedProperty().addListener((obs, oldVal, newVal) -> { + + if (!newVal) { + spinner.increment(0); + } + }); + + spinner.getEditor().setTextFormatter(formatter); + + spinner.setPrefWidth(200); + + Label infoLabel = new Label( + "Select your starting money balance for trading:" + ); + + VBox layout = new VBox(15); + + layout.setPadding(new Insets(10)); + + layout.getChildren().addAll(infoLabel, spinner); + + dialog.getDialogPane().setContent(layout); + + dialog.setResultConverter(button -> { + + if (button == confirmButton) { + + return BigDecimal.valueOf(spinner.getValue()); + } + + return null; + }); + + return dialog.showAndWait(); + } +} \ No newline at end of file From 0a2df656a787494eddbe25e378f97b54c6e61e0b Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Sat, 23 May 2026 10:39:29 +0200 Subject: [PATCH 074/142] Redefined how formating money worked --- .../gruppe53/controller/GameController.java | 28 +++++++++++-------- .../no/ntnu/gruppe53/views/MarketView.java | 12 ++------ 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index de83e3f..dd9ffce 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -87,10 +87,10 @@ private void initialize() { portfolioView.setSelectedShare(newValue); portfolioView.setQuantitySpinner(newValue.getQuantity().intValue()); - portfolioView.setGross(saleCalculator.calculateGross().toString()); - portfolioView.setCommission(saleCalculator.calculateCommission().toString()); - portfolioView.setTax(saleCalculator.calculateTax().toString()); - portfolioView.setTotal(saleCalculator.calculateTotal().toString()); + portfolioView.setGross(formatMoney(saleCalculator.calculateGross())); + portfolioView.setCommission(formatMoney(saleCalculator.calculateCommission())); + portfolioView.setTax(formatMoney(saleCalculator.calculateTax())); + portfolioView.setTotal(formatMoney(saleCalculator.calculateTotal())); }); portfolioView.onSellButton(() -> { @@ -121,9 +121,9 @@ private void initialize() { purchaseCalculator = new PurchaseCalculator(getSelectedStock()); marketView.selectedStock.setText(newValue); - marketView.gross.setText(purchaseCalculator.calculateGross().toString()); - marketView.commission.setText(purchaseCalculator.calculateCommission().toString()); - marketView.total.setText(purchaseCalculator.calculateTotal().toString()); + marketView.gross.setText(formatMoney(purchaseCalculator.calculateGross())); + marketView.commission.setText(formatMoney(purchaseCalculator.calculateCommission())); + marketView.total.setText(formatMoney(purchaseCalculator.calculateTotal())); }); marketView.onPurchaseButton(() -> { @@ -135,9 +135,9 @@ private void initialize() { //if no share selected need to be implemented purchaseCalculator = new PurchaseCalculator(getSelectedStock()); - marketView.gross.setText(purchaseCalculator.calculateGross().toString()); - marketView.commission.setText(purchaseCalculator.calculateCommission().toString()); - marketView.total.setText(purchaseCalculator.calculateTotal().toString()); + marketView.gross.setText(formatMoney(purchaseCalculator.calculateGross())); + marketView.commission.setText(formatMoney(purchaseCalculator.calculateCommission())); + marketView.total.setText(formatMoney(purchaseCalculator.calculateTotal())); }); @@ -259,12 +259,16 @@ private String formatStock(Stock stock) { return stock.getSymbol() + " - " + stock.getCompany() - + " | Price: $" + + " | Price: " + formatMoney(stock.getSalesPrice()); } private String formatMoney(BigDecimal money) { - return money == null ? "0.00" : money.setScale(2, RoundingMode.HALF_UP).toPlainString(); + + + + + return money == null ? "0.00" : "$" + money.abs().setScale(2, RoundingMode.HALF_UP).toPlainString(); } diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java index 95305e3..3f4665e 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java @@ -53,14 +53,6 @@ public class MarketView extends BorderPane { //Input private Spinner quantitySpinner; - //Models - private Exchange exchange; - private Player player; - - // Handlers - private BiConsumer purchaseHandler; - private Runnable showPortfolioHandler; - public MarketView() { @@ -76,7 +68,9 @@ public MarketView() { } - + /* + Styling of the center page + */ public VBox centerPane() { VBox vBox = new VBox(); From 47311b328349bc77ebbaab296847e0251aaa657b Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:40:21 +0200 Subject: [PATCH 075/142] Updated MarketView Added graph view to stocks. Made the view use the observer pattern. Added more info to formatStock. Sorts stockList alphabetically on ticker symbol. Added JavaDoc. --- .../no/ntnu/gruppe53/view/MarketView.java | 316 +++++++++--------- 1 file changed, 164 insertions(+), 152 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java index 959ffe6..ccc7b3a 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java @@ -1,88 +1,60 @@ package no.ntnu.gruppe53.view; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.List; -import java.util.function.BiConsumer; import java.util.function.Consumer; -import java.util.stream.Collectors; - -import javafx.geometry.Insets; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.ListView; -import javafx.scene.control.Spinner; -import javafx.scene.control.SpinnerValueFactory; + +import javafx.collections.transformation.SortedList; +import javafx.scene.control.*; +import javafx.scene.chart.XYChart; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; +import javafx.scene.chart.LineChart; +import javafx.util.Subscription; import no.ntnu.gruppe53.model.Exchange; import no.ntnu.gruppe53.model.Player; import no.ntnu.gruppe53.model.Stock; - +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.service.FormatBigDecimal; + +/** + * Represents a view of the market of an {@link Exchange}, allowing the {@link Player} to see + * price history statistics in the form of a {@link LineChart} through the + * {@link StockLineChartView} class, as well as buying {@link Stock}s. + *

Extends borderpane to allow easy organizing of the internal layout.

+ */ public class MarketView extends BorderPane { - //Main pane of view - - - //Buttons private Button purchaseButton; - - //NavigationBar Buttons - private Button marketButton; - private Button portfolioButton; - - //Dynamic Labels/Text private Label playerMoneyLabel; private Label selectedStock; - - - //StockListView - private ListView stocksListView; - - //Input + private ListView stocksListView; private Spinner quantitySpinner; - - //Models private Exchange exchange; private Player player; - // Handlers - private BiConsumer purchaseHandler; - private Runnable showPortfolioHandler; - - + private final StockLineChartView stockLineChartView; + private Subscription playerSubscription; + private Subscription priceSubscription; + /** + * Initializes the StockLineChartView graph and runs the view + * construction method and places it in the center of the + * BorderPane. + */ public MarketView() { - - - - this.setTop(navigationBar()); + stockLineChartView = new StockLineChartView(); this.setCenter(centerPane()); - - } - - public HBox navigationBar() { - - HBox hBox = new HBox(); - - hBox.setPadding(new Insets(15, 12, 15, 12)); - hBox.setSpacing(10); - hBox.setStyle("-fx-background-color: #336699;"); - - marketButton = new Button("Market"); - portfolioButton = new Button("Portfolio"); - - hBox.getChildren().addAll(marketButton, portfolioButton); - - return hBox; - } - - public VBox centerPane() { - - VBox vBox = new VBox(); + /** + * Constructs the view and its components. + *

Subscribes to a stock's sales price to update the value immediately.

+ *

Uses a cell factory to format the string found in the stocksListView.

+ * + * @return the constructed MarketView + */ + private VBox centerPane() { + VBox vBox = new VBox(10); Label titleLabel = new Label("Stock Market"); titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); @@ -90,142 +62,182 @@ public VBox centerPane() { playerMoneyLabel = new Label("Money: $0.00"); Label stocksTitleLabel = new Label("Available stocks:"); + stockLineChartView.getChart().setPrefHeight(150); + stocksListView = new ListView<>(); - stocksListView.setPrefHeight(300); + stocksListView.setPrefHeight(200); + + stocksListView.setCellFactory(list -> new ListCell<>() { + private Subscription cellSubscription; + + @Override + protected void updateItem(Stock stock, boolean empty) { + super.updateItem(stock, empty); + + if (cellSubscription != null) { + cellSubscription.unsubscribe(); + cellSubscription = null; + } + + if (empty || stock == null) { + setText(null); + } else { + cellSubscription = stock.salesPriceProperty().subscribe(newPrice -> { + setText(stock.getSymbol() + " - " + stock.getCompany() + " | Price: $" + + FormatBigDecimal.formatNumber(newPrice) + + " | Price change: " + FormatBigDecimal.formatNumber(stock.getLatestPriceChange()) + + " | Highest price: " + FormatBigDecimal.formatNumber(stock.getHighestPrice()) + + " | Lowest price: " + FormatBigDecimal.formatNumber(stock.getLowestPrice())); + }); + } + } + }); HBox selectedStockBox = new HBox(); - - Label selectedStockLabel = new Label("Selected Stock:"); + Label selectedStockLabel = new Label("Selected Stock: "); selectedStock = new Label(""); - selectedStockBox.getChildren().addAll(selectedStockLabel, selectedStock); - HBox purchaseBox = new HBox(); - - - - Label quantityLabel = new Label("Quantity:"); - quantitySpinner = new Spinner<>(); - quantitySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1_000_000, 1)); + HBox purchaseBox = new HBox(10); + Label quantityLabel = new Label("Quantity: "); + quantitySpinner = new Spinner<>(1, 1_000_000, 1); quantitySpinner.setEditable(true); purchaseButton = new Button("Purchase"); - purchaseBox.getChildren().addAll(quantityLabel, quantitySpinner, purchaseButton); - vBox.getChildren().addAll(titleLabel, + vBox.getChildren().addAll( + titleLabel, playerMoneyLabel, + stockLineChartView.getChart(), stocksTitleLabel, stocksListView, selectedStockBox, - purchaseBox); + purchaseBox + ); return vBox; } - public void onPortfolioButton(Runnable action) { - portfolioButton.setOnAction( e -> action.run()); - } + /** + * Sets and updates subscription to the selected stock's sales price. + * @param stock the selected stock + * @param priceChangeAction the action to run on price change (updates the sales price) + */ + public void setupPriceSubscription(Stock stock, Consumer priceChangeAction) { + if (priceSubscription != null) { + priceSubscription.unsubscribe(); + priceSubscription = null; + } - public void onPurchaseButton(Runnable action) { - purchaseButton.setOnAction(e -> action.run()); + if (stock != null) { + priceSubscription = stock.salesPriceProperty().subscribe(newPrice -> { + priceChangeAction.accept(stock); + }); + } } - public void onStockSelection(Consumer action) { - stocksListView.getSelectionModel() - .selectedItemProperty() - .addListener((observable, oldValue, newValue) -> { - if (newValue != null) { - action.accept(newValue); - } - }); + /** + * Updates chart data in the StockLineChartView. + * @param series the {@link javafx.scene.chart.XYChart.Series} data for the chart + * @param companyName the name of the company of the selected stock + */ + public void updateChart(XYChart.Series series, String companyName) { + stockLineChartView.setSeries(series, companyName); } - /* - public void onPurchaseButton(Runnable action) { - purchaseButton.setOnAction(event -> { - if (purchaseHandler == null) { - return; - } - - String selectedStock = getSelectedSymbol(); - if (selectedStock == null) { - return; - } - - int quantity = quantitySpinner.getValue(); - purchaseHandler.accept(selectedStock, quantity); - }); - }*/ + /** + * Unsubscribes from the previous stocks sales price and clears the LineChart. + */ + public void clearChart() { + if (priceSubscription != null) { + priceSubscription.unsubscribe(); + priceSubscription = null; + } + stockLineChartView.getChart().getData().clear(); + } - public void setPurchaseHandler(BiConsumer purchaseHandler) { - this.purchaseHandler = purchaseHandler; + /** + * Sets the action to be run on selecting a stock in stockListView. + * @param action the action to run when selecting a stock + */ + public void onStockSelection(Consumer action) { + stocksListView.getSelectionModel().selectedItemProperty().subscribe(action); } + /** + * Sets the action to be run when clicking the purchase button + * + * @param action the action to run when clicking the purchase button + */ + public void onPurchaseButton(Runnable action) { + purchaseButton.setOnAction(e -> action.run()); + } + /** + * Sets the player to subscribe to. + *

Observes the player's money value and formats it to max 2 decimals.

+ * + * @param player the player to observe + */ public void setPlayer(Player player) { + if (playerSubscription != null) { + playerSubscription.unsubscribe(); + playerSubscription = null; + } this.player = player; - - if (player == null) { + if (player != null) { + playerSubscription = player.getMoneyProperty().subscribe(newMoney -> { + playerMoneyLabel.setText("Money: $" + FormatBigDecimal.formatNumber(newMoney)); + }); + playerMoneyLabel.setText("Money: $" + FormatBigDecimal.formatNumber(player.getMoney())); + } else { playerMoneyLabel.setText("Money: $0.00"); - return; } - - playerMoneyLabel.setText("Money: $" + formatMoney(player.getMoney())); } + /** + * Sets the exchange to use for the view. + *

Uses a {@link SortedList} to sort the stocks in the exchange alphabetically + * on their symbol/ticker.

+ * + * @param exchange the exchange to be used for the view + */ public void setExchange(Exchange exchange) { this.exchange = exchange; - refreshStocks(); - } - - public void refreshPlayer() { - setPlayer(player); - } - - public void refreshStocks() { - if (exchange == null) { - stocksListView.getItems().clear(); - return; + if (exchange != null) { + SortedList sortedStocks = new SortedList<>(exchange.getObservableStocks()); + sortedStocks.setComparator((s1, s2) -> s1.getSymbol().compareToIgnoreCase(s2.getSymbol())); + stocksListView.setItems(sortedStocks); } - - List stocks = exchange.findStocks(""); - stocksListView.getItems().setAll( - stocks.stream() - .map(this::formatStock) - .collect(Collectors.toList()) - ); } - public String getSelectedSymbol() { - String selected = stocksListView.getSelectionModel().getSelectedItem(); - if (selected == null || selected.isBlank()) { - return null; - } - - int indexEnd = selected.indexOf("-"); - - - return selected.substring(0,indexEnd - 1 ); + /** + * Returns the selected stock in the stocksListView. + * + * @return the selected stock in the stocksListView + */ + public Stock getSelectedStock() { + return stocksListView.getSelectionModel().getSelectedItem(); } + /** + * Returns the numeric value of the quantitySpinner for purchasing {@link Share}s in a stock. + * + * @return an integer of the amount of shares to buy + */ public int getQuantityPurchase() { return quantitySpinner.getValue(); } - public void setSelectedStock(String selectedStockString) { + /** + * Sets the company name of the selected stock in the stocksListView. + * + * @param selectedStockString the company name of the selected stock + */ + public void setSelectedStockLabel(String selectedStockString) { selectedStock.setText(selectedStockString); } - private String formatStock(Stock stock) { - return stock.getSymbol() - + " - " - + stock.getCompany() - + " | Price: $" - + formatMoney(stock.getSalesPrice()); - } - private String formatMoney(BigDecimal money) { - return money == null ? "0.00" : money.setScale(2, RoundingMode.HALF_UP).toPlainString(); - } -} \ No newline at end of file +} From 7066d27b0d4aaae3a08f273f2dd6c2b4f405b32e Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:42:20 +0200 Subject: [PATCH 076/142] Updated PortfolioView Added JavaDoc. Added Observer pattern. Added colored text whether stock price went up or down compared to purchase price. --- .../no/ntnu/gruppe53/view/PortfolioView.java | 215 ++++++++---------- 1 file changed, 100 insertions(+), 115 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java index 928a00f..ddf4bec 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java @@ -1,182 +1,167 @@ package no.ntnu.gruppe53.view; import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.function.BiConsumer; +import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.scene.control.Button; import javafx.scene.control.Label; +import javafx.scene.control.ListCell; import javafx.scene.control.ListView; -import javafx.scene.control.Spinner; -import javafx.scene.control.SpinnerValueFactory; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; +import javafx.util.Subscription; import no.ntnu.gruppe53.model.Player; import no.ntnu.gruppe53.model.Share; - +import no.ntnu.gruppe53.service.FormatBigDecimal; + +/** + * Represent a view of the {@link Player}s {@link no.ntnu.gruppe53.model.Portfolio}. + *

Lets the player view their current {@link Share} assets, see their + * current sale value, whether the value has gone up or down since they purchased, + * and the ability to sell the share for money.

+ *

Extends the {@link BorderPane}

for easy layout setup. + *

Events are handled by the {@link no.ntnu.gruppe53.controller.GameController}.

+ */ public class PortfolioView extends BorderPane { - - - //Main pane of view - - - //Buttons private Button sellButton; - - //NavigationBar Buttons - private Button marketButton; - private Button portfolioButton; - - //Static Labels private Label viewLabel; - - //Dynamic Labels private Label playerNameLabel; - private Label netWorthLabel; - - //PortfolioListView private ListView portfolioListView; - //Input - private Spinner quantitySpinner; - - //Models private Player player; - //Handlers - private BiConsumer sellHandler; - private Runnable showMarketHandler; - - - + /** + * Builds the view components and places them in the center pane. + */ public PortfolioView() { - - - this.setTop(navigationBar()); this.setCenter(centerPane()); - - } - - private HBox navigationBar() { - - HBox hBox = new HBox(); - - //Sets simple styling - hBox.setPadding(new Insets(15, 12, 15, 12)); - hBox.setSpacing(10); - hBox.setStyle("-fx-background-color: #336699;"); - - marketButton = new Button("Market"); - portfolioButton = new Button("Portfolio"); - - hBox.getChildren().addAll(marketButton, portfolioButton); - - - return hBox; } + /** + * Constructs the view of the portfolio. + *

Subscribes to shares for sales price updates.

+ *

Uses a cell factory to format the displayed string representing each share.

+ * + * @return a VBox of the portfolio view + */ private VBox centerPane() { - VBox vBox = new VBox(); + VBox vBox = new VBox(10); vBox.setPadding(new Insets(15, 12, 15, 12)); - vBox.setSpacing(10); viewLabel = new Label("Portfolio"); viewLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); playerNameLabel = new Label("Player: -"); - netWorthLabel = new Label("Net worth: $0.00"); portfolioListView = new ListView<>(); portfolioListView.setPrefHeight(300); - portfolioListView.setCellFactory(list -> new javafx.scene.control.ListCell<>() { + + portfolioListView.setCellFactory(list -> new ListCell<>() { + private Subscription cellSubscription; + @Override protected void updateItem(Share item, boolean empty) { super.updateItem(item, empty); - setText(empty || item == null ? null : formatShare(item)); + + if (cellSubscription != null) { + cellSubscription.unsubscribe(); + cellSubscription = null; + } + + if (empty || item == null || item.getStock() == null) { + setText(null); + setGraphic(null); + } else { + setText(null); + cellSubscription = item.getStock().salesPriceProperty().subscribe(currentPrice -> { + setGraphic(createColoredShareText(item, currentPrice)); + }); + } } }); HBox sellBar = new HBox(10); - Label quantityLabel = new Label("Quantity:"); - quantitySpinner = new Spinner<>(); - quantitySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1_000_000, 1)); - quantitySpinner.setEditable(true); - sellButton = new Button("Sell"); - sellBar.getChildren().addAll(quantityLabel, quantitySpinner, sellButton); - - - vBox.getChildren().addAll(viewLabel, - playerNameLabel, - netWorthLabel, - portfolioListView, - sellBar); + sellBar.getChildren().addAll(sellButton); + vBox.getChildren().addAll(viewLabel, playerNameLabel, portfolioListView, sellBar); return vBox; } + /** + * Exposes the sell button to the GameController. + * @param action the action to be run when pressing the sell button + */ public void onSellButton(Runnable action) { - sellButton.setOnAction(event -> { - if (sellHandler == null) { - return; - } - - Share selectedShare = portfolioListView.getSelectionModel().getSelectedItem(); - if (selectedShare == null) { - return; - } - - int quantity = quantitySpinner.getValue(); - sellHandler.accept(selectedShare, quantity); - }); + sellButton.setOnAction(event -> action.run()); } - public void onMarketButton(Runnable action) { - marketButton.setOnAction(e -> action.run()); - } - - public void setShowMarketHandler(Runnable showMarketHandler) { - this.showMarketHandler = showMarketHandler; - } - - public void setSellHandler(BiConsumer sellHandler) { - this.sellHandler = sellHandler; + /** + * Gets the selected share in the portfolioListView. + * + * @return the currently selected share + */ + public Share getSelectedShare() { + return portfolioListView.getSelectionModel().getSelectedItem(); } + /** + * Sets the player to be observed. + *

The values observed are the players portfolio and name.

+ *

Uses an {@link ObservableList} to observe the shares and + * binds it to the portfolioListView.

+ * + * @param player the player to be observed + */ public void setPlayer(Player player) { this.player = player; - refresh(); - } - public void refresh() { - if (player == null) { + if (player != null && player.getPortfolio() != null) { + ObservableList observableShares = player.getPortfolio().getShares(); + + portfolioListView.setItems(observableShares); + + playerNameLabel.setText("Player: " + player.getName()); + } else { playerNameLabel.setText("Player: -"); - netWorthLabel.setText("Net worth: $0.00"); - portfolioListView.getItems().clear(); - return; + portfolioListView.setItems(null); } - - playerNameLabel.setText("Player: " + player.getName()); - netWorthLabel.setText("Net worth: $" + formatMoney(player.getNetWorth())); - portfolioListView.getItems().setAll(player.getPortfolio().getShares()); } - private String formatShare(Share share) { - return share.getStock().getSymbol() + /** + * Formats the text representation of the share in the portfolioListView + * and applies a color to it based on its current sales price compared + * to the buy price of the share. + * + * @param share the share to be formatted + * @param currentPrice the current price of the share + * @return a {@link TextFlow} representation of the share + */ + private TextFlow createColoredShareText(Share share, BigDecimal currentPrice) { + String text = share.getStock().getSymbol() + " - " + share.getStock().getCompany() + " | Qty: " + share.getQuantity() + " | Buy price: $" - + formatMoney(share.getPurchasePrice()); - } + + FormatBigDecimal.formatNumber(share.getPurchasePrice()) + + " | Current Price: $"; + + Text t1 = new Text(text); + Text tValue = new Text(FormatBigDecimal.formatNumber(currentPrice)); + + BigDecimal buyPrice = share.getPurchasePrice(); + if (buyPrice != null && currentPrice != null) { + int compare = currentPrice.compareTo(buyPrice); + if (compare > 0) tValue.setStyle("-fx-fill: #2ecc71; -fx-font-weight: bold;"); + else if (compare < 0) tValue.setStyle("-fx-fill: #e74c3c; -fx-font-weight: bold;"); + else tValue.setStyle("-fx-fill: #000000;"); + } - //Formats any BigDecimal to a legible money value with a scale of - > 0.00 to a plainStrign(). - private String formatMoney(BigDecimal money) { - return money == null ? "0.00" : money.setScale(2, RoundingMode.HALF_UP).toPlainString(); + return new TextFlow(t1, tValue); } - - } \ No newline at end of file From 24638da8480a2e500eee38ca990325d964aab1d7 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:43:24 +0200 Subject: [PATCH 077/142] Added TransactionArchiveView Lets the player view their transactions. --- .../gruppe53/view/TransactionArchiveView.java | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java new file mode 100644 index 0000000..81e501a --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java @@ -0,0 +1,103 @@ +package no.ntnu.gruppe53.view; + +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import java.math.BigDecimal; +import java.math.RoundingMode; +import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.model.Transaction; +import no.ntnu.gruppe53.model.Purchase; +import no.ntnu.gruppe53.model.Sale; +import no.ntnu.gruppe53.service.FormatBigDecimal; + +/** + * Represents a view of all the {@link Purchase} and {@link Sale} + * {@link Transaction}s a {@link Player} has committed in a game. + *

Extends the {@link BorderPane} for easy layout setup.

+ */ +public class TransactionArchiveView extends BorderPane { + + private ListView historyListView; + private Label titleLabel; + + /** + * Builds the view components and places them in the center pane. + */ + public TransactionArchiveView() { + this.setCenter(centerPane()); + } + + /** + * Constructs the view of the transaction archive. + *

Builds the necessary components and places them in a {@link VBox}.

+ *

Uses a cell factory to format the string shown

+ *

Assigns red text to purchases, and green text to sales.

+ *

Uses the formatBigDecimal method to convert the BigDecimal to a + * string with 2 decimals.

+ * + * @return a VBox of the constructed view of the transaction archive + */ + private VBox centerPane() { + VBox vBox = new VBox(10); + vBox.setPadding(new Insets(15, 12, 15, 12)); + + titleLabel = new Label("Transaction History"); + titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); + + historyListView = new ListView<>(); + historyListView.setPrefHeight(400); + + historyListView.setCellFactory(list -> new ListCell<>() { + @Override + protected void updateItem(Transaction item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(null); + setStyle(""); + } else { + String type = (item instanceof Purchase) ? "PURCHASE" : "SALE"; + String symbol = item.getShare().getStock().getSymbol(); + BigDecimal quantity = item.getShare().getQuantity(); + BigDecimal total = item.getCalculator().calculateTotal(); + + String cellText = String.format("Week %d | %s | %s | Qty: %s | Total: $%s", + item.getWeek(), + type, + symbol, + FormatBigDecimal.formatNumber(quantity), + FormatBigDecimal.formatNumber(total) + ); + + setText(cellText); + + if (item instanceof Purchase) { + setStyle("-fx-text-fill: #c0392b; -fx-font-weight: bold;"); + } else if (item instanceof Sale) { + setStyle("-fx-text-fill: #27ae60; -fx-font-weight: bold;"); + } + } + } + }); + + vBox.getChildren().addAll(titleLabel, historyListView); + return vBox; + } + + /** + * Sets the player to be observed. + * + * @param player the player to be observed + */ + public void setPlayer(Player player) { + if (player != null && player.getTransactionArchive() != null) { + historyListView.setItems(player.getTransactionArchive().getObservableTransactions()); + } else { + historyListView.setItems(null); + } + } +} From 62594d51736dde4ede24cb5f3ea57723a4be169d Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:45:29 +0200 Subject: [PATCH 078/142] Added GameSetupController Handles user input for data like name, starting money and .csv file to use. --- .../controller/GameSetupController.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/controller/GameSetupController.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameSetupController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameSetupController.java new file mode 100644 index 0000000..85432b9 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameSetupController.java @@ -0,0 +1,119 @@ +package no.ntnu.gruppe53.controller; + +import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.model.Stock; +import no.ntnu.gruppe53.model.Exchange; +import no.ntnu.gruppe53.service.FileHandler; +import no.ntnu.gruppe53.view.CSVChooserView; +import no.ntnu.gruppe53.view.PlayerNameChooserView; +import no.ntnu.gruppe53.view.PlayerStartingMoneyChooserView; +import javafx.stage.Stage; + +import java.io.File; +import java.math.BigDecimal; +import java.util.List; + +/** + * Controller for setting up relevant game data to start a new game. + *

Lets the {@link Player} set their name, starting money + * and provide a .csv file of {@link Stock} data to populate + * the {@link Exchange}.

+ */ +public class GameSetupController { + /** + * Nested class to pass the player and stocks values over to the + * {@link GameController}. + */ + public static class SetupResult { + private final Player player; + private final List stocks; + + /** + * Initializes the player and stock variables. + * @param player the player that will play the stock game + * @param stocks the list of stocks to populate the exchange + */ + public SetupResult(Player player, List stocks) { + this.player = player; + this.stocks = stocks; + } + + /** + * Returns the player object containing the set name and starting money. + * + * @return the player object for the player playing the game + */ + public Player getPlayer() { + return player; + } + + /** + * A list of stocks that will be used for buying and selling on the exchange. + * + * @return a list of stock objects + */ + public List getStocks() { + return stocks; + } + } + + private final Stage stage; + + /** + * Initializes the initial stage for the view setting up the relevant game data. + * + * @param stage the initial stage to be used + */ + public GameSetupController(Stage stage) { + this.stage = stage; + } + + /** + * Sets up the initial data for the {@code Player} and {@code Exchange}. + *

Uses default values if the player does not enter their desired values: + *

    + *
  • Player name: Player
  • + *
  • Player starting money: 5000
  • + *
  • CSV file: resources/sp500.csv
  • + *

+ * + * @return a SetupResult object containing data for the player and the exchange + */ + public SetupResult setupNewGame() { + PlayerNameChooserView playerNameDialog = new PlayerNameChooserView(); + // Player name + String playerName = + playerNameDialog.getDialog() + .orElse("Player"); + + if (playerName.isBlank()) { + playerName = "Player"; + } + + // Starting money + PlayerStartingMoneyChooserView playerStartingMoneyDialog = new PlayerStartingMoneyChooserView(); + + BigDecimal startingMoney = + playerStartingMoneyDialog.getDialog() + .orElse(new BigDecimal("5000")); + + + // .csv File + CSVChooserView csvChooser = new CSVChooserView(); + File csvFile = csvChooser.getDialog(stage); + + List stocks; + if (csvFile == null) { + stocks = FileHandler.readStocksFromFile("src/main/resources", "sp500.csv"); + } else { + stocks = FileHandler.readStocksFromFile(csvFile.getParent(), csvFile.getName()); + if (stocks.isEmpty()) { + stocks = FileHandler.readStocksFromFile("src/main/resources", "sp500.csv"); + } + } + + Player player = new Player(playerName, startingMoney); + + return new SetupResult(player, stocks); + } +} \ No newline at end of file From a4318a5248d4ee2106ede864a1aa961e4735ef91 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:46:59 +0200 Subject: [PATCH 079/142] Updated ViewController Restructured to have always on NavigationBar and FooterBar, and only swaps out center pane in the border pane layout. --- .../gruppe53/controller/ViewController.java | 65 ++++++++++++------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java index 3c91d5a..3d7f25c 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java @@ -2,52 +2,71 @@ import javafx.scene.Parent; import javafx.scene.Scene; +import javafx.scene.layout.BorderPane; import javafx.stage.Stage; +import no.ntnu.gruppe53.view.FooterBar; +import no.ntnu.gruppe53.view.NavigationBar; import java.util.HashMap; import java.util.Map; /** - * Manages application views and handles switching between them. - *

- * Stores a mapping of view names to JavaFX {@link Parent} nodes and updates - * the current Scene by replacing its root node when switching views. - *

+ * Controller for setting and switching between views. + *

Uses a {@link HashMap} as an archive for registered views that + * are possible to switch between.

*/ public class ViewController { - /** Primary application window used to display the Scene. */ - private Stage stage; - /** Scene containing the currently active view. */ - private Scene scene; - /** Map of view identifiers to their corresponding UI roots. */ - private Map views = new HashMap<>(); + private final Stage stage; + private final Scene scene; + private final Map views = new HashMap<>(); + private final BorderPane mainLayout; /** - * Constructs a ViewController and initializes a new Scene. - * - * @param stage the primary application stage where the scene will be set + * Initializes a ViewController using the {@link Stage} as the first default view. + *

Uses a {@link BorderPane} layout for views.

*/ public ViewController(Stage stage) { this.stage = stage; - this.scene = new Scene(new javafx.scene.layout.BorderPane(), 800, 600); - stage.setScene(scene); + this.mainLayout = new BorderPane(); + this.scene = new Scene(mainLayout, 800, 600); + this.stage.setScene(scene); + } + + /** + * Returns the stage of the ViewController. + * + * @return the stage of the ViewController. + */ + public Stage getStage() { + return this.stage; + } + + /** + * Sets the top and bottom part of the BorderPane. + *

Uses {@link NavigationBar} as the top bar, + * and {@link FooterBar} as the bottom bar.

+ */ + public void setGlobalPanels(NavigationBar navigationBar, FooterBar footerBar) { + this.mainLayout.setTop(navigationBar); + this.mainLayout.setBottom(footerBar); } /** - * Registers a view in the controller. + * Adds a view to the stored archive of possible views. * - * @param name unique identifier used to reference the view later - * @param view the JavaFX root node representing the view + * @param name the name/key to refer to the view when switching view + * @param view the view to be added */ public void addView(String name, Parent view) { views.put(name, view); } /** - * Switches the currently displayed view by replacing the Scene root. + * Switches the to the given view based on the string key + * stored in the view archive. + *

Only switches the center pane.

* - * @param name the identifier of the view to display - * @throws IllegalArgumentException if no view is registered under the given name + * @param name the name/key of the view to switch to */ public void switchView(String name) { Parent view = views.get(name); @@ -56,6 +75,6 @@ public void switchView(String name) { throw new IllegalArgumentException("No views found matching: " + name); } - scene.setRoot(view); + mainLayout.setCenter(view); } } From 9dbd38f04499f43e4e92e7c78dd8aafba75ebfdb Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:49:02 +0200 Subject: [PATCH 080/142] Updated GameController Added JavaDoc. Sets up global top and bottom bars. Restructuring to fit changes in views. --- .../gruppe53/controller/GameController.java | 206 +++++++++++------- 1 file changed, 130 insertions(+), 76 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index c403958..ddc86ad 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -3,134 +3,188 @@ import java.math.BigDecimal; import java.util.List; -import no.ntnu.gruppe53.model.Exchange; -import no.ntnu.gruppe53.view.FileHandler; -import no.ntnu.gruppe53.model.Player; -import no.ntnu.gruppe53.model.Share; -import no.ntnu.gruppe53.model.Stock; -import no.ntnu.gruppe53.view.PortfolioView; -import no.ntnu.gruppe53.view.MarketView; - +import javafx.scene.control.Alert; +import javafx.stage.Stage; +import no.ntnu.gruppe53.model.*; +import no.ntnu.gruppe53.service.StockLineChartService; +import no.ntnu.gruppe53.view.*; + +/** + * Represents the main controller for the views of the stock game. + */ public class GameController { - private final ViewController vm; - private final FileHandler fileHandler = new FileHandler(); - private Player player; private Exchange exchange; private List stocks; + + private final ViewController vm; + private MarketView marketView; private PortfolioView portfolioView; + private TransactionArchiveView transactionArchiveView; + /** + * Initializes the {@link ViewController} to be used for switching views. + * @param vm {@code ViewController} to be used when switching views + */ public GameController(ViewController vm) { this.vm = vm; } + /** + * Starts a new game by initializing all main views and navigation. + *

Creates the player and exchange to be used by the game from + * data from the {@link GameSetupController}.

+ */ public void startNewGame() { - player = new Player("Player 1", new BigDecimal("100000")); - stocks = fileHandler.readStocksFromFile("src/main/resources", "sp500.csv"); + Stage primaryStage = vm.getStage(); + + GameSetupController setupController = new GameSetupController(primaryStage); + GameSetupController.SetupResult setupResult = setupController.setupNewGame(); + if (setupResult == null) return; + + player = setupResult.getPlayer(); + stocks = setupResult.getStocks(); + exchange = new Exchange("Millions Exchange", stocks); + NavigationBar sharedNav = new NavigationBar(); + FooterBar sharedFooter = new FooterBar(); + vm.setGlobalPanels(sharedNav, sharedFooter); + marketView = new MarketView(); marketView.setPlayer(player); marketView.setExchange(exchange); - //marketView.setPurchaseHandler(this::purchaseStock); portfolioView = new PortfolioView(); portfolioView.setPlayer(player); - //marketView.setShowPortfolioHandler(this::showPortfolio); - portfolioView.setShowMarketHandler(this::showPurchaseView); - portfolioView.setSellHandler(this::sellShare); + transactionArchiveView = new TransactionArchiveView(); + transactionArchiveView.setPlayer(player); + + sharedFooter.bindPlayer(player); - //vm.addView("purchase", purchaseView); vm.addView("portfolio", portfolioView); vm.addView("market", marketView); + vm.addView("history", transactionArchiveView); - initialize(); + initialize(sharedNav, sharedFooter); vm.switchView("market"); } - - - - - public void showPortfolio() { - if (portfolioView != null) { - portfolioView.setPlayer(player); - vm.switchView("portfolio"); - } - } - - public void showPurchaseView() { + /** + * Switches the view to the {@link MarketView}. + *

MarketView lets the player perform {@link Purchase} transactions.

+ */ + public void showMarketView() { if (marketView != null) { - marketView.setPlayer(player); - marketView.refreshStocks(); - vm.switchView("purchase"); + vm.switchView("market"); } } - private void initialize() { - - //portfolio actions - portfolioView.onMarketButton(() -> { - vm.switchView("market"); - }); + /** + * Switches the view to the {@link PortfolioView}. + *

Contains the data from a player's {@link Portfolio}.

+ *

PortfolioView lets the player perform {@link Sale} transactions.

+ */ + public void showPortfolio() { + if (portfolioView != null) vm.switchView("portfolio"); + } + /** + * Switches the view to the {@link TransactionArchiveView}. + *

TransactionArchiveView contains data from {@link TransactionArchive}

+ */ + public void showTransactionHistory() { + if (transactionArchiveView != null) vm.switchView("history"); + } - //market actions - marketView.onPortfolioButton(() -> { - vm.switchView("portfolio"); - portfolioView.refresh(); + /** + * Initializes the {@link NavigationBar} and {@link FooterBar} for the controller. + *

Binds functionality to exposed buttons and lists in views.

+ *

Sets subscriptions for observable values for views.

+ * @param nav the NavigationBar view + * @param footer the FooterBar view + */ + public void initialize(NavigationBar nav, FooterBar footer) { + nav.onMarketButtonClick(() -> vm.switchView("market")); + nav.onPortfolioButtonClick(() -> vm.switchView("portfolio")); + nav.onHistoryButtonClick(() -> vm.switchView("history")); + + footer.onAdvanceButtonClick(() -> { + if (exchange != null) { + exchange.advance(); + } }); - marketView.onStockSelection(newValue ->{ - marketView.setSelectedStock(newValue); + marketView.onStockSelection(stock -> { + if (stock != null) { + marketView.setSelectedStockLabel(stock.getSymbol() + " - " + stock.getCompany()); + marketView.setupPriceSubscription(stock, currentStock -> { + var series = StockLineChartService.buildSeries(currentStock, exchange); + marketView.updateChart(series, currentStock.getCompany()); + }); + } else { + marketView.setSelectedStockLabel(""); + marketView.clearChart(); + } }); marketView.onPurchaseButton(() -> { - purchaseStock(marketView.getSelectedSymbol(), marketView.getQuantityPurchase()); + Stock stock = marketView.getSelectedStock(); + if (stock != null) purchaseStock(stock.getSymbol(), marketView.getQuantityPurchase()); }); - - + portfolioView.onSellButton(this::handleSell); } + /** + * Lets a player purchase {@link Share}s in a stock. + *

Utilizes the buy method from {@link Exchange}.

+ * @param stockSymbol the symbol/ticker of the stock to purchase shares for + * @param quantity the quantity of shares to be purchased + */ private void purchaseStock(String stockSymbol, int quantity) { - if (exchange == null || player == null || stockSymbol == null || quantity <= 0) { - return; - } - + if (exchange == null || player == null || stockSymbol == null || quantity <= 0) return; try { exchange.buy(stockSymbol, BigDecimal.valueOf(quantity), player); - marketView.setPlayer(player); - marketView.refreshStocks(); - portfolioView.setPlayer(player); - } catch (RuntimeException ex) { - System.out.println("Purchase failed: " + ex.getMessage()); + } catch (IllegalStateException | IllegalArgumentException ex) { + showErrorAlert("Purchase Failed", ex.getMessage()); } } - private void sellShare(Share share, int quantity) { - if (exchange == null || player == null || share == null || quantity <= 0) { - return; - } + /** + * Handles sale of the selected share. + */ + private void handleSell() { + Share selectedShare = portfolioView.getSelectedShare(); + if (selectedShare != null) sellShare(selectedShare); + } + /** + * Lets a player sell a share from their portfolio. + * @param selectedShare the currently selected share + */ + private void sellShare(Share selectedShare) { + if (exchange == null || player == null || selectedShare == null) return; try { - Share shareToSell = new Share( - share.getStock(), - BigDecimal.valueOf(quantity), - share.getPurchasePrice() - ); - - exchange.sell(shareToSell, player); - marketView.setPlayer(player); - marketView.refreshStocks(); - portfolioView.setPlayer(player); - } catch (RuntimeException ex) { - System.out.println("Sell failed: " + ex.getMessage()); + exchange.sell(selectedShare, player); + } catch (IllegalStateException | IllegalArgumentException ex) { + showErrorAlert("Sale Failed", ex.getMessage()); } } - -} \ No newline at end of file + /** + * Creates a default error alert to be used for notifying the player of errors. + * @param title title of the error alert + * @param content the message content of the error alert + */ + private void showErrorAlert(String title, String content) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(content); + alert.showAndWait(); + } +} From b51e7e46c2084be03f98133925b029b820b9c061 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:49:50 +0200 Subject: [PATCH 081/142] Updated StartViewController Added JavaDoc. --- .../controller/StartViewController.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java index a588e20..de9e1ae 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java @@ -4,16 +4,25 @@ import javafx.application.Platform; /** - * aaaa + * The controller for {@link StartView}, + * the first view for the player upon launching the app. + *

Contains two choices: + *

    + *
  • New Game
  • + *
  • Quit
  • + *

*/ public class StartViewController { - + /** + * Handles the events when "New Game" and "Quit" is pressed. + * + * @param view the StartView to be displayed + * @param vm the {@link ViewController} to handle switching of views + */ public StartViewController(StartView view, ViewController vm) { - view.setOnNewGame(() -> { GameController gameController = new GameController(vm); gameController.startNewGame(); - }); view.setOnQuit(Platform::exit); From 0e780720dd97812acebb4b14110b82238010fdd7 Mon Sep 17 00:00:00 2001 From: Roar Date: Sat, 23 May 2026 10:51:48 +0200 Subject: [PATCH 082/142] Updated App Added JavaDoc. --- millions/src/main/java/no/ntnu/gruppe53/App.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/App.java b/millions/src/main/java/no/ntnu/gruppe53/App.java index 6e3fa61..b3c3b06 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/App.java +++ b/millions/src/main/java/no/ntnu/gruppe53/App.java @@ -6,7 +6,11 @@ import no.ntnu.gruppe53.controller.ViewController; import no.ntnu.gruppe53.view.StartView; - +/** + * Represents the main app of the application. + *

Sets up necessary components and shows the first view + * of the application: {@link StartView}.

+ */ public class App extends Application { @Override public void start(Stage stage) { From 54bc1dd5972c75808f6dfe8b063aa377072f289e Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Sat, 23 May 2026 13:18:48 +0200 Subject: [PATCH 083/142] renamed file names and packages to fit with issue 38 --- .../src/main/java/no/ntnu/gruppe53/App.java | 2 +- .../gruppe53/controller/GameController.java | 6 ++-- .../controller/StartViewController.java | 2 +- .../ntnu/gruppe53/{ => model}/Exchange.java | 2 +- .../gruppe53/{ => model}/FileHandler.java | 2 +- .../no/ntnu/gruppe53/{ => model}/Player.java | 2 +- .../ntnu/gruppe53/{ => model}/Portfolio.java | 2 +- .../ntnu/gruppe53/{ => model}/Purchase.java | 2 +- .../{ => model}/PurchaseCalculator.java | 3 +- .../no/ntnu/gruppe53/{ => model}/Sale.java | 2 +- .../gruppe53/{ => model}/SaleCalculator.java | 2 +- .../no/ntnu/gruppe53/{ => model}/Share.java | 2 +- .../no/ntnu/gruppe53/{ => model}/Stock.java | 2 +- .../gruppe53/{ => model}/Transaction.java | 2 +- .../{ => model}/TransactionArchive.java | 2 +- .../{ => model}/TransactionCalculator.java | 2 +- .../BottomView.java => view/FooterBar.java} | 6 ++-- .../gruppe53/{views => view}/MarketView.java | 31 +++++++------------ .../NavigationBar.java} | 7 ++--- .../{views => view}/PortfolioView.java | 26 ++++++++-------- .../gruppe53/{views => view}/StartView.java | 2 +- .../java/no/ntnu/gruppe53/ExchangeTest.java | 5 ++- .../no/ntnu/gruppe53/FileHandlerTest.java | 2 ++ .../java/no/ntnu/gruppe53/PlayerTest.java | 1 + .../java/no/ntnu/gruppe53/PortfolioTest.java | 4 +++ .../ntnu/gruppe53/PurchaseCalculatorTest.java | 3 ++ .../java/no/ntnu/gruppe53/PurchaseTest.java | 1 + .../ntnu/gruppe53/SaleCalculatorUnitTest.java | 3 ++ .../test/java/no/ntnu/gruppe53/SaleTest.java | 1 + .../test/java/no/ntnu/gruppe53/ShareTest.java | 2 ++ .../test/java/no/ntnu/gruppe53/StockTest.java | 1 + .../ntnu/gruppe53/TransactionArchiveTest.java | 1 + 32 files changed, 72 insertions(+), 61 deletions(-) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Exchange.java (99%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/FileHandler.java (99%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Player.java (99%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Portfolio.java (99%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Purchase.java (98%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/PurchaseCalculator.java (97%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Sale.java (98%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/SaleCalculator.java (98%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Share.java (98%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Stock.java (99%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/Transaction.java (98%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/TransactionArchive.java (99%) rename millions/src/main/java/no/ntnu/gruppe53/{ => model}/TransactionCalculator.java (86%) rename millions/src/main/java/no/ntnu/gruppe53/{views/BottomView.java => view/FooterBar.java} (91%) rename millions/src/main/java/no/ntnu/gruppe53/{views => view}/MarketView.java (88%) rename millions/src/main/java/no/ntnu/gruppe53/{views/NavigationView.java => view/NavigationBar.java} (96%) rename millions/src/main/java/no/ntnu/gruppe53/{views => view}/PortfolioView.java (94%) rename millions/src/main/java/no/ntnu/gruppe53/{views => view}/StartView.java (98%) diff --git a/millions/src/main/java/no/ntnu/gruppe53/App.java b/millions/src/main/java/no/ntnu/gruppe53/App.java index f3ec24a..6e3fa61 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/App.java +++ b/millions/src/main/java/no/ntnu/gruppe53/App.java @@ -4,7 +4,7 @@ import javafx.stage.Stage; import no.ntnu.gruppe53.controller.StartViewController; import no.ntnu.gruppe53.controller.ViewController; -import no.ntnu.gruppe53.views.StartView; +import no.ntnu.gruppe53.view.StartView; public class App extends Application { diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index dd9ffce..ce5f740 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -7,9 +7,9 @@ import java.util.Map; import java.util.stream.Collectors; -import no.ntnu.gruppe53.*; -import no.ntnu.gruppe53.views.PortfolioView; -import no.ntnu.gruppe53.views.MarketView; +import no.ntnu.gruppe53.model.*; +import no.ntnu.gruppe53.view.PortfolioView; +import no.ntnu.gruppe53.view.MarketView; public class GameController { private final ViewController vm; diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java index acf3d43..a588e20 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java @@ -1,6 +1,6 @@ package no.ntnu.gruppe53.controller; -import no.ntnu.gruppe53.views.StartView; +import no.ntnu.gruppe53.view.StartView; import javafx.application.Platform; /** diff --git a/millions/src/main/java/no/ntnu/gruppe53/Exchange.java b/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java similarity index 99% rename from millions/src/main/java/no/ntnu/gruppe53/Exchange.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java index 31fc20f..731f770 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Exchange.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; import java.math.RoundingMode; diff --git a/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java b/millions/src/main/java/no/ntnu/gruppe53/model/FileHandler.java similarity index 99% rename from millions/src/main/java/no/ntnu/gruppe53/FileHandler.java rename to millions/src/main/java/no/ntnu/gruppe53/model/FileHandler.java index 13e26a6..fcd4224 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/FileHandler.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.io.BufferedReader; import java.io.BufferedWriter; diff --git a/millions/src/main/java/no/ntnu/gruppe53/Player.java b/millions/src/main/java/no/ntnu/gruppe53/model/Player.java similarity index 99% rename from millions/src/main/java/no/ntnu/gruppe53/Player.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Player.java index f10e8a3..84840dd 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Player.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Player.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; import java.math.RoundingMode; diff --git a/millions/src/main/java/no/ntnu/gruppe53/Portfolio.java b/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java similarity index 99% rename from millions/src/main/java/no/ntnu/gruppe53/Portfolio.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java index 2f40774..7744c57 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Portfolio.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; import java.util.ArrayList; diff --git a/millions/src/main/java/no/ntnu/gruppe53/Purchase.java b/millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java similarity index 98% rename from millions/src/main/java/no/ntnu/gruppe53/Purchase.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java index 0b622e2..dbc9a5d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Purchase.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; diff --git a/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java similarity index 97% rename from millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java rename to millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java index 752b3f0..0f983ad 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java @@ -1,7 +1,6 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; -import java.math.RoundingMode; /* PurchaseCalculator class: diff --git a/millions/src/main/java/no/ntnu/gruppe53/Sale.java b/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java similarity index 98% rename from millions/src/main/java/no/ntnu/gruppe53/Sale.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Sale.java index 00787d0..d8a6c36 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Sale.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; /** * Represents a sale {@link Transaction} where a {@link Player} sells diff --git a/millions/src/main/java/no/ntnu/gruppe53/SaleCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java similarity index 98% rename from millions/src/main/java/no/ntnu/gruppe53/SaleCalculator.java rename to millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java index b3b8345..cdc8371 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/SaleCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; diff --git a/millions/src/main/java/no/ntnu/gruppe53/Share.java b/millions/src/main/java/no/ntnu/gruppe53/model/Share.java similarity index 98% rename from millions/src/main/java/no/ntnu/gruppe53/Share.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Share.java index 6e3e21e..f2ec731 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Share.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Share.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; diff --git a/millions/src/main/java/no/ntnu/gruppe53/Stock.java b/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java similarity index 99% rename from millions/src/main/java/no/ntnu/gruppe53/Stock.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Stock.java index 71d93f4..5d06d45 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Stock.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; import java.util.ArrayList; diff --git a/millions/src/main/java/no/ntnu/gruppe53/Transaction.java b/millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java similarity index 98% rename from millions/src/main/java/no/ntnu/gruppe53/Transaction.java rename to millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java index 5ba478c..bb661c0 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/Transaction.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; /** * Represents a financial transaction of a {@link Share}. diff --git a/millions/src/main/java/no/ntnu/gruppe53/TransactionArchive.java b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java similarity index 99% rename from millions/src/main/java/no/ntnu/gruppe53/TransactionArchive.java rename to millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java index a6ca20d..1dc25ba 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/TransactionArchive.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.util.ArrayList; import java.util.HashSet; diff --git a/millions/src/main/java/no/ntnu/gruppe53/TransactionCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java similarity index 86% rename from millions/src/main/java/no/ntnu/gruppe53/TransactionCalculator.java rename to millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java index 0ad9274..96ef413 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/TransactionCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53; +package no.ntnu.gruppe53.model; import java.math.BigDecimal; diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/BottomView.java b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java similarity index 91% rename from millions/src/main/java/no/ntnu/gruppe53/views/BottomView.java rename to millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java index 0debe47..88af483 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/BottomView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53.views; +package no.ntnu.gruppe53.view; import javafx.geometry.Insets; import javafx.scene.control.Button; @@ -18,13 +18,13 @@ */ -public class BottomView extends HBox { +public class FooterBar extends HBox { //Buttons private Button advanceWeek; - public BottomView() { + public FooterBar() { this.setPadding(new Insets(15, 12, 15, 12)); this.setSpacing(10); //Blue Background color diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java similarity index 88% rename from millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java rename to millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java index 3f4665e..00c948b 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java @@ -1,11 +1,6 @@ -package no.ntnu.gruppe53.views; +package no.ntnu.gruppe53.view; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.List; -import java.util.function.BiConsumer; import java.util.function.Consumer; -import java.util.stream.Collectors; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -14,10 +9,6 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import no.ntnu.gruppe53.Exchange; -import no.ntnu.gruppe53.Player; -import no.ntnu.gruppe53.Stock; -import no.ntnu.gruppe53.controller.GameController; /* Market View: @@ -33,8 +24,8 @@ public class MarketView extends BorderPane { //Main pane of view - private final NavigationView navigationView; - private final BottomView bottomView; + private final NavigationBar navigationBar; + private final FooterBar footerBar; //Buttons private Button purchaseButton; @@ -57,12 +48,12 @@ public class MarketView extends BorderPane { public MarketView() { - navigationView = new NavigationView(); - bottomView = new BottomView(); + navigationBar = new NavigationBar(); + footerBar = new FooterBar(); - this.setTop(navigationView); + this.setTop(navigationBar); this.setCenter(centerPane()); - this.setBottom(bottomView); + this.setBottom(footerBar); } @@ -231,11 +222,11 @@ public void setCalculation() { - public NavigationView getNavigationView() { - return navigationView; + public NavigationBar getNavigationView() { + return navigationBar; } - public BottomView getBottomView() { - return bottomView; + public FooterBar getBottomView() { + return footerBar; } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java similarity index 96% rename from millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java rename to millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java index 1aa6fe5..98ef461 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/NavigationView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53.views; +package no.ntnu.gruppe53.view; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -7,7 +7,6 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; -import javafx.scene.layout.VBox; import java.math.BigDecimal; import java.math.RoundingMode; @@ -24,7 +23,7 @@ */ -public class NavigationView extends HBox { +public class NavigationBar extends HBox { @@ -37,7 +36,7 @@ public class NavigationView extends HBox { public Label networthLabel; public Label moneyLabel; - public NavigationView() { + public NavigationBar() { this.setPadding(new Insets(15, 12, 15, 12)); this.setSpacing(10); diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java similarity index 94% rename from millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java rename to millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java index 314f798..0e5a84f 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53.views; +package no.ntnu.gruppe53.view; import java.math.BigDecimal; import java.math.RoundingMode; @@ -12,8 +12,8 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import no.ntnu.gruppe53.Player; -import no.ntnu.gruppe53.Share; +import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.model.Share; /* Portfolio View: @@ -30,8 +30,8 @@ public class PortfolioView extends BorderPane { //Main pane of view - private final NavigationView navigationView; - private final BottomView bottomView; + private final NavigationBar navigationBar; + private final FooterBar footerBar; //Buttons private Button sellButton; @@ -67,12 +67,12 @@ public class PortfolioView extends BorderPane { public PortfolioView() { - navigationView = new NavigationView(); - bottomView = new BottomView(); + navigationBar = new NavigationBar(); + footerBar = new FooterBar(); - this.setTop(navigationView); + this.setTop(navigationBar); this.setCenter(centerPane()); - this.setBottom(bottomView); + this.setBottom(footerBar); } @@ -261,12 +261,12 @@ public ListView getPortfolioListView() { return portfolioListView; } - public NavigationView getNavigationView() { - return navigationView; + public NavigationBar getNavigationView() { + return navigationBar; } - public BottomView getBottomView() { - return bottomView; + public FooterBar getBottomView() { + return footerBar; } public void setQuantitySpinner(int quantitySpinner) { diff --git a/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java b/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java similarity index 98% rename from millions/src/main/java/no/ntnu/gruppe53/views/StartView.java rename to millions/src/main/java/no/ntnu/gruppe53/view/StartView.java index f5c30ef..5756028 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java @@ -1,4 +1,4 @@ -package no.ntnu.gruppe53.views; +package no.ntnu.gruppe53.view; import javafx.geometry.Pos; import javafx.scene.control.Button; diff --git a/millions/src/test/java/no/ntnu/gruppe53/ExchangeTest.java b/millions/src/test/java/no/ntnu/gruppe53/ExchangeTest.java index 7167bd1..f4b43ef 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/ExchangeTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/ExchangeTest.java @@ -1,13 +1,16 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.Exchange; +import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.util.ArrayList; -import java.util.*; import static org.junit.jupiter.api.Assertions.*; diff --git a/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java b/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java index 8b860b9..e3cf274 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/FileHandlerTest.java @@ -1,5 +1,7 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.FileHandler; +import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/millions/src/test/java/no/ntnu/gruppe53/PlayerTest.java b/millions/src/test/java/no/ntnu/gruppe53/PlayerTest.java index 8e10787..845fb75 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/PlayerTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/PlayerTest.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.*; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/PortfolioTest.java b/millions/src/test/java/no/ntnu/gruppe53/PortfolioTest.java index 8534868..badccdf 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/PortfolioTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/PortfolioTest.java @@ -1,5 +1,9 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.Portfolio; +import no.ntnu.gruppe53.model.SaleCalculator; +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/PurchaseCalculatorTest.java b/millions/src/test/java/no/ntnu/gruppe53/PurchaseCalculatorTest.java index 961cc9f..0dba3f2 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/PurchaseCalculatorTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/PurchaseCalculatorTest.java @@ -1,5 +1,8 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.PurchaseCalculator; +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/millions/src/test/java/no/ntnu/gruppe53/PurchaseTest.java b/millions/src/test/java/no/ntnu/gruppe53/PurchaseTest.java index c6b5ba5..72816d8 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/PurchaseTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/PurchaseTest.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.*; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/SaleCalculatorUnitTest.java b/millions/src/test/java/no/ntnu/gruppe53/SaleCalculatorUnitTest.java index 6434eff..7b4ea9d 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/SaleCalculatorUnitTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/SaleCalculatorUnitTest.java @@ -1,5 +1,8 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.SaleCalculator; +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/millions/src/test/java/no/ntnu/gruppe53/SaleTest.java b/millions/src/test/java/no/ntnu/gruppe53/SaleTest.java index 2478d80..d7820fe 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/SaleTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/SaleTest.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.*; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/ShareTest.java b/millions/src/test/java/no/ntnu/gruppe53/ShareTest.java index 8e88b3b..ffb5e0f 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/ShareTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/ShareTest.java @@ -1,5 +1,7 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/StockTest.java b/millions/src/test/java/no/ntnu/gruppe53/StockTest.java index c9abf4c..69b4a15 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/StockTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/StockTest.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.Stock; import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/millions/src/test/java/no/ntnu/gruppe53/TransactionArchiveTest.java b/millions/src/test/java/no/ntnu/gruppe53/TransactionArchiveTest.java index 8912ab3..39234d3 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/TransactionArchiveTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/TransactionArchiveTest.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53; +import no.ntnu.gruppe53.model.*; import org.junit.jupiter.api.Test; import java.math.BigDecimal; From f8d48fc697e7694f1af05b184f8a4036043779ca Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Sat, 23 May 2026 14:56:20 +0200 Subject: [PATCH 084/142] Fixed bugs, it now builds --- .../gruppe53/controller/GameController.java | 2 +- .../no/ntnu/gruppe53/service/FileHandler.java | 5 +-- .../java/no/ntnu/gruppe53/view/FooterBar.java | 3 +- .../no/ntnu/gruppe53/view/MarketView.java | 4 +- .../no/ntnu/gruppe53/view/NavigationBar.java | 7 +-- millions/target/classes/Millions-logo.png | Bin 0 -> 690 bytes millions/target/classes/Scroll-dot.png | Bin 0 -> 448 bytes .../compile/default-compile/createdFiles.lst | 11 ----- .../compile/default-compile/inputFiles.lst | 42 +++++++++++------- 9 files changed, 35 insertions(+), 39 deletions(-) create mode 100644 millions/target/classes/Millions-logo.png create mode 100644 millions/target/classes/Scroll-dot.png diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index 1f89852..b70392a 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -59,7 +59,7 @@ public void startNewGame() { vm.setGlobalPanels(sharedNav, sharedFooter); marketView = new MarketView(); - marketView.setPlayer(player); + //marketView.setPlayer(player); marketView.setExchange(exchange); portfolioView = new PortfolioView(); diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java b/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java index 66ab2cb..8ed2b0d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java +++ b/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java @@ -1,10 +1,7 @@ -<<<<<<<< HEAD:millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java package no.ntnu.gruppe53.service; import no.ntnu.gruppe53.model.Stock; -======== -package no.ntnu.gruppe53.model; ->>>>>>>> origin/develop:millions/src/main/java/no/ntnu/gruppe53/model/FileHandler.java + import java.io.BufferedReader; import java.io.BufferedWriter; diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java index a0ee74c..ddcf004 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java @@ -44,8 +44,7 @@ public FooterBar() { Region spacer = new Region(); HBox.setHgrow(spacer, Priority.ALWAYS); - Region spacer = new Region(); - HBox.setHgrow(spacer, Priority.ALWAYS); + advanceWeekButton = new Button("Advance Week"); advanceWeekButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java index b9be250..8bb085d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java @@ -99,7 +99,7 @@ private VBox centerPane() { stockLineChartView.getChart().setMinHeight(0); stockLineChartView.getChart().setMaxHeight(Double.MAX_VALUE); - stockLineChartView.setStyle("-fx-background-color: transparent;"); + stocksListView = new ListView<>(); @@ -146,7 +146,7 @@ protected void updateItem(Stock stock, boolean empty) { VBox.setVgrow(stocksListView, Priority.ALWAYS); VBox.setVgrow(stockLineChartView.getChart(), Priority.ALWAYS); - HBox.setVgrow(stockListBox, Priority.ALWAYS); + HBox.setHgrow(stockListBox, Priority.ALWAYS); HBox selectedStockBox = new HBox(); diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java index 12f05eb..725a4d5 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java @@ -34,8 +34,7 @@ public class NavigationBar extends HBox { //Navigation Buttons - public Button marketButton; - public Button portfolioButton; + //Dynamic Labels public Label playerLabel; @@ -56,6 +55,8 @@ public NavigationBar() { marketButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); portfolioButton = new Button("Portfolio"); portfolioButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + historyButton = new Button("History"); + historyButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); Region spacer = new Region(); HBox.setHgrow(spacer, Priority.ALWAYS); @@ -107,7 +108,7 @@ public NavigationBar() { networthBox.setAlignment(Pos.CENTER); networthBox.getChildren().addAll(networthLabelText, networthLabel); - this.getChildren().addAll(marketButton, portfolioButton, spacer, playerBox, moneyBox, networthBox); + this.getChildren().addAll(marketButton, portfolioButton, historyButton, spacer, playerBox, moneyBox, networthBox); } /** * Exposes the marketButton to the GameController. diff --git a/millions/target/classes/Millions-logo.png b/millions/target/classes/Millions-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..328546ffc4fb002d7e98513a4b18ceac838ea928 GIT binary patch literal 690 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV5;EC`zPI?B?a#uoV2`=@84WD^MW*-0g$M-};CC<(issRZ^z%y z&3mrBO!b9Vx}Rs@feAnM&perwbc3^mH*R@>W&Y+l^(EW}X`hyF$-KIfC->p5yp%J} zMV9%K**iD)F<$%g#Ju&z(){WUiKNB7D`#+2O$w4@6J3x{?BcsRULa^wTKEMq>xEOZ zN?19>SQx8*z`1Al%NexN=;3)4==009=Ueb1+C)A{?p{QrDLpl&F5Ab)%9-Uib*{mWmc zpO2qEzv%zty0@%-k;}K2;KZZi44q=Bm41-5#$K6}p zKXQLRe);RukAlnF+drqq&7W`fxBm`9;p}zQg{O4`EdCl#WJud5?wI<+cp*dDJ>?lQ ze`>WH{%K@9S@NHcPQy{5L+(otGQ)xpNPjrLmoZr1ed`_J2>~EsPgg&ebxsLQ00JJQ Axc~qF literal 0 HcmV?d00001 diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index e5cc627..4eae7af 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,14 +1,3 @@ -no/ntnu/gruppe53/TransactionArchive.class -no/ntnu/gruppe53/FileHandler.class -no/ntnu/gruppe53/Exchange.class -no/ntnu/gruppe53/Sale.class no/ntnu/gruppe53/controller/ViewController.class -no/ntnu/gruppe53/Transaction.class no/ntnu/gruppe53/controller/GameController.class -no/ntnu/gruppe53/Player.class -no/ntnu/gruppe53/views/PortfolioView.class no/ntnu/gruppe53/controller/StartViewController.class -no/ntnu/gruppe53/views/StartView.class -no/ntnu/gruppe53/views/PortfolioView$1.class -no/ntnu/gruppe53/Purchase.class -no/ntnu/gruppe53/views/MarketView.class diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index 47de60f..98c166d 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -1,20 +1,30 @@ /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/App.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Exchange.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/FileHandler.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Player.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Portfolio.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Purchase.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/PurchaseCalculator.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Sale.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/SaleCalculator.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Share.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Stock.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/Transaction.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/TransactionArchive.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/TransactionCalculator.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/GameSetupController.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/MarketView.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/PortfolioView.java -/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/views/StartView.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/Player.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/Share.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/service/FormatBigDecimal.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/StockLineChartView.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java From 7bbe4b79b6c5152f9e123e0c9ed1f032d8bc4837 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Sat, 23 May 2026 16:42:20 +0200 Subject: [PATCH 085/142] Added Subscription for PlayerName, Money and Net Worth for Navbar --- .../gruppe53/controller/GameController.java | 2 +- .../gruppe53/controller/ViewController.java | 2 +- .../java/no/ntnu/gruppe53/view/FooterBar.java | 31 ++--------- .../no/ntnu/gruppe53/view/NavigationBar.java | 54 ++++++++++++++++--- 4 files changed, 54 insertions(+), 35 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index b70392a..b8606df 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -68,7 +68,7 @@ public void startNewGame() { transactionArchiveView = new TransactionArchiveView(); transactionArchiveView.setPlayer(player); - sharedFooter.bindPlayer(player); + sharedNav.bindPlayer(player); vm.addView("portfolio", portfolioView); vm.addView("market", marketView); diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java index 3d7f25c..1bf8d20 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java @@ -28,7 +28,7 @@ public class ViewController { public ViewController(Stage stage) { this.stage = stage; this.mainLayout = new BorderPane(); - this.scene = new Scene(mainLayout, 800, 600); + this.scene = new Scene(mainLayout, 1200, 900); this.stage.setScene(scene); } diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java index ddcf004..8307751 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java @@ -24,9 +24,9 @@ * A {@link HBox} view representing the footer/bottom to be used in a {@link BorderPane} layout. */ public class FooterBar extends HBox { - private final Label netWorthLabel; + private final Button advanceWeekButton; - private Subscription netWorthSubscription; + /** * Constructs the layout of the view and its components. @@ -38,8 +38,7 @@ public FooterBar() { "-fx-border-color: #303539 transparent transparent transparent;" + "-fx-border-width: 5 0 0 0;"); - netWorthLabel = new Label("Net Worth: $0.00"); - netWorthLabel.setStyle("-fx-text-fill: #FFFFFF;"); + Region spacer = new Region(); HBox.setHgrow(spacer, Priority.ALWAYS); @@ -49,29 +48,9 @@ public FooterBar() { advanceWeekButton = new Button("Advance Week"); advanceWeekButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - this.getChildren().addAll(netWorthLabel, spacer, advanceWeekButton); + this.getChildren().addAll(spacer, advanceWeekButton); } - /** - * Binds a subscription/observer to a {@link Player}. - *

It specifically listens to the players money and net worth.

- * - * @param player the player object to be observed - */ - public void bindPlayer(Player player) { - if (netWorthSubscription != null) { - netWorthSubscription.unsubscribe(); - } - - if (player != null) { - netWorthSubscription = player.getNetWorthProperty().subscribe(newVal -> { - if (newVal != null) { - netWorthLabel.setText("Net Worth: $" + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { - netWorthLabel.setText("Net Worth: $0.00"); - } - }); - } - } + /** * Exposes the advanceWeekButton to {@link GameController} so it can diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java index 725a4d5..d6f7415 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java @@ -7,8 +7,9 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; +import javafx.util.Subscription; +import no.ntnu.gruppe53.model.Player; -import java.math.BigDecimal; import java.math.RoundingMode; @@ -31,15 +32,19 @@ public class NavigationBar extends HBox { private final Button marketButton; private final Button portfolioButton; private final Button historyButton; + private Subscription netWorthSubscription; + private Subscription moneySubscription; + private Player player; //Navigation Buttons //Dynamic Labels - public Label playerLabel; - public Label networthLabel; - public Label moneyLabel; + private Label playerLabel; + private Label netWorthLabel; + private Label moneyLabel; + public NavigationBar() { @@ -64,7 +69,7 @@ public NavigationBar() { HBox playerBox = new HBox(); Label playerLabelText = new Label("Player: "); - playerLabel = new Label(); + playerLabel = new Label("Player"); playerBox.setStyle("-fx-background-color: #ffe556;" + "-fx-text-fill: #303539;" + @@ -96,7 +101,7 @@ public NavigationBar() { HBox networthBox = new HBox(); Label networthLabelText = new Label("Net Worth: "); - networthLabel = new Label(); + netWorthLabel = new Label(); networthBox.setStyle("-fx-background-color: #ffe556;" + "-fx-text-fill: #303539;" + @@ -106,7 +111,7 @@ public NavigationBar() { ); networthBox.setPadding(new Insets(8, 14, 8, 14)); networthBox.setAlignment(Pos.CENTER); - networthBox.getChildren().addAll(networthLabelText, networthLabel); + networthBox.getChildren().addAll(networthLabelText, netWorthLabel); this.getChildren().addAll(marketButton, portfolioButton, historyButton, spacer, playerBox, moneyBox, networthBox); } @@ -136,4 +141,39 @@ public void onPortfolioButtonClick(Runnable action) { public void onHistoryButtonClick(Runnable action) { historyButton.setOnAction(e -> action.run()); } + + + + /** + * Binds a subscription/observer to a {@link Player}. + *

It specifically listens to the players money, net worth and name.

+ * + * @param player the player object to be observed + */ + public void bindPlayer(Player player) { + this.player = player; + + if (netWorthSubscription != null) { + netWorthSubscription.unsubscribe(); + } + + if (player != null) { + playerLabel.setText(player.getName()); + netWorthSubscription = player.getNetWorthProperty().subscribe(newVal -> { + if (newVal != null) { + netWorthLabel.setText("Net Worth: $" + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + netWorthLabel.setText("Net Worth: $0.00"); + } + }); + moneySubscription = player.getMoneyProperty().subscribe(newVal -> { + if (newVal != null) { + moneyLabel.setText("Money: $" + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + moneyLabel.setText("Money: $0.00"); + } + }); + + } + } } From 83a42bdbd0cd953c2c603a7d24f19adae00c80de Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Sat, 23 May 2026 16:54:04 +0200 Subject: [PATCH 086/142] Fixed portfolioview --- .../no/ntnu/gruppe53/view/MarketView.java | 31 +++++++++++++++++-- .../gruppe53/view/TransactionArchiveView.java | 9 +++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java index 8bb085d..5426f5d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java @@ -99,12 +99,16 @@ private VBox centerPane() { stockLineChartView.getChart().setMinHeight(0); stockLineChartView.getChart().setMaxHeight(Double.MAX_VALUE); + stockLineChartView.getChart().setMinWidth(0); + stockLineChartView.getChart().setMaxWidth(Double.MAX_VALUE); stocksListView = new ListView<>(); stocksListView.setMinHeight(0); stocksListView.setMaxHeight(Double.MAX_VALUE); + stocksListView.setMinWidth(0); + stocksListView.setMaxWidth(Double.MAX_VALUE); stocksListView.setStyle("-fx-background-color: transparent;"); stocksListView.setCellFactory(list -> new ListCell<>() { @@ -133,20 +137,41 @@ protected void updateItem(Stock stock, boolean empty) { } }); - HBox stockListBox = new HBox(stocksListView, stockLineChartView.getChart()); + HBox stockListBox = new HBox(stocksListView); stockListBox.setStyle("-fx-background-color: #ffe556;" + "-fx-text-fill: #303539;" + "-fx-background-radius: 8;" + "-fx-border-color: #303539;" + "-fx-border-radius: 8;" ); + + HBox stockChartBox = new HBox(stockLineChartView.getChart()); + stockChartBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + HBox stockBox = new HBox(stockListBox, stockChartBox); + stockBox.setSpacing(10); + stockListBox.setPadding(new Insets(4)); stockListBox.setMinHeight(0); stockListBox.setMaxHeight(Double.MAX_VALUE); + stockListBox.setMinWidth(0); + stockListBox.setMaxWidth(Double.MAX_VALUE); VBox.setVgrow(stocksListView, Priority.ALWAYS); + HBox.setHgrow(stocksListView, Priority.ALWAYS); VBox.setVgrow(stockLineChartView.getChart(), Priority.ALWAYS); - HBox.setHgrow(stockListBox, Priority.ALWAYS); + HBox.setHgrow(stockLineChartView.getChart(), Priority.ALWAYS); + VBox.setVgrow(stockListBox, Priority.ALWAYS); + HBox.setHgrow(stockListBox, Priority.ALWAYS); + VBox.setVgrow(stockChartBox, Priority.ALWAYS); + HBox.setHgrow(stockChartBox, Priority.ALWAYS); + VBox.setVgrow(stockBox, Priority.ALWAYS); + HBox.setHgrow(stockBox, Priority.ALWAYS); HBox selectedStockBox = new HBox(); @@ -204,7 +229,7 @@ protected void updateItem(Stock stock, boolean empty) { vBox.getChildren().addAll(titleBox, - stockListBox, + stockBox, purchaseBox); return vBox; diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java index 81e501a..08ee07e 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java @@ -14,6 +14,12 @@ import no.ntnu.gruppe53.model.Sale; import no.ntnu.gruppe53.service.FormatBigDecimal; +/* +Yellow Color, Hex: #ffe556 RGB(255, 229, 86) +Blue Color, Hex: #00bcf0 RGB(0, 188, 240) +Black Color, Hex: #303539 RGB(48, 53, 57) + */ + /** * Represents a view of all the {@link Purchase} and {@link Sale} * {@link Transaction}s a {@link Player} has committed in a game. @@ -44,9 +50,10 @@ public TransactionArchiveView() { private VBox centerPane() { VBox vBox = new VBox(10); vBox.setPadding(new Insets(15, 12, 15, 12)); + vBox.setStyle("-fx-background-color: #00bcf0; "); titleLabel = new Label("Transaction History"); - titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;"); + titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: #303539;"); historyListView = new ListView<>(); historyListView.setPrefHeight(400); From d389a93015d7846c4b5346f5da037d63475382ae Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Sat, 23 May 2026 17:03:35 +0200 Subject: [PATCH 087/142] Added styling to History --- .../gruppe53/controller/GameController.java | 2 ++ .../no/ntnu/gruppe53/view/PortfolioView.java | 9 +++++ .../gruppe53/view/TransactionArchiveView.java | 33 +++++++++++++++++-- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index b8606df..aadc8ea 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -147,6 +147,8 @@ public void initialize(NavigationBar nav, FooterBar footer) { }); portfolioView.onSellButton(this::handleSell); + + } /** diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java index 82373b0..b1c87cb 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java @@ -22,6 +22,7 @@ import javafx.util.Subscription; import no.ntnu.gruppe53.model.Player; import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; import no.ntnu.gruppe53.service.FormatBigDecimal; /* @@ -205,6 +206,14 @@ public void onSellButton(Runnable action) { sellButton.setOnAction(e -> action.run()); } + /** + * Sets the action to be run on selecting a share in portfolioListView. + * @param action the action to run when selecting a share + */ + public void onShareSelection(Consumer action) { + portfolioListView.getSelectionModel().selectedItemProperty().subscribe(action); + } + /** * Gets the selected share in the portfolioListView. * diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java index 08ee07e..630adc8 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java @@ -1,10 +1,12 @@ package no.ntnu.gruppe53.view; import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import java.math.BigDecimal; import java.math.RoundingMode; @@ -55,8 +57,20 @@ private VBox centerPane() { titleLabel = new Label("Transaction History"); titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: #303539;"); + VBox titleBox = new VBox(titleLabel); + titleBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + titleBox.setAlignment(Pos.CENTER); + titleBox.setPadding(new Insets(4)); + titleBox.setMaxWidth(300); + historyListView = new ListView<>(); - historyListView.setPrefHeight(400); + historyListView.setMinHeight(0); + historyListView.setMaxHeight(Double.MAX_VALUE); historyListView.setCellFactory(list -> new ListCell<>() { @Override @@ -91,7 +105,22 @@ protected void updateItem(Transaction item, boolean empty) { } }); - vBox.getChildren().addAll(titleLabel, historyListView); + VBox historyListBox = new VBox(historyListView); + historyListBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + historyListBox.setPadding(new Insets(4)); + historyListBox.setMinHeight(0); + historyListBox.setMaxHeight(Double.MAX_VALUE); + + VBox.setVgrow(historyListView, Priority.ALWAYS); + VBox.setVgrow(historyListBox, Priority.ALWAYS); + + vBox.getChildren().addAll(titleBox, historyListBox); return vBox; } From 1f781c8937b1998ab349492bb242b514deec7d40 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Sat, 23 May 2026 17:08:54 +0200 Subject: [PATCH 088/142] added share selection label in portfolioview --- .../no/ntnu/gruppe53/controller/GameController.java | 9 +++++++++ .../main/java/no/ntnu/gruppe53/view/PortfolioView.java | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index aadc8ea..8c9b59e 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -146,6 +146,15 @@ public void initialize(NavigationBar nav, FooterBar footer) { //implement the quantity select which updates gross, commision and total }); + portfolioView.onShareSelection(share -> { + if (share != null) { + portfolioView.setSelectedShareLabel(share.getStock().getSymbol() + " - " + share.getStock().getCompany()); + + } else { + portfolioView.setSelectedShareLabel(""); + } + }); + portfolioView.onSellButton(this::handleSell); diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java index b1c87cb..5fb1b3a 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java @@ -246,6 +246,16 @@ public void setPlayer(Player player) { } } + /** + * Sets the share name of the selected share in the portfolioListView. + * + * @param selectedShareString the share name of the selected share + */ + public void setSelectedShareLabel(String selectedShareString) { + selectedShare.setText(selectedShareString); + } + + /** * Formats the text representation of the share in the portfolioListView * and applies a color to it based on its current sales price compared From 7513bad88549e459e0e63de837313fb1eb25300f Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Sat, 23 May 2026 18:06:14 +0200 Subject: [PATCH 089/142] Added a week counter to footer --- .../gruppe53/controller/GameController.java | 1 + .../java/no/ntnu/gruppe53/model/Exchange.java | 13 ++++++++ .../java/no/ntnu/gruppe53/view/FooterBar.java | 33 ++++++++++++++++++- .../no/ntnu/gruppe53/view/MarketView.java | 2 +- 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index 8c9b59e..dad6f25 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -69,6 +69,7 @@ public void startNewGame() { transactionArchiveView.setPlayer(player); sharedNav.bindPlayer(player); + sharedFooter.setExchange(exchange); vm.addView("portfolio", portfolioView); vm.addView("market", marketView); diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java b/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java index 8a5684a..cb106b6 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java @@ -1,5 +1,7 @@ package no.ntnu.gruppe53.model; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -19,6 +21,7 @@ public class Exchange { private final Map stockMap; private final Random random; private final ObservableList stocks = FXCollections.observableArrayList(); + private final ObjectProperty currentWeekProperty = new SimpleObjectProperty<>(); /** * Sets the name of the exchange and the list of stocks to populate it. @@ -40,6 +43,7 @@ public Exchange(String name, List stocks) { this.name = name; this.week = 1; + this.currentWeekProperty.set(week); this.stockMap = new HashMap<>(); this.random = new Random(); @@ -173,6 +177,7 @@ public Transaction sell(Share share, Player player) { */ public void advance() { this.week++; + this.currentWeekProperty.set(week); double negativePriceChange = -0.05; double positivePriceChange = 0.1; @@ -230,4 +235,12 @@ public List getGainers(int entries) { public List getLosers(int entries) { return getTopStocks(entries, Comparator.comparing(Stock::getLatestPriceChange)); } + + /** + * Returns an observable of the current week + * @return an observable of the current week + */ + public ObjectProperty getCurrentWeekProperty() { + return currentWeekProperty; + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java index 8307751..efea8a3 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java @@ -2,6 +2,7 @@ import java.math.RoundingMode; import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.BorderPane; @@ -26,7 +27,10 @@ public class FooterBar extends HBox { private final Button advanceWeekButton; + private Label currentWeekLabel; + private Subscription currentWeekSubscription; + private Exchange exchange; /** * Constructs the layout of the view and its components. @@ -40,17 +44,44 @@ public FooterBar() { + Region spacer = new Region(); HBox.setHgrow(spacer, Priority.ALWAYS); + HBox currentWeekBox = new HBox(); + currentWeekBox.setSpacing(10); + + currentWeekBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + currentWeekBox.setPadding(new Insets(8, 14, 8, 14)); + currentWeekBox.setAlignment(Pos.CENTER); + + currentWeekLabel = new Label(); + currentWeekBox.getChildren().add(currentWeekLabel); + advanceWeekButton = new Button("Advance Week"); advanceWeekButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - this.getChildren().addAll(spacer, advanceWeekButton); + this.getChildren().addAll(spacer, currentWeekBox, advanceWeekButton); } + public void setExchange(Exchange exchange) { + this.exchange = exchange; + currentWeekSubscription = exchange.getCurrentWeekProperty().subscribe(newVal -> { + if (exchange != null) { + currentWeekLabel.setText("Current Week: " + newVal); + } else { + currentWeekLabel.setText("Current Week: None"); + } + }); + + } /** * Exposes the advanceWeekButton to {@link GameController} so it can diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java index 5426f5d..8a2cf80 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java @@ -175,7 +175,7 @@ protected void updateItem(Stock stock, boolean empty) { HBox selectedStockBox = new HBox(); - Label selectedStockLabel = new Label("Selected Stock:"); + Label selectedStockLabel = new Label("Selected Stock: "); selectedStock = new Label(""); selectedStockBox.getChildren().addAll(selectedStockLabel, selectedStock); From 8b16010bf57bd6bfd389fbbe3cab66a6c66bbc12 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Sat, 23 May 2026 18:10:46 +0200 Subject: [PATCH 090/142] Added test to exchange for the currentweekproperty --- .../java/no/ntnu/gruppe53/model/ExchangeTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/millions/src/test/java/no/ntnu/gruppe53/model/ExchangeTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/ExchangeTest.java index 128badd..6e28192 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/model/ExchangeTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/ExchangeTest.java @@ -1,6 +1,7 @@ package no.ntnu.gruppe53.model; +import javafx.beans.property.ObjectProperty; import javafx.collections.ObservableList; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -343,4 +344,14 @@ void sellWithNullShareShouldThrowIllegalArgumentException() { "Should throw if share is null."); } + @Test + void getCurrentWeekPropertyShouldReturnObservable() { + + + assertInstanceOf(ObjectProperty.class, exchange.getCurrentWeekProperty(), + "Should be Observable."); + assertEquals(exchange.getWeek(), exchange.getCurrentWeekProperty().getValue(), + "Should have correct week value."); + } + } From c1dd2786ee368486c2a31b71bf7a175bd3d2ef07 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Sat, 23 May 2026 19:46:34 +0200 Subject: [PATCH 091/142] Set up proper display of purchasecalculator values in the marketView --- .../gruppe53/controller/GameController.java | 18 +++++++- .../gruppe53/model/PurchaseCalculator.java | 44 ++++++++++++++++++- .../no/ntnu/gruppe53/view/MarketView.java | 36 ++++++++++++--- 3 files changed, 91 insertions(+), 7 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index dad6f25..1caaaf8 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -132,6 +132,8 @@ public void initialize(NavigationBar nav, FooterBar footer) { var series = StockLineChartService.buildSeries(currentStock, exchange); marketView.updateChart(series, currentStock.getCompany()); }); + marketView.setPurchaseCalculator(calculatePurchase(marketView.getSelectedStock(), + marketView.getQuantityPurchase())); } else { marketView.setSelectedStockLabel(""); marketView.clearChart(); @@ -144,7 +146,10 @@ public void initialize(NavigationBar nav, FooterBar footer) { }); marketView.onQuantitySelect(() -> { - //implement the quantity select which updates gross, commision and total + if (marketView.getSelectedStock() != null) { + marketView.setPurchaseCalculator(calculatePurchase(marketView.getSelectedStock(), + marketView.getQuantityPurchase())); + } }); portfolioView.onShareSelection(share -> { @@ -197,6 +202,17 @@ private void sellShare(Share selectedShare) { } } + private PurchaseCalculator calculatePurchase(Stock stock, int quantity) { + stock = marketView.getSelectedStock(); + quantity = marketView.getQuantityPurchase(); + + Share share = new Share(stock, BigDecimal.valueOf(quantity), stock.getSalesPrice()); + + + + return purchaseCalculator = new PurchaseCalculator(share); + } + /** * Creates a default error alert to be used for notifying the player of errors. * @param title title of the error alert diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java index 37fe66a..a98e809 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java @@ -1,5 +1,8 @@ package no.ntnu.gruppe53.model; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + import java.math.BigDecimal; /** @@ -10,9 +13,16 @@ public class PurchaseCalculator implements TransactionCalculator{ private final BigDecimal purchasePrice; private final BigDecimal quantity; + //Objectproperties + private final ObjectProperty grossProperty = new SimpleObjectProperty<>(); + private final ObjectProperty commissionProperty = new SimpleObjectProperty<>(); + private final ObjectProperty totalProperty = new SimpleObjectProperty<>(); + + /** * Sets the purchase price and quantity based on data from the given share - * + *

Also sets up the ObjectProperties of the different calculations such + * that Subscriptions can listen to the calculations

* @param share the share to calculate purchase costs for */ public PurchaseCalculator(Share share) { @@ -20,6 +30,10 @@ public PurchaseCalculator(Share share) { this.purchasePrice = share.getPurchasePrice(); this.quantity = share.getQuantity(); + this.grossProperty.set(calculateGross()); + this.commissionProperty.set(calculateCommission()); + this.totalProperty.set(calculateTotal()); + } /** @@ -29,6 +43,7 @@ public PurchaseCalculator(Share share) { */ @Override public BigDecimal calculateGross(){ + return this.quantity.multiply(this.purchasePrice); } @@ -42,6 +57,8 @@ public BigDecimal calculateGross(){ public BigDecimal calculateCommission() { BigDecimal commission = new BigDecimal("0.005"); + + return calculateGross().multiply(commission); } @@ -64,9 +81,34 @@ public BigDecimal calculateTax() { @Override public BigDecimal calculateTotal() { + return calculateGross().add(calculateCommission()).add(calculateTax()); } + /** + * Returns an observable of the calculated gross + * @return an observable of the calculated gross + */ + public ObjectProperty getGrossProperty() { + return grossProperty; + } + + /** + * Returns an observable of the calculated commission + * @return an observable of the calculated commission + */ + public ObjectProperty getCommissionProperty() { + return commissionProperty; + } + + /** + * Returns an observable of the calculated total + * @return an observable of the calculated total + */ + public ObjectProperty getTotalProperty() { + return totalProperty; + } + diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java index 8a2cf80..dcdcf0c 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53.view; +import java.math.RoundingMode; import java.util.function.Consumer; import javafx.collections.transformation.SortedList; @@ -13,10 +14,7 @@ import javafx.scene.layout.VBox; import javafx.scene.chart.LineChart; import javafx.util.Subscription; -import no.ntnu.gruppe53.model.Exchange; -import no.ntnu.gruppe53.model.Player; -import no.ntnu.gruppe53.model.Stock; -import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.*; import no.ntnu.gruppe53.service.FormatBigDecimal; /* @@ -51,11 +49,14 @@ public class MarketView extends BorderPane { //Input private Spinner quantitySpinner; private Exchange exchange; - private Player player; + private PurchaseCalculator purchaseCalculator; private final StockLineChartView stockLineChartView; private Subscription playerSubscription; private Subscription priceSubscription; + private Subscription grossSubscription; + private Subscription commissionSubscription; + private Subscription totalSubscription; /** * Initializes the StockLineChartView graph and runs the view @@ -314,6 +315,31 @@ public void setExchange(Exchange exchange) { } } + public void setPurchaseCalculator(PurchaseCalculator purchaseCalculator) { + this.purchaseCalculator = purchaseCalculator; + grossSubscription = purchaseCalculator.getGrossProperty().subscribe(newVal -> { + if (newVal != null) { + gross.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + gross.setText(""); + } + }); + commissionSubscription = purchaseCalculator.getCommissionProperty().subscribe(newVal -> { + if (newVal != null) { + commission.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + commission.setText(""); + } + }); + totalSubscription = purchaseCalculator.getTotalProperty().subscribe(newVal -> { + if (newVal != null) { + total.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + total.setText(""); + } + }); + } + /** * Returns the selected stock in the stocksListView. * From b7cc682ea7f18359b58b87292692529df6678369 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Sat, 23 May 2026 19:50:08 +0200 Subject: [PATCH 092/142] Added tests for the Objectproperties added to Purchase Calculators --- .../model/PurchaseCalculatorTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/millions/src/test/java/no/ntnu/gruppe53/model/PurchaseCalculatorTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/PurchaseCalculatorTest.java index 4eb3102..4935a80 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/model/PurchaseCalculatorTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/PurchaseCalculatorTest.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53.model; +import javafx.beans.property.ObjectProperty; import no.ntnu.gruppe53.model.PurchaseCalculator; import no.ntnu.gruppe53.model.Share; import no.ntnu.gruppe53.model.Stock; @@ -9,6 +10,7 @@ import java.math.BigDecimal; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; //The need to setScale(2) at all the BigDecimals is to ensure that assertEquals works @@ -55,6 +57,36 @@ void testCalculateTotal() { } + @Test + void getGrossPropertyShouldReturnObservable() { + + + assertInstanceOf(ObjectProperty.class, purchaseCalculator.getGrossProperty(), + "Should be Observable."); + assertEquals(purchaseCalculator.calculateGross(), purchaseCalculator.getGrossProperty().getValue(), + "Should have the correct gross calculation."); + } + + @Test + void getCommissionPropertyShouldReturnObservable() { + + + assertInstanceOf(ObjectProperty.class, purchaseCalculator.getCommissionProperty(), + "Should be Observable."); + assertEquals(purchaseCalculator.calculateCommission(), purchaseCalculator.getCommissionProperty().getValue(), + "Should have the correct commission calculation."); + } + + @Test + void getTotalPropertyShouldReturnObservable() { + + + assertInstanceOf(ObjectProperty.class, purchaseCalculator.getTotalProperty(), + "Should be Observable."); + assertEquals(purchaseCalculator.calculateTotal(), purchaseCalculator.getTotalProperty().getValue(), + "Should have the correct total calculation."); + } + } From fec97744494deeea418c7e44852c247c7cd7d160 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Sat, 23 May 2026 20:11:10 +0200 Subject: [PATCH 093/142] Added the same to PortfolioView --- .../gruppe53/controller/GameController.java | 24 ++++++++- .../ntnu/gruppe53/model/SaleCalculator.java | 46 +++++++++++++++++ .../no/ntnu/gruppe53/view/MarketView.java | 7 +++ .../no/ntnu/gruppe53/view/PortfolioView.java | 49 ++++++++++++++++++- .../gruppe53/model/SaleCalculatorTest.java | 40 +++++++++++++++ 5 files changed, 164 insertions(+), 2 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index 1caaaf8..6bc40d8 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -155,7 +155,7 @@ public void initialize(NavigationBar nav, FooterBar footer) { portfolioView.onShareSelection(share -> { if (share != null) { portfolioView.setSelectedShareLabel(share.getStock().getSymbol() + " - " + share.getStock().getCompany()); - + portfolioView.setSaleCalculator(calculateSale(portfolioView.getSelectedShare())); } else { portfolioView.setSelectedShareLabel(""); } @@ -202,6 +202,14 @@ private void sellShare(Share selectedShare) { } } + + /** + * Creates a {@link PurchaseCalculator} that can be used for the + * for the setPurchaseCalculator() function in the {@link MarketView}. + * @param stock the currently selected stock + * @param quantity the currently selected quantity + * @return {@link PurchaseCalculator} + */ private PurchaseCalculator calculatePurchase(Stock stock, int quantity) { stock = marketView.getSelectedStock(); quantity = marketView.getQuantityPurchase(); @@ -213,6 +221,20 @@ private PurchaseCalculator calculatePurchase(Stock stock, int quantity) { return purchaseCalculator = new PurchaseCalculator(share); } + /** + * Creates a {@link SaleCalculator} that can be used for the + * for the setSaleCalculator() function in the {@link PortfolioView}. + * @param share + * @return {@link SaleCalculator} + */ + private SaleCalculator calculateSale(Share share) { + + share = portfolioView.getSelectedShare(); + + return saleCalculator = new SaleCalculator(share); + + } + /** * Creates a default error alert to be used for notifying the player of errors. * @param title title of the error alert diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java index 872aa07..e96015a 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java @@ -1,5 +1,8 @@ package no.ntnu.gruppe53.model; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + import java.math.BigDecimal; import java.math.RoundingMode; @@ -13,6 +16,12 @@ public class SaleCalculator implements TransactionCalculator { private final BigDecimal salesPrice; private final BigDecimal quantity; + //Objectproperties + private final ObjectProperty grossProperty = new SimpleObjectProperty<>(); + private final ObjectProperty commissionProperty = new SimpleObjectProperty<>(); + private final ObjectProperty taxProperty = new SimpleObjectProperty<>(); + private final ObjectProperty totalProperty = new SimpleObjectProperty<>(); + /** * Sets the quantity, purchase and sales price based on the given share. * @@ -22,6 +31,11 @@ public SaleCalculator(Share share) { this.purchasePrice = share.getPurchasePrice(); this.quantity = share.getQuantity(); this.salesPrice = share.getStock().getSalesPrice(); + + this.grossProperty.set(calculateGross()); + this.commissionProperty.set(calculateCommission()); + this.taxProperty.set(calculateTax()); + this.totalProperty.set(calculateTotal()); } /** @@ -89,4 +103,36 @@ public BigDecimal calculateTotal() { .subtract(calculateTax()) .setScale(2, RoundingMode.HALF_UP); } + + /** + * Returns an observable of the calculated gross + * @return an observable of the calculated gross + */ + public ObjectProperty getGrossProperty() { + return grossProperty; + } + + /** + * Returns an observable of the calculated commission + * @return an observable of the calculated commission + */ + public ObjectProperty getCommissionProperty() { + return commissionProperty; + } + + /** + * Returns an observable of the calculated tax + * @return an observable of the calculated tax + */ + public ObjectProperty getTaxProperty() { + return taxProperty; + } + + /** + * Returns an observable of the calculated total + * @return an observable of the calculated total + */ + public ObjectProperty getTotalProperty() { + return totalProperty; + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java index dcdcf0c..380ef7b 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java @@ -315,6 +315,13 @@ public void setExchange(Exchange exchange) { } } + /** + * Sets the PurchaseCalculator to use for the view. + *

Uses a {@link Subscription} to observe the calculations made by the current + * Stock and Quantity selected.

+ * + * @param purchaseCalculator the purchaseCalculator to be used for the view + */ public void setPurchaseCalculator(PurchaseCalculator purchaseCalculator) { this.purchaseCalculator = purchaseCalculator; grossSubscription = purchaseCalculator.getGrossProperty().subscribe(newVal -> { diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java index 5fb1b3a..7388c16 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java @@ -21,6 +21,7 @@ import javafx.scene.text.TextFlow; import javafx.util.Subscription; import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.model.SaleCalculator; import no.ntnu.gruppe53.model.Share; import no.ntnu.gruppe53.model.Stock; import no.ntnu.gruppe53.service.FormatBigDecimal; @@ -61,6 +62,12 @@ public class PortfolioView extends BorderPane { private ListView portfolioListView; private Player player; + private SaleCalculator saleCalculator; + + private Subscription grossSubscription; + private Subscription commissionSubscription; + private Subscription taxSubscription; + private Subscription totalSubscription; /** * Builds the view components and places them in the center pane. @@ -255,6 +262,45 @@ public void setSelectedShareLabel(String selectedShareString) { selectedShare.setText(selectedShareString); } + /** + * Sets the SaleCalculator to use for the view. + *

Uses a {@link Subscription} to observe the calculations made by the current + * Share selected.

+ * + * @param saleCalculator the SaleCalculator to be used for the view + */ + public void setSaleCalculator(SaleCalculator saleCalculator) { + this.saleCalculator = saleCalculator; + grossSubscription = saleCalculator.getGrossProperty().subscribe(newVal -> { + if (newVal != null) { + gross.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + gross.setText(""); + } + }); + commissionSubscription = saleCalculator.getCommissionProperty().subscribe(newVal -> { + if (newVal != null) { + commission.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + commission.setText(""); + } + }); + taxSubscription = saleCalculator.getTaxProperty().subscribe(newVal -> { + if (newVal != null) { + tax.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN).abs()); + } else { + tax.setText(""); + } + }); + totalSubscription = saleCalculator.getTotalProperty().subscribe(newVal -> { + if (newVal != null) { + total.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + total.setText(""); + } + }); + } + /** * Formats the text representation of the share in the portfolioListView @@ -262,7 +308,8 @@ public void setSelectedShareLabel(String selectedShareString) { * to the buy price of the share. * * @param share the share to be formatted - * @param currentPrice the current price of the share + * @param currentPrice the cusort the stocks in the exchange alphabetically + * on their symbol/tickerrrent price of the share * @return a {@link TextFlow} representation of the share */ private TextFlow createColoredShareText(Share share, BigDecimal currentPrice) { diff --git a/millions/src/test/java/no/ntnu/gruppe53/model/SaleCalculatorTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/SaleCalculatorTest.java index 0848f1e..fc0be79 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/model/SaleCalculatorTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/model/SaleCalculatorTest.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53.model; +import javafx.beans.property.ObjectProperty; import no.ntnu.gruppe53.model.SaleCalculator; import no.ntnu.gruppe53.model.Share; import no.ntnu.gruppe53.model.Stock; @@ -9,6 +10,7 @@ import java.math.BigDecimal; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; //The need to setScale(2) at all the BigDecimals is to ensure that assertEquals works @@ -67,4 +69,42 @@ void testCalculateTotal() { } + @Test + void getGrossPropertyShouldReturnObservable() { + + + assertInstanceOf(ObjectProperty.class, saleCalculator.getGrossProperty(), + "Should be Observable."); + assertEquals(saleCalculator.calculateGross(), saleCalculator.getGrossProperty().getValue(), + "Should have the correct gross calculation."); + } + + @Test + void getCommissionPropertyShouldReturnObservable() { + + + assertInstanceOf(ObjectProperty.class, saleCalculator.getCommissionProperty(), + "Should be Observable."); + assertEquals(saleCalculator.calculateCommission(), saleCalculator.getCommissionProperty().getValue(), + "Should have the correct commission calculation."); + } + + @Test + void getTaxPropertyShouldReturnObservable() { + assertInstanceOf(ObjectProperty.class, saleCalculator.getTaxProperty(), + "Should be Observable."); + assertEquals(saleCalculator.calculateTax(), saleCalculator.getTaxProperty().getValue(), + "Should have the correct tax calculation."); + } + + @Test + void getTotalPropertyShouldReturnObservable() { + + + assertInstanceOf(ObjectProperty.class, saleCalculator.getTotalProperty(), + "Should be Observable."); + assertEquals(saleCalculator.calculateTotal(), saleCalculator.getTotalProperty().getValue(), + "Should have the correct total calculation."); + } + } From c2cdc27c79ae70c932ec1868ce3fe1fc545ab437 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Sat, 23 May 2026 20:28:09 +0200 Subject: [PATCH 094/142] idk --- .../java/no/ntnu/gruppe53/view/PlayerNameChooserView.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java index b844df9..669c5f2 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java @@ -9,6 +9,12 @@ import java.util.Optional; +/* +Yellow Color, Hex: #ffe556 RGB(255, 229, 86) +Blue Color, Hex: #00bcf0 RGB(0, 188, 240) +Black Color, Hex: #303539 RGB(48, 53, 57) + */ + /** * Represents a prompt for the {@link Player}'s name */ From 860eee197b08f5a8e668b088035a5caaef424961 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 11:15:01 +0200 Subject: [PATCH 095/142] Updated FileHandlerTest Fixed an error where it tried to look for FileHandler in view instead of service package. --- .../src/test/java/no/ntnu/gruppe53/service/FileHandlerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/millions/src/test/java/no/ntnu/gruppe53/service/FileHandlerTest.java b/millions/src/test/java/no/ntnu/gruppe53/service/FileHandlerTest.java index 55e6133..b0a09f1 100644 --- a/millions/src/test/java/no/ntnu/gruppe53/service/FileHandlerTest.java +++ b/millions/src/test/java/no/ntnu/gruppe53/service/FileHandlerTest.java @@ -1,7 +1,6 @@ package no.ntnu.gruppe53.service; import no.ntnu.gruppe53.model.Stock; -import no.ntnu.gruppe53.view.FileHandler; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; From 357fd60ceb1aa6d0c5a3402e4175f9c1e2939d53 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 11:15:29 +0200 Subject: [PATCH 096/142] Added TransactionFactory Abstract class for creating transactions. --- .../gruppe53/model/TransactionFactory.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/model/TransactionFactory.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionFactory.java b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionFactory.java new file mode 100644 index 0000000..9ddb766 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionFactory.java @@ -0,0 +1,33 @@ +package no.ntnu.gruppe53.model; + +/** + * Factory for creating a {@link Transaction} for a {@link Share} in an {@link Exchange}. + *

Subclasses implement the specific type of transaction to be used.

+ */ +public abstract class TransactionFactory { + + /** + * Creates a transaction. + * + * @param share the share involved + * @param week the transaction week + * @return a transaction + */ + public Transaction create(Share share, int week) { + + if (share == null) { + throw new IllegalArgumentException("Share cannot be null."); + } + + if (week <= 0) { + throw new IllegalArgumentException("Week must be positive whole number."); + } + + return createTransaction(share, week); + } + + protected abstract Transaction createTransaction( + Share share, int week + ); + +} From b27f41987fdb34821eda33b1f9406d17a62d75b4 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 11:16:02 +0200 Subject: [PATCH 097/142] Added PurchaseFactory Creates Purchase transactions. --- .../ntnu/gruppe53/model/PurchaseFactory.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/model/PurchaseFactory.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseFactory.java b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseFactory.java new file mode 100644 index 0000000..ffce90a --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseFactory.java @@ -0,0 +1,19 @@ +package no.ntnu.gruppe53.model; + +/** + * Creates a {@link Purchase} {@link Transaction} of a {@link Share}. + */ +public class PurchaseFactory extends TransactionFactory{ + + /** + * Returns the purchase transaction for the given share and week. + * + * @param share the share to be purchased + * @param week the week of purchase + * @return the purchase transaction + */ + @Override + protected Transaction createTransaction(Share share, int week) { + return new Purchase(share, week); + } +} From c033ef15abbeebba2c30589bc678ca177a60dbbc Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 11:18:10 +0200 Subject: [PATCH 098/142] Added SaleFactory Creates Sale transactions. --- .../no/ntnu/gruppe53/model/SaleFactory.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/model/SaleFactory.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/SaleFactory.java b/millions/src/main/java/no/ntnu/gruppe53/model/SaleFactory.java new file mode 100644 index 0000000..2340e5c --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/model/SaleFactory.java @@ -0,0 +1,19 @@ +package no.ntnu.gruppe53.model; + +/** + * Creates a {@link Sale} {@link Transaction} of a {@link Share}. + */ +public class SaleFactory extends TransactionFactory { + + /** + * Returns the sale transaction of the given share and week. + * + * @param share the share to be sold + * @param week the week of the sale + * @return the sale transaction + */ + @Override + protected Transaction createTransaction(Share share, int week) { + return new Sale(share, week); + } +} From a66d146a7b301c957f146fb04f85f4a2770ff673 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 11:18:38 +0200 Subject: [PATCH 099/142] Added PurchaseFactoryTest Test class for PurchaseFactory. --- .../gruppe53/model/PurchaseFactoryTest.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 millions/src/test/java/no/ntnu/gruppe53/model/PurchaseFactoryTest.java diff --git a/millions/src/test/java/no/ntnu/gruppe53/model/PurchaseFactoryTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/PurchaseFactoryTest.java new file mode 100644 index 0000000..cfab01a --- /dev/null +++ b/millions/src/test/java/no/ntnu/gruppe53/model/PurchaseFactoryTest.java @@ -0,0 +1,64 @@ +package no.ntnu.gruppe53.model; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static org.junit.jupiter.api.Assertions.*; + +class PurchaseFactoryTest { + private int week; + private Share share; + private TransactionFactory purchaseFactory; + private Transaction transaction; + + @BeforeEach + void setUp() { + String symbol = "AAPL"; + String company = "Apple Inc."; + var price = new BigDecimal("100"); + + var testStock = new Stock(symbol,company, price); + var quantity = new BigDecimal("10"); + var purchasePrice = testStock.getSalesPrice(); + + this.week = 1; + this.share = new Share(testStock, quantity, purchasePrice); + this.purchaseFactory = new PurchaseFactory(); + this.transaction = purchaseFactory.create(share, week); + } + + @Test + void createShouldMakeACorrectTransactionType() { + assertInstanceOf(Transaction.class, transaction, + "The returned value should be a transaction."); + assertInstanceOf(Purchase.class, transaction, + "Should be a purchase transaction."); + assertFalse(transaction instanceof Sale, + "Should not be a sale transaction."); + } + + @Test + void shouldHaveCorrectShareAndWeek() { + assertEquals(share, transaction.getShare(), + "Should contain the correct share."); + assertEquals(week, transaction.getWeek(), + "Should contain the correct week."); + } + + @Test + void shouldThrowIfShareIsNull() { + assertThrows(IllegalArgumentException.class, () -> purchaseFactory.create(null, week), + "Share should not be allowed to be null."); + } + + @Test + void shouldThrowIfWeekIsZeroOrBelow() { + assertThrows(IllegalArgumentException.class, () -> purchaseFactory.create(share, 0), + "Week should not be allowed to be zero"); + assertThrows(IllegalArgumentException.class, () -> purchaseFactory.create(share, -1), + "Week should not be allowed to be negative"); + } + +} \ No newline at end of file From 48f8a56fa7836f9f4ae0c2aad9c9714ababde949 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 11:19:01 +0200 Subject: [PATCH 100/142] Added SaleFactoryTest Test class for SaleFactory. --- .../ntnu/gruppe53/model/SaleFactoryTest.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 millions/src/test/java/no/ntnu/gruppe53/model/SaleFactoryTest.java diff --git a/millions/src/test/java/no/ntnu/gruppe53/model/SaleFactoryTest.java b/millions/src/test/java/no/ntnu/gruppe53/model/SaleFactoryTest.java new file mode 100644 index 0000000..3bfed18 --- /dev/null +++ b/millions/src/test/java/no/ntnu/gruppe53/model/SaleFactoryTest.java @@ -0,0 +1,64 @@ +package no.ntnu.gruppe53.model; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static org.junit.jupiter.api.Assertions.*; + +class SaleFactoryTest { + private int week; + private Share share; + private TransactionFactory saleFactory; + private Transaction transaction; + + @BeforeEach + void setUp() { + String symbol = "AAPL"; + String company = "Apple Inc."; + var price = new BigDecimal("100"); + + var testStock = new Stock(symbol,company, price); + var quantity = new BigDecimal("10"); + var purchasePrice = testStock.getSalesPrice(); + + this.week = 1; + this.share = new Share(testStock, quantity, purchasePrice); + this.saleFactory = new SaleFactory(); + this.transaction = saleFactory.create(share, week); + } + + @Test + void createShouldMakeACorrectTransactionType() { + assertInstanceOf(Transaction.class, transaction, + "The returned value should be a transaction."); + assertInstanceOf(Sale.class, transaction, + "Should be a sale transaction."); + assertFalse(transaction instanceof Purchase, + "Should not be a purchase transaction."); + } + + @Test + void shouldHaveCorrectShareAndWeek() { + assertEquals(share, transaction.getShare(), + "Should contain the correct share."); + assertEquals(week, transaction.getWeek(), + "Should contain the correct week."); + } + + @Test + void shouldThrowIfShareIsNull() { + assertThrows(IllegalArgumentException.class, () -> saleFactory.create(null, week), + "Share should not be allowed to be null."); + } + + @Test + void shouldThrowIfWeekIsZeroOrBelow() { + assertThrows(IllegalArgumentException.class, () -> saleFactory.create(share, 0), + "Week should not be allowed to be zero"); + assertThrows(IllegalArgumentException.class, () -> saleFactory.create(share, -1), + "Week should not be allowed to be negative"); + } + +} \ No newline at end of file From 6eb274fea72763f6c4e826c97840e1ccff9b024f Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 11:19:27 +0200 Subject: [PATCH 101/142] Updated Exchange Exchange now uses factories for making transactions. --- .../main/java/no/ntnu/gruppe53/model/Exchange.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java b/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java index cb106b6..f20919c 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java @@ -22,10 +22,13 @@ public class Exchange { private final Random random; private final ObservableList stocks = FXCollections.observableArrayList(); private final ObjectProperty currentWeekProperty = new SimpleObjectProperty<>(); + private final TransactionFactory purchaseFactory; + private final TransactionFactory saleFactory; /** * Sets the name of the exchange and the list of stocks to populate it. *

Validates that the values are proper before creating the exchange.

+ *

Initializes factories for creating a {@link Transaction}.

* * @param name the name of the exchange * @param stocks the list of stocks to be tradeable on the exchange @@ -59,6 +62,9 @@ public Exchange(String name, List stocks) { stockMap.put(stock.getSymbol(), stock); this.stocks.add(stock); } + + this.purchaseFactory = new PurchaseFactory(); + this.saleFactory = new SaleFactory(); } /** @@ -130,7 +136,7 @@ public List findStocks(String searchTerm) { /** * Represents a player buying a share on the exchange. - *

Creates a {@link Purchase} {@link Transaction} for the + *

Creates a {@link Purchase} transaction for the * purchase.

* * @param symbol the symbol/ticker of the stock @@ -146,7 +152,7 @@ public Transaction buy(String symbol, BigDecimal quantity, Player player) { Stock stock = getStock(symbol); Share share = new Share(stock, quantity, stock.getSalesPrice()); - Transaction transaction = new Purchase(share, week); + Transaction transaction = purchaseFactory.create(share, week); transaction.commit(player); return transaction; @@ -165,7 +171,7 @@ public Transaction sell(Share share, Player player) { if (player == null) throw new IllegalArgumentException("Player cannot be null"); if (share == null) throw new IllegalArgumentException("Share cannot be null"); - Transaction transaction = new Sale(share, week); + Transaction transaction = saleFactory.create(share, week); transaction.commit(player); return transaction; } From 17c04d92a76587a664becf6e17da62352f217286 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 20:48:07 +0200 Subject: [PATCH 102/142] Added LanguageManager Acts as a manager that sets the language to be used by JavaFX, swaps the strings associated with UI elements and returns observables to watch for language changes. --- .../gruppe53/service/LanguageManager.java | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java new file mode 100644 index 0000000..09b9494 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java @@ -0,0 +1,96 @@ +package no.ntnu.gruppe53.service; + +import javafx.beans.binding.Bindings; +import javafx.beans.binding.StringBinding; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * Manages the language used in the user-interface. + */ +public class LanguageManager { + + /** + * Sets a default language and creates an observable {@link ResourceBundle}. + */ + private final ObjectProperty bundleProperty = + new SimpleObjectProperty<>(); + + private LanguageManager() { + setLocale(LanguageRegistry.getDefaultLocale()); + } + + /** + * Static Inner Class to lazily create the instance + */ + private static class LanguageManagerInner { + private static final LanguageManager INSTANCE = + new LanguageManager(); + } + + /** + * Returns the instance of the language manager + * + * @return the instance of the language manager + */ + public static LanguageManager getInstance() { + return LanguageManagerInner.INSTANCE; + } + + /** + * Returns the observable resource bundle. + * + * @return an observable resource bundle + */ + public ObjectProperty getBundleProperty() { + return bundleProperty; + } + + /** + * Sets the locale of the instance to a given locale which determines the properties file to load + * values from. + * + * @param locale the locale to change to + */ + public void setLocale(Locale locale) { + ResourceBundle bundle = + ResourceBundle.getBundle("i18n.lang", locale); + + bundleProperty.set(bundle); + } + + /** + * Returns the string value of the given key in a .properties file. + * + * @param key the key to look for in the .properties file + * @return the value of the key + */ + public String getString(String key) { + return bundleProperty.get().getString(key); + } + + /** + * Returns the current locale. + * + * @return the current locale + */ + public Locale getLocale() { + return bundleProperty.get().getLocale(); + } + + /** + * Creates a {@link StringBinding} to a given string and associates it with a given key in a .properties file + * + * @param key the key to associate the string binding with + * @return a string binding observable + */ + public StringBinding bindString(String key) { + return Bindings.createStringBinding( + () -> getString(key), + getBundleProperty() + ); + } +} \ No newline at end of file From d987e024af2d0ae055c4659e371a1cfc9085e5de Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 20:48:54 +0200 Subject: [PATCH 103/142] Added LanguageManagerTest Test class for LanguageManager. --- .../gruppe53/service/LanguageManagerTest.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 millions/src/test/java/no/ntnu/gruppe53/service/LanguageManagerTest.java diff --git a/millions/src/test/java/no/ntnu/gruppe53/service/LanguageManagerTest.java b/millions/src/test/java/no/ntnu/gruppe53/service/LanguageManagerTest.java new file mode 100644 index 0000000..4f04ae2 --- /dev/null +++ b/millions/src/test/java/no/ntnu/gruppe53/service/LanguageManagerTest.java @@ -0,0 +1,68 @@ +package no.ntnu.gruppe53.service; + +import javafx.beans.binding.StringBinding; +import javafx.beans.property.ObjectProperty; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Constructor; +import java.util.Locale; + +import static org.junit.jupiter.api.Assertions.*; + +class LanguageManagerTest { + + @Test + void constructorShouldBePrivate() throws Exception { + Constructor constructor = + LanguageManager.class.getDeclaredConstructor(); + + assertTrue(java.lang.reflect.Modifier.isPrivate(constructor.getModifiers())); + } + + @Test + void instanceShouldBeUniform() { + var lm1 = LanguageManager.getInstance(); + var lm2 = LanguageManager.getInstance(); + + assertEquals(lm1, lm2, "The instance should be unchanged."); + } + + @Test + void shouldLoadStringsCorrectly() { + LanguageManager manager = LanguageManager.getInstance(); + + manager.setLocale(new Locale("en")); + + String value = manager.getString("hello"); + + assertEquals("Hello world", value); + } + + @Test + void shouldReturnCorrectLocale() { + LanguageManager manager = LanguageManager.getInstance(); + + manager.setLocale(new Locale("en")); + + assertEquals(new Locale("en"), manager.getLocale()); + } + + @Test + void getBundlePropertyShouldBeObservable() { + assertInstanceOf(ObjectProperty.class, LanguageManager.getInstance().getBundleProperty()); + } + + @Test + void bindStringShouldBeReactiveOnLocaleChange() { + LanguageManager lm = LanguageManager.getInstance(); + + lm.setLocale(new Locale("en")); + StringBinding binding = lm.bindString("hello"); + + assertEquals("Hello world", binding.get(), "Should return english version of key."); + + lm.setLocale(new java.util.Locale("no")); + + assertEquals("Hallo verden", binding.get(), "Should return norwegian version of key."); + } +} \ No newline at end of file From 4c8fd8849003d95d6168deb04d5914d9daf36ed3 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 20:50:00 +0200 Subject: [PATCH 104/142] Added lang_en.properties Properties test file for the english language. --- millions/src/test/resources/i18n/lang_en.properties | 1 + 1 file changed, 1 insertion(+) create mode 100644 millions/src/test/resources/i18n/lang_en.properties diff --git a/millions/src/test/resources/i18n/lang_en.properties b/millions/src/test/resources/i18n/lang_en.properties new file mode 100644 index 0000000..6d5d7ab --- /dev/null +++ b/millions/src/test/resources/i18n/lang_en.properties @@ -0,0 +1 @@ +hello = Hello world \ No newline at end of file From 83de3476b51fa80c1db3deb01ebc34eae6509f65 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 20:50:24 +0200 Subject: [PATCH 105/142] Added lang_no.properties Properties test file for the norwegian language. --- millions/src/test/resources/i18n/lang_no.properties | 1 + 1 file changed, 1 insertion(+) create mode 100644 millions/src/test/resources/i18n/lang_no.properties diff --git a/millions/src/test/resources/i18n/lang_no.properties b/millions/src/test/resources/i18n/lang_no.properties new file mode 100644 index 0000000..05bdb92 --- /dev/null +++ b/millions/src/test/resources/i18n/lang_no.properties @@ -0,0 +1 @@ +hello = Hallo verden \ No newline at end of file From 61b0244595e1860e2361bd9569f55e94241a1ecb Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 20:51:43 +0200 Subject: [PATCH 106/142] Added lang.properties Default properties file that loads if target locale was not found. --- .../src/main/resources/i18n/lang.properties | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 millions/src/main/resources/i18n/lang.properties diff --git a/millions/src/main/resources/i18n/lang.properties b/millions/src/main/resources/i18n/lang.properties new file mode 100644 index 0000000..ac7a7dd --- /dev/null +++ b/millions/src/main/resources/i18n/lang.properties @@ -0,0 +1,62 @@ +# Default file to use if there is an error with language locale + +# StartView +newGame = New Game +languageButtonLabel = Toggle Language +quitGame = Quit Game + +# PlayerNameChooserView +playerNameConfirmButton = Confirm +playerNameInfo = Please enter your player name: + +# PlayerStartingMoneyChooserView +playerStartingMoneyConfirmButton = Confirm +playerStartingMoneyInfoLabel = Select your starting money balance for trading: + +# CSVChooserView +csvHeader = CSV File Required +csvContent = Please select a .csv file of the stocks to be used for the exchange in the next window.\n\nIf none is selected (or if .csv file is empty/malformed), a default file will be used.\n\nThe file needs to be formated as: StockTicker,CorporationName,PriceTypeDouble.\n\nExample: NVDA,Nvidia,191.27\n\n'#' signifies comments, and along with lines not formatted correctly, will be ignored. +csvFileChooserTitle = Select CSV File +csvFileFilterLabel = CSV Files + +# NavigationBar +navMoneyLabelText = Money: +navNetWorthLabelText = Net Worth: +navPlayerLabelText = Player: +marketButton = Market +portfolioButton = Portfolio +historyButton = History + +# FooterBar +currentWeekLabelText = Current Week: +advanceWeekButton = Advance Week + +# MarketView +marketTitle = Stock Market +xAxisLabel = Week +yAxisLabel = Price +selectedStock = Selected Stock: +purchaseQuantity = Quantity: +purchaseGross = Gross: +purchaseCommission = Commission: +purchaseTotal = Total: +purchaseButton = Purchase +stockCellFormat=%s - %s | Price: $%s | Price change: %s | Highest price: %s | Lowest price: %s + +# PortfolioView +portfolioTitle = Portfolio +selectedShare = Selected Share: +portfolioGross = Gross: +portfolioCommission = Commission: +portfolioTax = Tax: +portfolioTotal = Total: +sellButton = Sell +portfolioShareQuantity = Qty: +portfolioBuyPrice = Buy price: +portfolioCurrentPrice = Current Price: + +# TransactionArchiveView +historyTitle=Transaction History +historyTypePurchase=Purchase +historyTypeSale=Sale +historyCellFormat=Week %d | %s | %s | Qty: %s | Total: $%s From e5927c979cb8b86765943b5d5b136e19e4645f93 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 20:52:09 +0200 Subject: [PATCH 107/142] Added lang_en.properties Properties file for the english language. --- .../main/resources/i18n/lang_en.properties | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 millions/src/main/resources/i18n/lang_en.properties diff --git a/millions/src/main/resources/i18n/lang_en.properties b/millions/src/main/resources/i18n/lang_en.properties new file mode 100644 index 0000000..3e162bc --- /dev/null +++ b/millions/src/main/resources/i18n/lang_en.properties @@ -0,0 +1,62 @@ +# File for the english language + +# StartView +newGame = New Game +languageButtonLabel = Toggle Language +quitGame = Quit Game + +# PlayerNameChooserView +playerNameConfirmButton = Confirm +playerNameInfo = Please enter your player name: + +# PlayerStartingMoneyChooserView +playerStartingMoneyConfirmButton = Confirm +playerStartingMoneyInfoLabel = Select your starting money balance for trading: + +# CSVChooserView +csvHeader = CSV File Required +csvContent = Please select a .csv file of the stocks to be used for the exchange in the next window.\n\nIf none is selected (or if .csv file is empty/malformed), a default file will be used.\n\nThe file needs to be formated as: StockTicker,CorporationName,PriceTypeDouble.\n\nExample: NVDA,Nvidia,191.27\n\n'#' signifies comments, and along with lines not formatted correctly, will be ignored. +csvFileChooserTitle = Select CSV File +csvFileFilterLabel = CSV Files + +# NavigationBar +navMoneyLabelText = Money: +navNetWorthLabelText = Net Worth: +navPlayerLabelText = Player: +marketButton = Market +portfolioButton = Portfolio +historyButton = History + +# FooterBar +currentWeekLabelText = Current Week: +advanceWeekButton = Advance Week + +# MarketView +marketTitle = Stock Market +xAxisLabel = Week +yAxisLabel = Price +selectedStock = Selected Stock: +purchaseQuantity = Quantity: +purchaseGross = Gross: +purchaseCommission = Commission: +purchaseTotal = Total: +purchaseButton = Purchase +stockCellFormat=%s - %s | Price: $%s | Price change: %s | Highest price: %s | Lowest price: %s + +# PortfolioView +portfolioTitle = Portfolio +selectedShare = Selected Share: +portfolioGross = Gross: +portfolioCommission = Commission: +portfolioTax = Tax: +portfolioTotal = Total: +sellButton = Sell +portfolioShareQuantity = Qty: +portfolioBuyPrice = Buy price: +portfolioCurrentPrice = Current Price: + +# TransactionArchiveView +historyTitle=Transaction History +historyTypePurchase=Purchase +historyTypeSale=Sale +historyCellFormat=Week %d | %s | %s | Qty: %s | Total: $%s From bc20399ff73a2c2f6af03e9a90abd92334e1e642 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 20:52:26 +0200 Subject: [PATCH 108/142] Added lang_en.properties Properties file for the norwegian language. --- .../main/resources/i18n/lang_no.properties | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 millions/src/main/resources/i18n/lang_no.properties diff --git a/millions/src/main/resources/i18n/lang_no.properties b/millions/src/main/resources/i18n/lang_no.properties new file mode 100644 index 0000000..63261ec --- /dev/null +++ b/millions/src/main/resources/i18n/lang_no.properties @@ -0,0 +1,62 @@ +# File for the norwegian language + +# StartView +newGame = Nytt Spill +languageButtonLabel = Skift Språk +quitGame = Avslutt + +# PlayerNameChooserView +playerNameConfirmButton = Bekreft +playerNameInfo = Vennligst skriv spillernavnet ditt: + +# PlayerStartingMoneyChooserView +playerStartingMoneyConfirmButton = Bekreft +playerStartingMoneyInfoLabel = Velg startsaldo for aksjehandel: + +# CSVChooserView +csvHeader = CSV Fil Påkrevd +csvContent = Vennligst velg en .csv fil som inneholder aksjene som skal brukes på børsen i neste vindu. \n\nDersom ingen fil velges (eller .csv filen er tom eller har feil struktur), vil en standard fil bli brukt. \n\nFilen må formates slik: Aksjesymbol,FirmaNavn,PrisTypeDobbel. \n\nEksempel: NVDA,Nvidia,191.27\n\n'#' brukes for kommentarer, og som med feilformaterte linjer, vil slike linjer bli ignorert. +csvFileChooserTitle = Velg CSV fil +csvFileFilterLabel = CSV Filer + +# NavigationBar +navMoneyLabelText = Saldo: +navNetWorthLabelText = Nettoformue: +navPlayerLabelText = Spiller: +marketButton = Marked +portfolioButton = Portefølje +historyButton = Historikk + +# FooterBar +currentWeekLabelText = Gjeldende Uke: +advanceWeekButton = Neste Uke + +# MarketView +marketTitle = Aksjemarked +xAxisLabel = Uke +yAxisLabel = Pris +selectedStock = Valgt aksje: +purchaseQuantity = Antall: +purchaseGross = Brutto: +purchaseCommission = Kurtasje: +purchaseTotal = Total: +purchaseButton = Kjøp +stockCellFormat=%s - %s | Pris: $%s | Prisendring: %s | Høyeste pris: %s | Laveste pris: %s + +# PortfolioView +portfolioTitle = Portefølje +selectedShare = Valgt andel: +portfolioGross = Brutto: +portfolioCommission = Kurtasje: +portfolioTax = Skatt: +portfolioTotal = Total: +sellButton = Selg +portfolioShareQuantity = Ant: +portfolioBuyPrice = Kjøpspris: +portfolioCurrentPrice = Gjeldende pris: + +# TransactionArchiveView +historyTitle=Transaksjonshistorikk +historyTypePurchase=Kjøp +historyTypeSale=Salg +historyCellFormat=Uke %d | %s | %s | Ant: %s | Total: $%s \ No newline at end of file From 6618ef3c72b311180fd9dc94bcc390c250f6710c Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 20:53:03 +0200 Subject: [PATCH 109/142] Added LanguageOption Act as a datacarrier for language. --- .../no/ntnu/gruppe53/service/LanguageOption.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/service/LanguageOption.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/LanguageOption.java b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageOption.java new file mode 100644 index 0000000..cd607b1 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageOption.java @@ -0,0 +1,12 @@ +package no.ntnu.gruppe53.service; + +import java.util.Locale; + +/** + * Acts as a data-carrier representing a language with a string name and a {@link Locale}. + * + * @param name the name of the language + * @param locale the locale of the language + */ +public record LanguageOption(String name, Locale locale) {} + From 77a243bdee455198d2c72b21ea49ebf802c42471 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 20:53:39 +0200 Subject: [PATCH 110/142] Added LanguageRegistry A registry of all possible languages that can be selected by LanguageManager. --- .../gruppe53/service/LanguageRegistry.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/service/LanguageRegistry.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/LanguageRegistry.java b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageRegistry.java new file mode 100644 index 0000000..1122d2a --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageRegistry.java @@ -0,0 +1,54 @@ +package no.ntnu.gruppe53.service; + +import java.util.List; +import java.util.Locale; + +/** + * Represents a registry of allowed {@link LanguageOption}s. + */ +public class LanguageRegistry { + + /** + * Sets all possible languages that can be used. + */ + private static final List languages = List.of( + new LanguageOption("English", new Locale("en")), + new LanguageOption("Norsk", new Locale("no")) + ); + + /** + * Sets the default language to be used. + *

Searches through the registry to find english and sets it as default.

+ */ + private static final LanguageOption defaultLang = languages.stream() + .filter(lang -> lang.locale().getLanguage().equals("en")) + .findFirst() + .orElse(languages.getFirst()); + + /** + * Returns all registered languages + * + * @return a list of registered languages + */ + public static List getLanguages() { + return languages; + } + + /** + * Returns the default language. + * + * @return the default language + */ + public static LanguageOption getDefaultLanguage() { + return defaultLang; + } + + /** + * Returns the default locale of the default language. + * + * @return the default locale + */ + public static Locale getDefaultLocale() { + return defaultLang.locale(); + } +} From 52ccd01b70b19215d0784245d059be753edf0963 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 22:14:11 +0200 Subject: [PATCH 111/142] Added LanguageRegistryTest Test file for LanguageRegistry. --- .../service/LanguageRegistryTest.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 millions/src/test/java/no/ntnu/gruppe53/service/LanguageRegistryTest.java diff --git a/millions/src/test/java/no/ntnu/gruppe53/service/LanguageRegistryTest.java b/millions/src/test/java/no/ntnu/gruppe53/service/LanguageRegistryTest.java new file mode 100644 index 0000000..ab72057 --- /dev/null +++ b/millions/src/test/java/no/ntnu/gruppe53/service/LanguageRegistryTest.java @@ -0,0 +1,37 @@ +package no.ntnu.gruppe53.service; + +import org.junit.jupiter.api.Test; + +import java.util.Locale; + +import static org.junit.jupiter.api.Assertions.*; + +class LanguageRegistryTest { + @Test + void getLanguageShouldReturnCorrectAmountAndCorrectLanguages() { + var languages = LanguageRegistry.getLanguages(); + boolean hasEnglish = languages.stream() + .anyMatch(lang -> lang.locale().equals(new Locale("en"))); + boolean hasNorwegian = languages.stream() + .anyMatch(lang -> lang.locale().equals(new Locale("no"))); + + assertEquals(2, languages.size(), "Should be 2 languages."); + assertTrue(hasEnglish, "English should be a supported language."); + assertTrue(hasNorwegian, "Norwegian should be a supported language."); + } + + @Test + void getDefaultLanguageShouldEqualEnglish() { + assertEquals("English", LanguageRegistry.getDefaultLanguage().name(), + "Default language name should be 'English'."); + assertEquals(new Locale("en"), LanguageRegistry.getDefaultLanguage().locale(), + "Default language locale should be 'en'."); + } + + @Test + void getDefaultLocaleShouldReturnEnglish() { + assertEquals(new Locale("en"), LanguageRegistry.getDefaultLocale(), + "Default language locale should be 'en'."); + } + +} \ No newline at end of file From 492f7cf25eb267eb09c4c14e61b41720268e43a6 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 22:29:31 +0200 Subject: [PATCH 112/142] Updated StartView Changed buttons and labels to use LanguageManager for string generation. --- .../java/no/ntnu/gruppe53/view/StartView.java | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java b/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java index 5756028..a49d52b 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java @@ -5,16 +5,23 @@ import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.VBox; + +import javafx.util.Subscription; import no.ntnu.gruppe53.controller.StartViewController; import no.ntnu.gruppe53.controller.ViewController; +import no.ntnu.gruppe53.service.LanguageManager; /** * Represents the first page seen by the user of the application. + *

Uses a {@link LanguageManager} for dynamically setting button and label text. + * Subscribes to changes in language set by the manager.

*/ public class StartView extends VBox { private final Button newGameButton; + private final Button languageButton; private final Button quitButton; private final Image logo; + private Subscription languageSubscription; /** * Constructs the view and sets parameters to be exposed for the {@link StartViewController}. @@ -22,6 +29,8 @@ public class StartView extends VBox { * @param vm the {@link ViewController} for the view */ public StartView(ViewController vm) { + LanguageManager lm = LanguageManager.getInstance(); + this.setAlignment(Pos.CENTER); this.setSpacing(20); @@ -32,29 +41,47 @@ public StartView(ViewController vm) { logoView.setFitWidth(300); logoView.setPreserveRatio(true); - newGameButton = new Button("New Game"); - quitButton = new Button("Quit"); + newGameButton = new Button(); + newGameButton.textProperty().bind(lm.bindString("newGame")); + + languageButton = new Button(); + languageButton.setPrefWidth(200); + languageButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + languageSubscription = lm.getBundleProperty().subscribe(bundle -> { + languageButton.setText(lm.getString("languageButtonLabel")); + }); + + quitButton = new Button(); + quitButton.textProperty().bind(lm.bindString("quitGame")); newGameButton.setPrefWidth(200); newGameButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); quitButton.setPrefWidth(200); quitButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - this.getChildren().addAll(logoView, newGameButton, quitButton); + this.getChildren().addAll(logoView, newGameButton, languageButton, quitButton); } /** - * Sets the method to be run when + * Sets the method to be run when the new game button is pressed * - * @param action + * @param action the method to execute on button press */ public void setOnNewGame(Runnable action) { newGameButton.setOnAction(e -> action.run()); } /** + * Sets the method to be run when the languageButton is pressed + * + * @param action the method to execute on button press + */ + public void setOnLanguage(Runnable action) {languageButton.setOnAction(e -> action.run());} + + /** + * Sets the method to be run when the quit game button is pressed * - * @param action + * @param action the method to execute on button press */ public void setOnQuit(Runnable action) { quitButton.setOnAction(e -> action.run()); From 0423e585ca5ce9f820ea2c477b0d1c3020bc3a18 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 22:33:13 +0200 Subject: [PATCH 113/142] Updated StartViewController Added method to change language when clicking the language button in StartView. Updated JavaDoc. --- .../controller/StartViewController.java | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java index de9e1ae..82d8fde 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java @@ -1,16 +1,23 @@ package no.ntnu.gruppe53.controller; +import no.ntnu.gruppe53.service.LanguageManager; +import no.ntnu.gruppe53.service.LanguageOption; +import no.ntnu.gruppe53.service.LanguageRegistry; import no.ntnu.gruppe53.view.StartView; import javafx.application.Platform; +import java.util.List; + /** * The controller for {@link StartView}, * the first view for the player upon launching the app. - *

Contains two choices: + *

Contains three choices: *

    *
  • New Game
  • + *
  • Language Select
  • *
  • Quit
  • *

+ *

Uses a {@link LanguageManager} to change the UI language.

*/ public class StartViewController { /** @@ -25,6 +32,25 @@ public StartViewController(StartView view, ViewController vm) { gameController.startNewGame(); }); + view.setOnLanguage(() -> { + LanguageManager lm = LanguageManager.getInstance(); + List availableLanguages = LanguageRegistry.getLanguages(); + + int currentIndex = 0; + for (int i = 0; i < availableLanguages.size(); i++) { + if (availableLanguages.get(i).locale().equals(lm.getLocale())) { + currentIndex = i; + break; + } + } + + int nextIndex = (currentIndex + 1) % availableLanguages.size(); + LanguageOption nextLanguage = availableLanguages.get(nextIndex); + + lm.setLocale(nextLanguage.locale()); + }); + + view.setOnQuit(Platform::exit); } } \ No newline at end of file From afea12c4e362d1992c52cf51660f295777a9cb37 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 22:35:18 +0200 Subject: [PATCH 114/142] Updated PlayerNameChooserView Updated to use a LanguageManager to insert text in given language. Updated JavaDoc. --- .../gruppe53/view/PlayerNameChooserView.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java index 669c5f2..0711ae2 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java @@ -6,6 +6,7 @@ import javafx.scene.control.*; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; +import no.ntnu.gruppe53.service.LanguageManager; import java.util.Optional; @@ -19,18 +20,22 @@ * Represents a prompt for the {@link Player}'s name */ public class PlayerNameChooserView { - Dialog dialog = new Dialog<>(); + private final LanguageManager lm = LanguageManager.getInstance(); + Dialog dialog; /** * Returns the dialog where the player can input a name of maximum 50 characters. *

A {@link TextFormatter} is used to set the maximum amount * of characters for the player name.

+ *

Uses a {@link LanguageManager} to determine text language.

* * @return the dialog for player name input */ public Optional getDialog() { + dialog = new Dialog<>(); + // Sets dialog title and header text - dialog.setTitle("Player Name"); + dialog.setTitle(null); dialog.setHeaderText(null); // Sets the dimensions for the dialog pane @@ -41,8 +46,10 @@ public Optional getDialog() { ButtonType confirmButton = new ButtonType("Confirm", ButtonBar.ButtonData.OK_DONE); - dialog.getDialogPane().getButtonTypes().add(confirmButton); + Button actualConfirmButton = (Button) dialog.getDialogPane().lookupButton(confirmButton); + + actualConfirmButton.textProperty().bind(lm.bindString("playerNameConfirmButton")); TextField nameField = new TextField(); @@ -73,9 +80,8 @@ public Optional getDialog() { counterLabel.setText(newVal.length() + " / 50"); }); - Label infoLabel = new Label( - "Please enter your player name:" - ); + Label infoLabel = new Label(); + infoLabel.textProperty().bind(lm.bindString("playerNameInfo")); VBox layout = new VBox(15); From 00d63a8e27471e3b52516726b5ccb2bb6c3b85c4 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 22:36:29 +0200 Subject: [PATCH 115/142] Updated CSVChooserView Updated to use LanguageManager to set text to given language. Updated JavaDoc. --- .../no/ntnu/gruppe53/view/CSVChooserView.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java b/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java index 092e53f..a60d295 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java @@ -3,15 +3,18 @@ import javafx.scene.control.Alert; import javafx.stage.FileChooser; import javafx.stage.Stage; +import no.ntnu.gruppe53.service.LanguageManager; import java.io.File; /** * Represents an {@link Alert} and {@link FileChooser} for informing user of .csv requirement and picking said file. + *

Uses a {@link LanguageManager} to insert set text to target language.

*/ public class CSVChooserView { private final FileChooser fileChooser = new FileChooser(); Alert newGameAlert = new Alert(Alert.AlertType.INFORMATION); + private final LanguageManager lm = LanguageManager.getInstance(); /** * Returns the dialog for the alert and file chooser and exposes it to controllers. @@ -21,13 +24,9 @@ public class CSVChooserView { */ public File getDialog(Stage stage) { // Notifies the user about .csv selection - newGameAlert.setTitle("Please read:"); - newGameAlert.setHeaderText("CSV File Required"); - newGameAlert.setContentText("Please select a .csv file of the stocks to be used for the exchange " + - " in the next window.\n\nIf none is selected (or if .csv file is empty/malformed), a default file will be used." + - "\n\nThe file needs to be formated as: StockTicker,CorporationName,PriceTypeDouble." + - "\n\nExample: NVDA,Nvidia,191.27" + "\n\n'#' signifies comments, and along with lines not formatted " + - "correctly, will be ignored."); + newGameAlert.setTitle(null); + newGameAlert.headerTextProperty().bind(lm.bindString("csvHeader")); + newGameAlert.contentTextProperty().bind(lm.bindString("csvContent")); // Sets the dimensions of the alert to show all text, and as a band-aid, be resizable newGameAlert.setResizable(true); @@ -38,11 +37,12 @@ public File getDialog(Stage stage) { newGameAlert.showAndWait(); // File chooser to choose .csv file - fileChooser.setTitle("Select CSV File"); + fileChooser.setTitle(lm.getString("csvFileChooserTitle")); // Limits to .csv file only + fileChooser.getExtensionFilters().clear(); fileChooser.getExtensionFilters().add( - new FileChooser.ExtensionFilter("CSV Files", "*.csv") + new FileChooser.ExtensionFilter(lm.getString("csvFileFilterLabel"), "*.csv") ); return fileChooser.showOpenDialog(stage); } From 00f6e802d52c145e2268b04a2edc9b371ebcee3a Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 22:38:48 +0200 Subject: [PATCH 116/142] Updated PlayerStartingMoneyChooserView Updated to set text of UI using language manager. Updated JavaDoc. --- .../view/PlayerStartingMoneyChooserView.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java index 82b0c50..7dd6d65 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java @@ -5,15 +5,18 @@ import javafx.geometry.Insets; import javafx.scene.control.*; import javafx.scene.layout.VBox; +import no.ntnu.gruppe53.service.LanguageManager; import java.math.BigDecimal; import java.util.Optional; /** * Represents the prompt for the {@link Player}'s starting money. + *

Uses a {@link LanguageManager} to set text to target language.

*/ public class PlayerStartingMoneyChooserView { Dialog dialog = new Dialog<>(); + private final LanguageManager lm = LanguageManager.getInstance(); /** * Returns the dialog for the player's starting money. @@ -25,7 +28,7 @@ public class PlayerStartingMoneyChooserView { public Optional getDialog() { // Sets dialog title and header text - dialog.setTitle("Select starting money:"); + dialog.setTitle(null); dialog.setHeaderText(null); // Sets the dimensions for the dialog pane @@ -36,9 +39,12 @@ public Optional getDialog() { ButtonType confirmButton = new ButtonType("Confirm", ButtonBar.ButtonData.OK_DONE); - dialog.getDialogPane().getButtonTypes().add(confirmButton); + Button actualConfirmButton = (Button) dialog.getDialogPane().lookupButton(confirmButton); + + actualConfirmButton.textProperty().bind(lm.bindString("playerStartingMoneyConfirmButton")); + Spinner spinner = new Spinner<>(); // Sets allowed values and step size for the spinner @@ -78,9 +84,8 @@ public Optional getDialog() { spinner.setPrefWidth(200); - Label infoLabel = new Label( - "Select your starting money balance for trading:" - ); + Label infoLabel = new Label(); + infoLabel.textProperty().bind(lm.bindString("playerStartingMoneyInfoLabel")); VBox layout = new VBox(15); From 3126b4049e8b66ee750b486ba9494c2c19a412cf Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 22:42:50 +0200 Subject: [PATCH 117/142] Updated NavigationBar Updated to use a LanguageManager to set text dynamically. Updated JavaDoc. --- .../no/ntnu/gruppe53/view/NavigationBar.java | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java index d6f7415..e494941 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java @@ -9,6 +9,7 @@ import javafx.scene.layout.Region; import javafx.util.Subscription; import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.service.LanguageManager; import java.math.RoundingMode; @@ -26,8 +27,10 @@ * clicking on the buttons representing the desired view.

*

The {@link no.ntnu.gruppe53.controller.GameController} handles * the actions for the buttons.

+ *

Uses a {@link LanguageManager} to set text of UI elements dynamically.

*/ public class NavigationBar extends HBox { + private final LanguageManager lm = LanguageManager.getInstance(); private final Button marketButton; private final Button portfolioButton; @@ -45,7 +48,10 @@ public class NavigationBar extends HBox { private Label netWorthLabel; private Label moneyLabel; - + /** + * Constructs the UI elements of the navigationBar. + *

Binds the language manager to the textProperty of the UI elements to dynamically change the text.

+ */ public NavigationBar() { this.setPadding(new Insets(15, 12, 15, 12)); @@ -56,11 +62,14 @@ public NavigationBar() { "-fx-border-width: 0 0 5 0;"); - marketButton = new Button("Market"); + marketButton = new Button(); + marketButton.textProperty().bind(lm.bindString("marketButton")); marketButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - portfolioButton = new Button("Portfolio"); + portfolioButton = new Button(); + portfolioButton.textProperty().bind(lm.bindString("portfolioButton")); portfolioButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - historyButton = new Button("History"); + historyButton = new Button(); + historyButton.textProperty().bind(lm.bindString("historyButton")); historyButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); Region spacer = new Region(); @@ -68,7 +77,8 @@ public NavigationBar() { HBox playerBox = new HBox(); - Label playerLabelText = new Label("Player: "); + Label playerLabelText = new Label(); + playerLabelText.textProperty().bind(lm.bindString("navPlayerLabelText")); playerLabel = new Label("Player"); playerBox.setStyle("-fx-background-color: #ffe556;" + @@ -85,7 +95,8 @@ public NavigationBar() { HBox moneyBox = new HBox(); - Label moneyLabelText = new Label("Money: "); + Label moneyLabelText = new Label(); + moneyLabelText.textProperty().bind(lm.bindString("navMoneyLabelText")); moneyLabel = new Label(); moneyBox.setStyle("-fx-background-color: #ffe556;" + @@ -100,7 +111,8 @@ public NavigationBar() { HBox networthBox = new HBox(); - Label networthLabelText = new Label("Net Worth: "); + Label networthLabelText = new Label(); + networthLabelText.textProperty().bind(lm.bindString("navNetWorthLabelText")); netWorthLabel = new Label(); networthBox.setStyle("-fx-background-color: #ffe556;" + @@ -161,16 +173,16 @@ public void bindPlayer(Player player) { playerLabel.setText(player.getName()); netWorthSubscription = player.getNetWorthProperty().subscribe(newVal -> { if (newVal != null) { - netWorthLabel.setText("Net Worth: $" + newVal.setScale(2, RoundingMode.HALF_EVEN)); + netWorthLabel.setText("$" + newVal.setScale(2, RoundingMode.HALF_EVEN)); } else { - netWorthLabel.setText("Net Worth: $0.00"); + netWorthLabel.setText("$0.00"); } }); moneySubscription = player.getMoneyProperty().subscribe(newVal -> { if (newVal != null) { - moneyLabel.setText("Money: $" + newVal.setScale(2, RoundingMode.HALF_EVEN)); + moneyLabel.setText("$" + newVal.setScale(2, RoundingMode.HALF_EVEN)); } else { - moneyLabel.setText("Money: $0.00"); + moneyLabel.setText("$0.00"); } }); From 41ad7cbbb9300cfb16f04585379caef38f7a0f0e Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 22:47:26 +0200 Subject: [PATCH 118/142] Updated FooterBar Updated to use LanguageManager to set text dynamically and uses a ComboBox to change language quickly. Updated JavaDoc. --- .../java/no/ntnu/gruppe53/view/FooterBar.java | 171 +++++++++++++----- 1 file changed, 122 insertions(+), 49 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java index efea8a3..58cf825 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java @@ -1,18 +1,25 @@ package no.ntnu.gruppe53.view; -import java.math.RoundingMode; +import java.util.Locale; +import java.util.function.Consumer; + import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; import javafx.scene.control.Label; +import javafx.scene.control.ListCell; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.util.Subscription; -import no.ntnu.gruppe53.model.Player; + import no.ntnu.gruppe53.controller.GameController; import no.ntnu.gruppe53.model.Exchange; +import no.ntnu.gruppe53.service.LanguageManager; +import no.ntnu.gruppe53.service.LanguageOption; +import no.ntnu.gruppe53.service.LanguageRegistry; /* Yellow Color, Hex: #ffe556 RGB(255, 229, 86) @@ -23,65 +30,117 @@ /** * A {@link HBox} view representing the footer/bottom to be used in a {@link BorderPane} layout. + *

Uses a {@link LanguageManager} to change text to target language dynamically.

*/ public class FooterBar extends HBox { private final Button advanceWeekButton; - private Label currentWeekLabel; - private Subscription currentWeekSubscription; + private Label currentWeekLabel; + private Subscription currentWeekSubscription; - private Exchange exchange; + private Exchange exchange; + private final LanguageManager lm = LanguageManager.getInstance(); + + private final ComboBox languageBox; /** * Constructs the layout of the view and its components. + *

Constructs a {@link ComboBox} to let the user switch language on the fly.

+ *

Sets the text of the UI elements dynamically.

*/ public FooterBar() { this.setSpacing(10); this.setPadding(new Insets(15, 12, 15, 12)); - this.setStyle("-fx-background-color: #00bcf0;" + - "-fx-border-color: #303539 transparent transparent transparent;" + - "-fx-border-width: 5 0 0 0;"); - - - - - Region spacer = new Region(); - HBox.setHgrow(spacer, Priority.ALWAYS); - - HBox currentWeekBox = new HBox(); - currentWeekBox.setSpacing(10); - - currentWeekBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - currentWeekBox.setPadding(new Insets(8, 14, 8, 14)); - currentWeekBox.setAlignment(Pos.CENTER); - - currentWeekLabel = new Label(); - currentWeekBox.getChildren().add(currentWeekLabel); - - - - advanceWeekButton = new Button("Advance Week"); - advanceWeekButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - - this.getChildren().addAll(spacer, currentWeekBox, advanceWeekButton); - } - - public void setExchange(Exchange exchange) { - this.exchange = exchange; - currentWeekSubscription = exchange.getCurrentWeekProperty().subscribe(newVal -> { - if (exchange != null) { - currentWeekLabel.setText("Current Week: " + newVal); - } else { - currentWeekLabel.setText("Current Week: None"); - } - }); + this.setStyle("-fx-background-color: #00bcf0;" + + "-fx-border-color: #303539 transparent transparent transparent;" + + "-fx-border-width: 5 0 0 0;"); + + languageBox = new ComboBox<>(); + languageBox.getItems().addAll(LanguageRegistry.getLanguages()); + + Locale activeLocale = lm.getLocale(); + LanguageOption activeOption = LanguageRegistry.getLanguages().stream() + .filter(lang -> lang.locale().equals(activeLocale)) + .findFirst() + .orElse(LanguageRegistry.getDefaultLanguage()); + + languageBox.setValue(activeOption); + + // Listens for changes in language from other sources (e.g. startView) + lm.getBundleProperty().subscribe(bundle -> { + java.util.Locale currentLocale = lm.getLocale(); + + LanguageOption matchingOption = LanguageRegistry.getLanguages().stream() + .filter(lang -> lang.locale().equals(currentLocale)) + .findFirst() + .orElse(null); + + if (matchingOption != null && !matchingOption.equals(languageBox.getValue())) { + languageBox.setValue(matchingOption); + } + }); + + languageBox.setCellFactory(list -> new ListCell<>() { + private Subscription cellSub; + @Override + protected void updateItem(LanguageOption item, boolean empty) { + super.updateItem(item, empty); + if (cellSub != null) cellSub.unsubscribe(); + + if (empty || item == null) { + setText(null); + } else { + cellSub = lm.getBundleProperty().subscribe(bundle -> setText(item.name())); + } + } + }); + + languageBox.setButtonCell(languageBox.getCellFactory().call(null)); + + + + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + + HBox currentWeekBox = new HBox(); + currentWeekBox.setSpacing(10); + + currentWeekBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + currentWeekBox.setPadding(new Insets(8, 14, 8, 14)); + currentWeekBox.setAlignment(Pos.CENTER); + + Label currentWeekLabelText = new Label(); + currentWeekLabelText.textProperty().bind(lm.bindString("currentWeekLabelText")); + currentWeekLabel = new Label(); + currentWeekBox.getChildren().addAll(currentWeekLabelText, currentWeekLabel); + + advanceWeekButton = new Button(); + advanceWeekButton.textProperty().bind(lm.bindString("advanceWeekButton")); + advanceWeekButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + + this.getChildren().addAll(languageBox, spacer, currentWeekBox, advanceWeekButton); + } - } + /** + * Sets the exchange and subscribes to week updates. + * + * @param exchange the {@link Exchange} used in the game + */ + public void setExchange(Exchange exchange) { + this.exchange = exchange; + currentWeekSubscription = exchange.getCurrentWeekProperty().subscribe(newVal -> { + if (exchange != null) { + currentWeekLabel.setText(newVal.toString()); + } else { + currentWeekLabel.setText("None"); + } + }); + } /** * Exposes the advanceWeekButton to {@link GameController} so it can @@ -92,4 +151,18 @@ public void setExchange(Exchange exchange) { public void onAdvanceButtonClick(Runnable action) { advanceWeekButton.setOnAction(e -> action.run()); } -} + + /** + * Exoposes the LanguageOption ComboButton to the controller. + * + * @param action the action to be performed when selecting the language + */ + public void onLanguageChange(Consumer action) { + languageBox.setOnAction(e -> { + LanguageOption selected = languageBox.getValue(); + if (selected != null) { + action.accept(selected); + } + }); + } +} \ No newline at end of file From 6868f1ed5b29221ba611118b62410ab30e582d3e Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 22:47:42 +0200 Subject: [PATCH 119/142] Updated LanguageManager Updated JavaDoc. --- .../src/main/java/no/ntnu/gruppe53/service/LanguageManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java index 09b9494..776f15a 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java +++ b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java @@ -10,6 +10,7 @@ /** * Manages the language used in the user-interface. + *

The possible languages to be used are set by {@link LanguageRegistry}.

*/ public class LanguageManager { From b3e25938e973a159a981f4551f4a3c6a484f30a7 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 22:54:56 +0200 Subject: [PATCH 120/142] Updated MarketView Updated to use a LanguageManager for dynamically setting text to the target language. Updated JavaDoc. Added some comments. --- .../no/ntnu/gruppe53/view/MarketView.java | 69 +++++++++++++------ 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java index 380ef7b..bab3197 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java @@ -16,6 +16,7 @@ import javafx.util.Subscription; import no.ntnu.gruppe53.model.*; import no.ntnu.gruppe53.service.FormatBigDecimal; +import no.ntnu.gruppe53.service.LanguageManager; /* Yellow Color, Hex: #ffe556 RGB(255, 229, 86) @@ -29,15 +30,14 @@ * price history statistics in the form of a {@link LineChart} through the * {@link StockLineChartView} class, as well as buying {@link Stock}s. *

Extends borderpane to allow easy organizing of the internal layout.

+ *

Uses a {@link LanguageManager} to set text to the targer language dynamically.

*/ public class MarketView extends BorderPane { + private final LanguageManager lm = LanguageManager.getInstance(); private Button purchaseButton; private Label selectedStock; - - - //Dynamic Labels/Text public TextField gross; public TextField commission; @@ -52,7 +52,6 @@ public class MarketView extends BorderPane { private PurchaseCalculator purchaseCalculator; private final StockLineChartView stockLineChartView; - private Subscription playerSubscription; private Subscription priceSubscription; private Subscription grossSubscription; private Subscription commissionSubscription; @@ -71,6 +70,7 @@ public MarketView() { /** * Constructs the view and its components. *

Subscribes to a stock's sales price to update the value immediately.

+ *

Subscribes to the current language to change text immediately.

*

Uses a cell factory to format the string found in the stocksListView.

* * @return the constructed MarketView @@ -83,7 +83,8 @@ private VBox centerPane() { //Blue Background color vBox.setStyle("-fx-background-color: #00bcf0;"); - Label titleLabel = new Label("Stock Market"); + Label titleLabel = new Label(); + titleLabel.textProperty().bind(lm.bindString("marketTitle")); titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; "); VBox titleBox = new VBox(titleLabel); @@ -97,14 +98,15 @@ private VBox centerPane() { titleBox.setPadding(new Insets(4)); titleBox.setMaxWidth(200); - + // Stock Graph stockLineChartView.getChart().setMinHeight(0); stockLineChartView.getChart().setMaxHeight(Double.MAX_VALUE); stockLineChartView.getChart().setMinWidth(0); stockLineChartView.getChart().setMaxWidth(Double.MAX_VALUE); + stockLineChartView.getChart().getXAxis().labelProperty().bind(lm.bindString("xAxisLabel")); + stockLineChartView.getChart().getYAxis().labelProperty().bind(lm.bindString("yAxisLabel")); - - + // List of stocks stocksListView = new ListView<>(); stocksListView.setMinHeight(0); stocksListView.setMaxHeight(Double.MAX_VALUE); @@ -114,6 +116,7 @@ private VBox centerPane() { stocksListView.setCellFactory(list -> new ListCell<>() { private Subscription cellSubscription; + private Subscription langSubscription; @Override protected void updateItem(Stock stock, boolean empty) { @@ -123,22 +126,37 @@ protected void updateItem(Stock stock, boolean empty) { cellSubscription.unsubscribe(); cellSubscription = null; } + if (langSubscription != null) { + langSubscription.unsubscribe(); + langSubscription = null; + } if (empty || stock == null) { setText(null); } else { - cellSubscription = stock.salesPriceProperty().subscribe(newPrice -> { - setText(stock.getSymbol() + " - " + stock.getCompany() + " | Price: $" + - FormatBigDecimal.formatNumber(newPrice) - + " | Price change: " + FormatBigDecimal.formatNumber(stock.getLatestPriceChange()) - + " | Highest price: " + FormatBigDecimal.formatNumber(stock.getHighestPrice()) - + " | Lowest price: " + FormatBigDecimal.formatNumber(stock.getLowestPrice())); - }); + Runnable updateTextAction = () -> { + String template = lm.getString("stockCellFormat"); + setText(String.format(template, + stock.getSymbol(), + stock.getCompany(), + FormatBigDecimal.formatNumber(stock.salesPriceProperty().get()), + FormatBigDecimal.formatNumber(stock.getLatestPriceChange()), + FormatBigDecimal.formatNumber(stock.getHighestPrice()), + FormatBigDecimal.formatNumber(stock.getLowestPrice()) + )); + }; + + cellSubscription = stock.salesPriceProperty().subscribe(newPrice -> updateTextAction.run()); + langSubscription = lm.getBundleProperty().subscribe(bundle -> updateTextAction.run()); + + updateTextAction.run(); } } }); - HBox stockListBox = new HBox(stocksListView); + + // Container for list of stocks + HBox stockListBox = new HBox(stocksListView); stockListBox.setStyle("-fx-background-color: #ffe556;" + "-fx-text-fill: #303539;" + "-fx-background-radius: 8;" + @@ -146,6 +164,7 @@ protected void updateItem(Stock stock, boolean empty) { "-fx-border-radius: 8;" ); + // Container for graph of stocks HBox stockChartBox = new HBox(stockLineChartView.getChart()); stockChartBox.setStyle("-fx-background-color: #ffe556;" + "-fx-text-fill: #303539;" + @@ -176,14 +195,16 @@ protected void updateItem(Stock stock, boolean empty) { HBox selectedStockBox = new HBox(); - Label selectedStockLabel = new Label("Selected Stock: "); + Label selectedStockLabel = new Label(); + selectedStockLabel.textProperty().bind(lm.bindString("selectedStock")); selectedStock = new Label(""); selectedStockBox.getChildren().addAll(selectedStockLabel, selectedStock); HBox quantityBox = new HBox(); - Label quantityLabel = new Label("Quantity: "); + Label quantityLabel = new Label(); + quantityLabel.textProperty().bind(lm.bindString("purchaseQuantity")); quantitySpinner = new Spinner<>(1, 1_000_000, 1); quantitySpinner.setEditable(true); @@ -191,11 +212,13 @@ protected void updateItem(Stock stock, boolean empty) { HBox calculationBox = new HBox(); - Label grossLabel = new Label("Gross: "); + Label grossLabel = new Label(); + grossLabel.textProperty().bind(lm.bindString("purchaseGross")); gross = new TextField(); gross.setEditable(false); gross.setMaxWidth(200); - Label commissionLabel = new Label("Commission: "); + Label commissionLabel = new Label(); + commissionLabel.textProperty().bind(lm.bindString("purchaseCommission")); commission = new TextField(); commission.setEditable(false); commission.setMaxWidth(200); @@ -204,14 +227,16 @@ protected void updateItem(Stock stock, boolean empty) { HBox totalBox = new HBox(); - Label totalLabel = new Label("Total: "); + Label totalLabel = new Label(); + totalLabel.textProperty().bind(lm.bindString("purchaseTotal")); total = new TextField(""); total.setEditable(false); total.setMaxWidth(200); totalBox.getChildren().addAll(totalLabel, total); - purchaseButton = new Button("Purchase"); + purchaseButton = new Button(); + purchaseButton.textProperty().bind(lm.bindString("purchaseButton")); VBox purchaseBox = new VBox(5); From 331b8b13ac0981453ee45ef673141985fbb6a1e3 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 22:58:59 +0200 Subject: [PATCH 121/142] Updated GameController Updated to include method in footer for setting language locale. Updated JavaDoc. --- .../ntnu/gruppe53/controller/GameController.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index 6bc40d8..ae36e05 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -1,25 +1,24 @@ package no.ntnu.gruppe53.controller; import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import javafx.scene.control.Alert; import javafx.stage.Stage; import no.ntnu.gruppe53.model.*; +import no.ntnu.gruppe53.service.LanguageManager; import no.ntnu.gruppe53.service.StockLineChartService; import no.ntnu.gruppe53.view.*; /** * Represents the main controller for the views of the stock game. + *

Uses a {@link LanguageManager} for methods to change text dynamically.

*/ public class GameController { private Player player; private Exchange exchange; private List stocks; + private final LanguageManager lm = LanguageManager.getInstance(); private final ViewController vm; @@ -111,6 +110,7 @@ public void showTransactionHistory() { * Initializes the {@link NavigationBar} and {@link FooterBar} for the controller. *

Binds functionality to exposed buttons and lists in views.

*

Sets subscriptions for observable values for views.

+ *

Sets the method for setting the selected language locale for the footer.

* @param nav the NavigationBar view * @param footer the FooterBar view */ @@ -119,6 +119,10 @@ public void initialize(NavigationBar nav, FooterBar footer) { nav.onPortfolioButtonClick(() -> vm.switchView("portfolio")); nav.onHistoryButtonClick(() -> vm.switchView("history")); + footer.onLanguageChange(selected -> { + lm.setLocale(selected.locale()); + }); + footer.onAdvanceButtonClick(() -> { if (exchange != null) { exchange.advance(); @@ -223,8 +227,8 @@ private PurchaseCalculator calculatePurchase(Stock stock, int quantity) { /** * Creates a {@link SaleCalculator} that can be used for the - * for the setSaleCalculator() function in the {@link PortfolioView}. - * @param share + * setSaleCalculator() function in the {@link PortfolioView}. + * @param share the share to use the calculator on * @return {@link SaleCalculator} */ private SaleCalculator calculateSale(Share share) { From 7fc4129e77c90d4e23017cdd6e505cd88cc48e61 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 23:13:24 +0200 Subject: [PATCH 122/142] Updated PortfolioView Updated UI to use language manager to dynamically set text. Updated JavaDoc. --- .../no/ntnu/gruppe53/view/PortfolioView.java | 65 ++++++++++++++----- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java index 7388c16..290d375 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java @@ -2,7 +2,6 @@ import java.math.BigDecimal; import java.math.RoundingMode; -import java.util.function.BiConsumer; import java.util.function.Consumer; import javafx.collections.ObservableList; @@ -23,8 +22,8 @@ import no.ntnu.gruppe53.model.Player; import no.ntnu.gruppe53.model.SaleCalculator; import no.ntnu.gruppe53.model.Share; -import no.ntnu.gruppe53.model.Stock; import no.ntnu.gruppe53.service.FormatBigDecimal; +import no.ntnu.gruppe53.service.LanguageManager; /* Yellow Color, Hex: #ffe556 RGB(255, 229, 86) @@ -40,13 +39,12 @@ * and the ability to sell the share for money.

*

Extends the {@link BorderPane}

for easy layout setup. *

Events are handled by the {@link no.ntnu.gruppe53.controller.GameController}.

+ *

Uses a {@link LanguageManager} to dynamically set text based on target language.

*/ public class PortfolioView extends BorderPane { - private Button sellButton; + private final LanguageManager lm = LanguageManager.getInstance(); - //NavigationBar Buttons - private Button marketButton; - private Button portfolioButton; + private Button sellButton; //Static Labels private Label titleLabel; @@ -79,7 +77,8 @@ public PortfolioView() { /** * Constructs the view of the portfolio. *

Subscribes to shares for sales price updates.

- *

Uses a cell factory to format the displayed string representing each share.

+ *

Uses a cell factory to format the displayed string representing each share. + * The sell factory has the text dynamically set by the language manager.

* * @return a VBox of the portfolio view */ @@ -90,7 +89,8 @@ private VBox centerPane() { //Blue Background color vBox.setStyle("-fx-background-color: #00bcf0; "); - titleLabel = new Label("Portfolio"); + titleLabel = new Label(); + titleLabel.textProperty().bind(lm.bindString("portfolioTitle")); titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: #303539;"); VBox titleBox = new VBox(titleLabel); @@ -110,6 +110,7 @@ private VBox centerPane() { portfolioListView.setCellFactory(list -> new ListCell<>() { private Subscription cellSubscription; + private Subscription langSubscription; @Override protected void updateItem(Share item, boolean empty) { @@ -119,15 +120,32 @@ protected void updateItem(Share item, boolean empty) { cellSubscription.unsubscribe(); cellSubscription = null; } + if (langSubscription != null) { + langSubscription.unsubscribe(); + langSubscription = null; + } if (empty || item == null || item.getStock() == null) { setText(null); setGraphic(null); } else { setText(null); - cellSubscription = item.getStock().salesPriceProperty().subscribe(currentPrice -> { + + + Runnable updateGraphicAction = () -> { + BigDecimal currentPrice = item.getStock().salesPriceProperty().get(); setGraphic(createColoredShareText(item, currentPrice)); + }; + + cellSubscription = item.getStock().salesPriceProperty().subscribe(currentPrice -> { + updateGraphicAction.run(); }); + + langSubscription = lm.getBundleProperty().subscribe(bundle -> { + updateGraphicAction.run(); + }); + + updateGraphicAction.run(); } } }); @@ -150,22 +168,26 @@ protected void updateItem(Share item, boolean empty) { HBox selectedShareBox = new HBox(); - Label selectedShareLabel = new Label("Selected Share: "); + Label selectedShareLabel = new Label(); + selectedShareLabel.textProperty().bind(lm.bindString("selectedShare")); selectedShare = new Label(); selectedShareBox.getChildren().addAll(selectedShareLabel, selectedShare); HBox calculationBox = new HBox(); - Label grossLabel = new Label("Gross: "); + Label grossLabel = new Label(); + grossLabel.textProperty().bind(lm.bindString("portfolioGross")); gross = new TextField(); gross.setEditable(false); gross.setMaxWidth(200); - Label commissionLabel = new Label("Commission: "); + Label commissionLabel = new Label(); + commissionLabel.textProperty().bind(lm.bindString("portfolioCommission")); commission = new TextField(); commission.setEditable(false); commission.setMaxWidth(200); - Label taxLabel = new Label("Tax: "); + Label taxLabel = new Label(); + taxLabel.textProperty().bind(lm.bindString("portfolioTax")); tax = new TextField(); tax.setEditable(false); tax.setMaxWidth(200); @@ -176,14 +198,16 @@ protected void updateItem(Share item, boolean empty) { HBox totalBox = new HBox(); - Label totalLabel = new Label("Total: "); + Label totalLabel = new Label(); + totalLabel.textProperty().bind(lm.bindString("portfolioTotal")); total = new TextField(); total.setEditable(false); total.setMaxWidth(200); totalBox.getChildren().addAll(totalLabel, total); - sellButton = new Button("Sell"); + sellButton = new Button(); + sellButton.textProperty().bind(lm.bindString("sellButton")); VBox saleBox = new VBox(5); @@ -316,11 +340,16 @@ private TextFlow createColoredShareText(Share share, BigDecimal currentPrice) { String text = share.getStock().getSymbol() + " - " + share.getStock().getCompany() - + " | Qty: " + + " | " + + lm.getString("portfolioShareQuantity") + share.getQuantity() - + " | Buy price: $" + + " | " + + lm.getString("portfolioBuyPrice") + + "$" + FormatBigDecimal.formatNumber(share.getPurchasePrice()) - + " | Current Price: $"; + + " | " + + lm.getString("portfolioCurrentPrice") + + "$"; Text t1 = new Text(text); Text tValue = new Text(FormatBigDecimal.formatNumber(currentPrice)); From 547c9f5e83834e888806cd2890641087375834a4 Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 23:14:59 +0200 Subject: [PATCH 123/142] Updated TransactionArchiveView Updated to use language manager to set text dynamically. Updated JavaDoc. --- .../gruppe53/view/TransactionArchiveView.java | 59 ++++++++++++++----- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java index 630adc8..fb5e11b 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java @@ -10,11 +10,14 @@ import javafx.scene.layout.VBox; import java.math.BigDecimal; import java.math.RoundingMode; + +import javafx.util.Subscription; import no.ntnu.gruppe53.model.Player; import no.ntnu.gruppe53.model.Transaction; import no.ntnu.gruppe53.model.Purchase; import no.ntnu.gruppe53.model.Sale; import no.ntnu.gruppe53.service.FormatBigDecimal; +import no.ntnu.gruppe53.service.LanguageManager; /* Yellow Color, Hex: #ffe556 RGB(255, 229, 86) @@ -28,6 +31,7 @@ *

Extends the {@link BorderPane} for easy layout setup.

*/ public class TransactionArchiveView extends BorderPane { + private final LanguageManager lm = LanguageManager.getInstance(); private ListView historyListView; private Label titleLabel; @@ -46,6 +50,7 @@ public TransactionArchiveView() { *

Assigns red text to purchases, and green text to sales.

*

Uses the formatBigDecimal method to convert the BigDecimal to a * string with 2 decimals.

+ *

Uses a {@link LanguageManager} to dynamically set text based on target language.

* * @return a VBox of the constructed view of the transaction archive */ @@ -54,7 +59,8 @@ private VBox centerPane() { vBox.setPadding(new Insets(15, 12, 15, 12)); vBox.setStyle("-fx-background-color: #00bcf0; "); - titleLabel = new Label("Transaction History"); + titleLabel = new Label(); + titleLabel.textProperty().bind(lm.bindString("historyTitle")); titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: #303539;"); VBox titleBox = new VBox(titleLabel); @@ -73,28 +79,50 @@ private VBox centerPane() { historyListView.setMaxHeight(Double.MAX_VALUE); historyListView.setCellFactory(list -> new ListCell<>() { + private Subscription langSubscription; + @Override protected void updateItem(Transaction item, boolean empty) { super.updateItem(item, empty); + if (langSubscription != null) { + langSubscription.unsubscribe(); + langSubscription = null; + } + if (empty || item == null) { setText(null); setStyle(""); } else { - String type = (item instanceof Purchase) ? "PURCHASE" : "SALE"; - String symbol = item.getShare().getStock().getSymbol(); - BigDecimal quantity = item.getShare().getQuantity(); - BigDecimal total = item.getCalculator().calculateTotal(); - - String cellText = String.format("Week %d | %s | %s | Qty: %s | Total: $%s", - item.getWeek(), - type, - symbol, - FormatBigDecimal.formatNumber(quantity), - FormatBigDecimal.formatNumber(total) - ); - - setText(cellText); + Runnable updateTextAction = () -> { + String typeStr; + // Changed to allow for more transaction types in the future + if (item instanceof Purchase) { + typeStr = lm.getString("historyTypePurchase"); + } else if (item instanceof Sale) { + typeStr = lm.getString("historyTypeSale"); + } else { + typeStr = "ERROR"; + } + + BigDecimal quantity = item.getShare().getQuantity(); + BigDecimal total = item.getCalculator().calculateTotal(); + String symbol = item.getShare().getStock().getSymbol(); + + String template = lm.getString("historyCellFormat"); + + setText(String.format(template, + item.getWeek(), + typeStr, + symbol, + FormatBigDecimal.formatNumber(quantity), + FormatBigDecimal.formatNumber(total) + )); + }; + + langSubscription = lm.getBundleProperty().subscribe(bundle -> updateTextAction.run()); + + updateTextAction.run(); if (item instanceof Purchase) { setStyle("-fx-text-fill: #c0392b; -fx-font-weight: bold;"); @@ -105,6 +133,7 @@ protected void updateItem(Transaction item, boolean empty) { } }); + VBox historyListBox = new VBox(historyListView); historyListBox.setStyle("-fx-background-color: #ffe556;" + "-fx-text-fill: #303539;" + From a25a9966d75a9a1be82dcfd575a4460d552ee46f Mon Sep 17 00:00:00 2001 From: Roar Date: Sun, 24 May 2026 23:50:30 +0200 Subject: [PATCH 124/142] Updated GameController Added two new subscriptions: selectedStockPriceSubscription and selectedSharePriceSubscription to dynamically update the calculator fields on stock/share price change. --- .../gruppe53/controller/GameController.java | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index ae36e05..1a1d865 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -5,6 +5,7 @@ import javafx.scene.control.Alert; import javafx.stage.Stage; +import javafx.util.Subscription; import no.ntnu.gruppe53.model.*; import no.ntnu.gruppe53.service.LanguageManager; import no.ntnu.gruppe53.service.StockLineChartService; @@ -28,6 +29,9 @@ public class GameController { private PurchaseCalculator purchaseCalculator; private SaleCalculator saleCalculator; + private Subscription selectedStockPriceSubscription; + private Subscription selectedSharePriceSubscription; + /** * Initializes the {@link ViewController} to be used for switching views. * @param vm {@code ViewController} to be used when switching views @@ -130,6 +134,11 @@ public void initialize(NavigationBar nav, FooterBar footer) { }); marketView.onStockSelection(stock -> { + if (selectedStockPriceSubscription != null) { + selectedStockPriceSubscription.unsubscribe(); + selectedStockPriceSubscription = null; + } + if (stock != null) { marketView.setSelectedStockLabel(stock.getSymbol() + " - " + stock.getCompany()); marketView.setupPriceSubscription(stock, currentStock -> { @@ -138,6 +147,16 @@ public void initialize(NavigationBar nav, FooterBar footer) { }); marketView.setPurchaseCalculator(calculatePurchase(marketView.getSelectedStock(), marketView.getQuantityPurchase())); + + selectedStockPriceSubscription = stock.salesPriceProperty().subscribe(newPrice -> { + if (marketView.getSelectedStock() != null) { + marketView.setPurchaseCalculator(calculatePurchase( + marketView.getSelectedStock(), + marketView.getQuantityPurchase() + )); + } + }); + } else { marketView.setSelectedStockLabel(""); marketView.clearChart(); @@ -149,17 +168,28 @@ public void initialize(NavigationBar nav, FooterBar footer) { if (stock != null) purchaseStock(stock.getSymbol(), marketView.getQuantityPurchase()); }); - marketView.onQuantitySelect(() -> { - if (marketView.getSelectedStock() != null) { + marketView.onQuantitySelect(() -> { + if (marketView.getSelectedStock() != null) { marketView.setPurchaseCalculator(calculatePurchase(marketView.getSelectedStock(), marketView.getQuantityPurchase())); } - }); + }); portfolioView.onShareSelection(share -> { + if (selectedSharePriceSubscription != null) { + selectedSharePriceSubscription.unsubscribe(); + selectedSharePriceSubscription = null; + } if (share != null) { portfolioView.setSelectedShareLabel(share.getStock().getSymbol() + " - " + share.getStock().getCompany()); portfolioView.setSaleCalculator(calculateSale(portfolioView.getSelectedShare())); + + selectedSharePriceSubscription = share.getStock().salesPriceProperty().subscribe(newPrice -> { + if (portfolioView.getSelectedShare() != null) { + portfolioView.setSaleCalculator(calculateSale(portfolioView.getSelectedShare())); + } + }); + } else { portfolioView.setSelectedShareLabel(""); } @@ -209,7 +239,7 @@ private void sellShare(Share selectedShare) { /** * Creates a {@link PurchaseCalculator} that can be used for the - * for the setPurchaseCalculator() function in the {@link MarketView}. + * setPurchaseCalculator() function in the {@link MarketView}. * @param stock the currently selected stock * @param quantity the currently selected quantity * @return {@link PurchaseCalculator} From d94df4e5b1d134bb0ace8a402da007725faef451 Mon Sep 17 00:00:00 2001 From: Roar Date: Mon, 25 May 2026 12:33:37 +0200 Subject: [PATCH 125/142] Updated pom.xml Changed org.openjfx javafx-controls to the required 25.0.1 version. --- millions/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/millions/pom.xml b/millions/pom.xml index fed2ae7..117b7d9 100644 --- a/millions/pom.xml +++ b/millions/pom.xml @@ -26,7 +26,7 @@ org.openjfx javafx-controls - 25.0.3 + 25.0.1 From 37d00612e76ac5a135818b55c34b07fc062dc47f Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Mon, 25 May 2026 17:49:03 +0200 Subject: [PATCH 126/142] Added biggest loser and winner to the footer --- .../java/no/ntnu/gruppe53/view/FooterBar.java | 65 ++++++++++++++++++- .../no/ntnu/gruppe53/view/NavigationBar.java | 8 +++ .../compile/default-compile/createdFiles.lst | 3 + .../compile/default-compile/inputFiles.lst | 3 + 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java index efea8a3..b5cc00c 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java @@ -13,6 +13,7 @@ import no.ntnu.gruppe53.model.Player; import no.ntnu.gruppe53.controller.GameController; import no.ntnu.gruppe53.model.Exchange; +import no.ntnu.gruppe53.model.Stock; /* Yellow Color, Hex: #ffe556 RGB(255, 229, 86) @@ -28,7 +29,13 @@ public class FooterBar extends HBox { private final Button advanceWeekButton; private Label currentWeekLabel; + private Label biggestLoserLabel; + private Label biggestLoserPriceLabel; + private Label biggestWinnerLabel; + private Label biggestWinnerPriceLabel; private Subscription currentWeekSubscription; + private Subscription biggestLoserSubscription; + private Subscription biggestWinnerSubscription; private Exchange exchange; @@ -42,7 +49,43 @@ public FooterBar() { "-fx-border-color: #303539 transparent transparent transparent;" + "-fx-border-width: 5 0 0 0;"); + HBox loserBox = new HBox(); + Label biggestLoserLabelText = new Label("Biggest Loser: "); + biggestLoserLabel = new Label(); + biggestLoserPriceLabel = new Label(); + biggestLoserPriceLabel.setStyle("-fx-text-fill: red;"); + + loserBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + loserBox.setPadding(new Insets(8, 14, 8, 14)); + loserBox.setAlignment(Pos.CENTER); + + loserBox.getChildren().addAll(biggestLoserLabelText, biggestLoserLabel, biggestLoserPriceLabel); + + HBox winnerBox = new HBox(); + + Label biggestWinnerLabelText = new Label("Biggest Winner: "); + biggestWinnerLabel = new Label(); + biggestWinnerPriceLabel = new Label(); + biggestWinnerPriceLabel.setStyle("-fx-text-fill: green;"); + + winnerBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + winnerBox.setPadding(new Insets(8, 14, 8, 14)); + winnerBox.setAlignment(Pos.CENTER); + + winnerBox.getChildren().addAll(biggestWinnerLabelText, biggestWinnerLabel, biggestWinnerPriceLabel); Region spacer = new Region(); @@ -68,7 +111,7 @@ public FooterBar() { advanceWeekButton = new Button("Advance Week"); advanceWeekButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - this.getChildren().addAll(spacer, currentWeekBox, advanceWeekButton); + this.getChildren().addAll(loserBox, winnerBox, spacer, currentWeekBox, advanceWeekButton); } public void setExchange(Exchange exchange) { @@ -81,6 +124,26 @@ public void setExchange(Exchange exchange) { } }); + if (biggestLoserSubscription != null) { + biggestLoserSubscription.unsubscribe(); + } + + biggestLoserSubscription = exchange.getCurrentWeekProperty().subscribe(newWeek -> { + Stock loser = exchange.getLosers(1).getFirst(); + biggestLoserLabel.setText(loser.getSymbol() + " - " + loser.getCompany()); + biggestLoserPriceLabel.setText(" $" + loser.getLatestPriceChange()); + }); + + if (biggestWinnerSubscription != null) { + biggestWinnerSubscription.unsubscribe(); + } + + biggestWinnerSubscription = exchange.getCurrentWeekProperty().subscribe(newWeek -> { + Stock winner = exchange.getGainers(1).getFirst(); + biggestWinnerLabel.setText(winner.getSymbol() + " - " + winner.getCompany()); + biggestWinnerPriceLabel.setText(" $" + winner.getLatestPriceChange()); + }); + } /** diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java index d6f7415..d7ef3df 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java @@ -8,6 +8,7 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.util.Subscription; +import no.ntnu.gruppe53.model.Exchange; import no.ntnu.gruppe53.model.Player; import java.math.RoundingMode; @@ -36,7 +37,9 @@ public class NavigationBar extends HBox { private Subscription moneySubscription; + private Player player; + //Navigation Buttons @@ -46,6 +49,7 @@ public class NavigationBar extends HBox { private Label moneyLabel; + public NavigationBar() { this.setPadding(new Insets(15, 12, 15, 12)); @@ -66,6 +70,8 @@ public NavigationBar() { Region spacer = new Region(); HBox.setHgrow(spacer, Priority.ALWAYS); + + HBox playerBox = new HBox(); Label playerLabelText = new Label("Player: "); @@ -176,4 +182,6 @@ public void bindPlayer(Player player) { } } + + } diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index 4eae7af..471bd6d 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,3 +1,6 @@ no/ntnu/gruppe53/controller/ViewController.class no/ntnu/gruppe53/controller/GameController.class +no/ntnu/gruppe53/model/SaleFactory.class +no/ntnu/gruppe53/model/PurchaseFactory.class +no/ntnu/gruppe53/model/TransactionFactory.class no/ntnu/gruppe53/controller/StartViewController.class diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index 98c166d..c3b6ad4 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -8,13 +8,16 @@ /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseFactory.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/SaleFactory.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/Share.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/TransactionFactory.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/service/FormatBigDecimal.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java From 8cd9ef094147cb8f72d03a215ba21fb0a2ae95b8 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Mon, 25 May 2026 18:25:42 +0200 Subject: [PATCH 127/142] Added PlayerStatus update in navbar --- .../gruppe53/controller/GameController.java | 1 + .../no/ntnu/gruppe53/view/NavigationBar.java | 31 ++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index 6bc40d8..1147e1f 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -69,6 +69,7 @@ public void startNewGame() { transactionArchiveView.setPlayer(player); sharedNav.bindPlayer(player); + sharedNav.bindExchange(exchange); sharedFooter.setExchange(exchange); vm.addView("portfolio", portfolioView); diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java index d7ef3df..72cac0a 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java @@ -35,21 +35,26 @@ public class NavigationBar extends HBox { private final Button historyButton; private Subscription netWorthSubscription; private Subscription moneySubscription; + private Subscription currentWeekSubscription; + private Player player; + private Exchange exchange; //Navigation Buttons //Dynamic Labels private Label playerLabel; + private Label statusLabel; private Label netWorthLabel; private Label moneyLabel; + public NavigationBar() { this.setPadding(new Insets(15, 12, 15, 12)); @@ -89,6 +94,22 @@ public NavigationBar() { playerBox.getChildren().addAll(playerLabelText, playerLabel); + HBox statusBox = new HBox(); + + Label statusLabelText = new Label("Status: "); + statusLabel = new Label(); + + statusBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + statusBox.setPadding(new Insets(8, 14, 8, 14)); + statusBox.setAlignment(Pos.CENTER); + + statusBox.getChildren().addAll(statusLabelText, statusLabel); + HBox moneyBox = new HBox(); Label moneyLabelText = new Label("Money: "); @@ -119,7 +140,7 @@ public NavigationBar() { networthBox.setAlignment(Pos.CENTER); networthBox.getChildren().addAll(networthLabelText, netWorthLabel); - this.getChildren().addAll(marketButton, portfolioButton, historyButton, spacer, playerBox, moneyBox, networthBox); + this.getChildren().addAll(marketButton, portfolioButton, historyButton, spacer, playerBox, statusBox, moneyBox, networthBox); } /** * Exposes the marketButton to the GameController. @@ -183,5 +204,13 @@ public void bindPlayer(Player player) { } } + public void bindExchange(Exchange exchange) { + this.exchange = exchange; + + currentWeekSubscription = exchange.getCurrentWeekProperty().subscribe(newVal -> { + statusLabel.setText(player.getStatus(exchange)); + }); + } + } From 84ba81cddd89875d28d2eb20523695313fd745ae Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Mon, 25 May 2026 18:26:59 +0200 Subject: [PATCH 128/142] Some comments --- .../src/main/java/no/ntnu/gruppe53/view/NavigationBar.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java index 72cac0a..77dddfb 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java @@ -204,6 +204,12 @@ public void bindPlayer(Player player) { } } + /** + * Binds a subscription/observer to a {@link Exchange}. + *

It specifically listens to week changes so that it can update the player status.

+ * + * @param exchange the exchange object to be observed + */ public void bindExchange(Exchange exchange) { this.exchange = exchange; From 0c1b7b269f95dc019002d2584f36748775c60166 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Mon, 25 May 2026 19:48:23 +0200 Subject: [PATCH 129/142] Quick fix after error with merge --- .../java/no/ntnu/gruppe53/view/FooterBar.java | 2 +- millions/target/classes/i18n/lang.properties | 62 +++++++++++++++++++ .../target/classes/i18n/lang_en.properties | 62 +++++++++++++++++++ .../target/classes/i18n/lang_no.properties | 62 +++++++++++++++++++ .../compile/default-compile/createdFiles.lst | 9 ++- .../compile/default-compile/inputFiles.lst | 3 + 6 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 millions/target/classes/i18n/lang.properties create mode 100644 millions/target/classes/i18n/lang_en.properties create mode 100644 millions/target/classes/i18n/lang_no.properties diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java index 52c37e6..93af951 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java @@ -215,7 +215,7 @@ public void setExchange(Exchange exchange) { public void onAdvanceButtonClick(Runnable action) { advanceWeekButton.setOnAction(e -> action.run()); } -} + /** * Exoposes the LanguageOption ComboButton to the controller. diff --git a/millions/target/classes/i18n/lang.properties b/millions/target/classes/i18n/lang.properties new file mode 100644 index 0000000..ac7a7dd --- /dev/null +++ b/millions/target/classes/i18n/lang.properties @@ -0,0 +1,62 @@ +# Default file to use if there is an error with language locale + +# StartView +newGame = New Game +languageButtonLabel = Toggle Language +quitGame = Quit Game + +# PlayerNameChooserView +playerNameConfirmButton = Confirm +playerNameInfo = Please enter your player name: + +# PlayerStartingMoneyChooserView +playerStartingMoneyConfirmButton = Confirm +playerStartingMoneyInfoLabel = Select your starting money balance for trading: + +# CSVChooserView +csvHeader = CSV File Required +csvContent = Please select a .csv file of the stocks to be used for the exchange in the next window.\n\nIf none is selected (or if .csv file is empty/malformed), a default file will be used.\n\nThe file needs to be formated as: StockTicker,CorporationName,PriceTypeDouble.\n\nExample: NVDA,Nvidia,191.27\n\n'#' signifies comments, and along with lines not formatted correctly, will be ignored. +csvFileChooserTitle = Select CSV File +csvFileFilterLabel = CSV Files + +# NavigationBar +navMoneyLabelText = Money: +navNetWorthLabelText = Net Worth: +navPlayerLabelText = Player: +marketButton = Market +portfolioButton = Portfolio +historyButton = History + +# FooterBar +currentWeekLabelText = Current Week: +advanceWeekButton = Advance Week + +# MarketView +marketTitle = Stock Market +xAxisLabel = Week +yAxisLabel = Price +selectedStock = Selected Stock: +purchaseQuantity = Quantity: +purchaseGross = Gross: +purchaseCommission = Commission: +purchaseTotal = Total: +purchaseButton = Purchase +stockCellFormat=%s - %s | Price: $%s | Price change: %s | Highest price: %s | Lowest price: %s + +# PortfolioView +portfolioTitle = Portfolio +selectedShare = Selected Share: +portfolioGross = Gross: +portfolioCommission = Commission: +portfolioTax = Tax: +portfolioTotal = Total: +sellButton = Sell +portfolioShareQuantity = Qty: +portfolioBuyPrice = Buy price: +portfolioCurrentPrice = Current Price: + +# TransactionArchiveView +historyTitle=Transaction History +historyTypePurchase=Purchase +historyTypeSale=Sale +historyCellFormat=Week %d | %s | %s | Qty: %s | Total: $%s diff --git a/millions/target/classes/i18n/lang_en.properties b/millions/target/classes/i18n/lang_en.properties new file mode 100644 index 0000000..3e162bc --- /dev/null +++ b/millions/target/classes/i18n/lang_en.properties @@ -0,0 +1,62 @@ +# File for the english language + +# StartView +newGame = New Game +languageButtonLabel = Toggle Language +quitGame = Quit Game + +# PlayerNameChooserView +playerNameConfirmButton = Confirm +playerNameInfo = Please enter your player name: + +# PlayerStartingMoneyChooserView +playerStartingMoneyConfirmButton = Confirm +playerStartingMoneyInfoLabel = Select your starting money balance for trading: + +# CSVChooserView +csvHeader = CSV File Required +csvContent = Please select a .csv file of the stocks to be used for the exchange in the next window.\n\nIf none is selected (or if .csv file is empty/malformed), a default file will be used.\n\nThe file needs to be formated as: StockTicker,CorporationName,PriceTypeDouble.\n\nExample: NVDA,Nvidia,191.27\n\n'#' signifies comments, and along with lines not formatted correctly, will be ignored. +csvFileChooserTitle = Select CSV File +csvFileFilterLabel = CSV Files + +# NavigationBar +navMoneyLabelText = Money: +navNetWorthLabelText = Net Worth: +navPlayerLabelText = Player: +marketButton = Market +portfolioButton = Portfolio +historyButton = History + +# FooterBar +currentWeekLabelText = Current Week: +advanceWeekButton = Advance Week + +# MarketView +marketTitle = Stock Market +xAxisLabel = Week +yAxisLabel = Price +selectedStock = Selected Stock: +purchaseQuantity = Quantity: +purchaseGross = Gross: +purchaseCommission = Commission: +purchaseTotal = Total: +purchaseButton = Purchase +stockCellFormat=%s - %s | Price: $%s | Price change: %s | Highest price: %s | Lowest price: %s + +# PortfolioView +portfolioTitle = Portfolio +selectedShare = Selected Share: +portfolioGross = Gross: +portfolioCommission = Commission: +portfolioTax = Tax: +portfolioTotal = Total: +sellButton = Sell +portfolioShareQuantity = Qty: +portfolioBuyPrice = Buy price: +portfolioCurrentPrice = Current Price: + +# TransactionArchiveView +historyTitle=Transaction History +historyTypePurchase=Purchase +historyTypeSale=Sale +historyCellFormat=Week %d | %s | %s | Qty: %s | Total: $%s diff --git a/millions/target/classes/i18n/lang_no.properties b/millions/target/classes/i18n/lang_no.properties new file mode 100644 index 0000000..63261ec --- /dev/null +++ b/millions/target/classes/i18n/lang_no.properties @@ -0,0 +1,62 @@ +# File for the norwegian language + +# StartView +newGame = Nytt Spill +languageButtonLabel = Skift Språk +quitGame = Avslutt + +# PlayerNameChooserView +playerNameConfirmButton = Bekreft +playerNameInfo = Vennligst skriv spillernavnet ditt: + +# PlayerStartingMoneyChooserView +playerStartingMoneyConfirmButton = Bekreft +playerStartingMoneyInfoLabel = Velg startsaldo for aksjehandel: + +# CSVChooserView +csvHeader = CSV Fil Påkrevd +csvContent = Vennligst velg en .csv fil som inneholder aksjene som skal brukes på børsen i neste vindu. \n\nDersom ingen fil velges (eller .csv filen er tom eller har feil struktur), vil en standard fil bli brukt. \n\nFilen må formates slik: Aksjesymbol,FirmaNavn,PrisTypeDobbel. \n\nEksempel: NVDA,Nvidia,191.27\n\n'#' brukes for kommentarer, og som med feilformaterte linjer, vil slike linjer bli ignorert. +csvFileChooserTitle = Velg CSV fil +csvFileFilterLabel = CSV Filer + +# NavigationBar +navMoneyLabelText = Saldo: +navNetWorthLabelText = Nettoformue: +navPlayerLabelText = Spiller: +marketButton = Marked +portfolioButton = Portefølje +historyButton = Historikk + +# FooterBar +currentWeekLabelText = Gjeldende Uke: +advanceWeekButton = Neste Uke + +# MarketView +marketTitle = Aksjemarked +xAxisLabel = Uke +yAxisLabel = Pris +selectedStock = Valgt aksje: +purchaseQuantity = Antall: +purchaseGross = Brutto: +purchaseCommission = Kurtasje: +purchaseTotal = Total: +purchaseButton = Kjøp +stockCellFormat=%s - %s | Pris: $%s | Prisendring: %s | Høyeste pris: %s | Laveste pris: %s + +# PortfolioView +portfolioTitle = Portefølje +selectedShare = Valgt andel: +portfolioGross = Brutto: +portfolioCommission = Kurtasje: +portfolioTax = Skatt: +portfolioTotal = Total: +sellButton = Selg +portfolioShareQuantity = Ant: +portfolioBuyPrice = Kjøpspris: +portfolioCurrentPrice = Gjeldende pris: + +# TransactionArchiveView +historyTitle=Transaksjonshistorikk +historyTypePurchase=Kjøp +historyTypeSale=Salg +historyCellFormat=Uke %d | %s | %s | Ant: %s | Total: $%s \ No newline at end of file diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index 471bd6d..2f61388 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,6 +1,11 @@ no/ntnu/gruppe53/controller/ViewController.class no/ntnu/gruppe53/controller/GameController.class -no/ntnu/gruppe53/model/SaleFactory.class +no/ntnu/gruppe53/service/LanguageManager$LanguageManagerInner.class +no/ntnu/gruppe53/view/FooterBar$1.class no/ntnu/gruppe53/model/PurchaseFactory.class -no/ntnu/gruppe53/model/TransactionFactory.class no/ntnu/gruppe53/controller/StartViewController.class +no/ntnu/gruppe53/service/LanguageManager.class +no/ntnu/gruppe53/model/SaleFactory.class +no/ntnu/gruppe53/service/LanguageOption.class +no/ntnu/gruppe53/model/TransactionFactory.class +no/ntnu/gruppe53/service/LanguageRegistry.class diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index c3b6ad4..5cf1ce9 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -20,6 +20,9 @@ /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/model/TransactionFactory.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/service/FormatBigDecimal.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/service/LanguageOption.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/service/LanguageRegistry.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java From 19c4ccbdd8db0ac47a26ea28e34b7364173bf72c Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Mon, 25 May 2026 20:00:30 +0200 Subject: [PATCH 130/142] Added neccessary translations --- millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java | 6 ++++-- .../src/main/java/no/ntnu/gruppe53/view/NavigationBar.java | 3 ++- millions/src/main/resources/i18n/lang.properties | 3 +++ millions/src/main/resources/i18n/lang_en.properties | 3 +++ millions/src/main/resources/i18n/lang_no.properties | 3 +++ millions/target/classes/i18n/lang.properties | 3 +++ millions/target/classes/i18n/lang_en.properties | 3 +++ millions/target/classes/i18n/lang_no.properties | 3 +++ 8 files changed, 24 insertions(+), 3 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java index 93af951..8785d28 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java @@ -106,7 +106,8 @@ protected void updateItem(LanguageOption item, boolean empty) { HBox loserBox = new HBox(); - Label biggestLoserLabelText = new Label("Biggest Loser: "); + Label biggestLoserLabelText = new Label(); + biggestLoserLabelText.textProperty().bind(lm.bindString("biggestLoserLabelText")); biggestLoserLabel = new Label(); biggestLoserPriceLabel = new Label(); biggestLoserPriceLabel.setStyle("-fx-text-fill: red;"); @@ -125,7 +126,8 @@ protected void updateItem(LanguageOption item, boolean empty) { HBox winnerBox = new HBox(); - Label biggestWinnerLabelText = new Label("Biggest Winner: "); + Label biggestWinnerLabelText = new Label(); + biggestWinnerLabelText.textProperty().bind(lm.bindString("biggestWinnerLabelText")); biggestWinnerLabel = new Label(); biggestWinnerPriceLabel = new Label(); biggestWinnerPriceLabel.setStyle("-fx-text-fill: green;"); diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java index 16bbc9e..c44680b 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java @@ -104,7 +104,8 @@ public NavigationBar() { HBox statusBox = new HBox(); - Label statusLabelText = new Label("Status: "); + Label statusLabelText = new Label(); + statusLabelText.textProperty().bind(lm.bindString("statusLabelText")); statusLabel = new Label(); statusBox.setStyle("-fx-background-color: #ffe556;" + diff --git a/millions/src/main/resources/i18n/lang.properties b/millions/src/main/resources/i18n/lang.properties index ac7a7dd..61c65d9 100644 --- a/millions/src/main/resources/i18n/lang.properties +++ b/millions/src/main/resources/i18n/lang.properties @@ -26,10 +26,13 @@ navPlayerLabelText = Player: marketButton = Market portfolioButton = Portfolio historyButton = History +statusLabelText = Status: # FooterBar currentWeekLabelText = Current Week: advanceWeekButton = Advance Week +biggestLoserLabelText = Biggest Loser: +biggestWinnerLabelText = Biggest Winner: # MarketView marketTitle = Stock Market diff --git a/millions/src/main/resources/i18n/lang_en.properties b/millions/src/main/resources/i18n/lang_en.properties index 3e162bc..0c7bae8 100644 --- a/millions/src/main/resources/i18n/lang_en.properties +++ b/millions/src/main/resources/i18n/lang_en.properties @@ -26,10 +26,13 @@ navPlayerLabelText = Player: marketButton = Market portfolioButton = Portfolio historyButton = History +statusLabelText = Status: # FooterBar currentWeekLabelText = Current Week: advanceWeekButton = Advance Week +biggestLoserLabelText = Biggest Loser: +biggestWinnerLabelText = Biggest Winner: # MarketView marketTitle = Stock Market diff --git a/millions/src/main/resources/i18n/lang_no.properties b/millions/src/main/resources/i18n/lang_no.properties index 63261ec..b61e8e7 100644 --- a/millions/src/main/resources/i18n/lang_no.properties +++ b/millions/src/main/resources/i18n/lang_no.properties @@ -26,10 +26,13 @@ navPlayerLabelText = Spiller: marketButton = Marked portfolioButton = Portefølje historyButton = Historikk +statusLabelText = Nivå: # FooterBar currentWeekLabelText = Gjeldende Uke: advanceWeekButton = Neste Uke +biggestLoserLabelText = Største Taper: +biggestWinnerLabelText = Største Vinner: # MarketView marketTitle = Aksjemarked diff --git a/millions/target/classes/i18n/lang.properties b/millions/target/classes/i18n/lang.properties index ac7a7dd..61c65d9 100644 --- a/millions/target/classes/i18n/lang.properties +++ b/millions/target/classes/i18n/lang.properties @@ -26,10 +26,13 @@ navPlayerLabelText = Player: marketButton = Market portfolioButton = Portfolio historyButton = History +statusLabelText = Status: # FooterBar currentWeekLabelText = Current Week: advanceWeekButton = Advance Week +biggestLoserLabelText = Biggest Loser: +biggestWinnerLabelText = Biggest Winner: # MarketView marketTitle = Stock Market diff --git a/millions/target/classes/i18n/lang_en.properties b/millions/target/classes/i18n/lang_en.properties index 3e162bc..0c7bae8 100644 --- a/millions/target/classes/i18n/lang_en.properties +++ b/millions/target/classes/i18n/lang_en.properties @@ -26,10 +26,13 @@ navPlayerLabelText = Player: marketButton = Market portfolioButton = Portfolio historyButton = History +statusLabelText = Status: # FooterBar currentWeekLabelText = Current Week: advanceWeekButton = Advance Week +biggestLoserLabelText = Biggest Loser: +biggestWinnerLabelText = Biggest Winner: # MarketView marketTitle = Stock Market diff --git a/millions/target/classes/i18n/lang_no.properties b/millions/target/classes/i18n/lang_no.properties index 63261ec..b61e8e7 100644 --- a/millions/target/classes/i18n/lang_no.properties +++ b/millions/target/classes/i18n/lang_no.properties @@ -26,10 +26,13 @@ navPlayerLabelText = Spiller: marketButton = Marked portfolioButton = Portefølje historyButton = Historikk +statusLabelText = Nivå: # FooterBar currentWeekLabelText = Gjeldende Uke: advanceWeekButton = Neste Uke +biggestLoserLabelText = Største Taper: +biggestWinnerLabelText = Største Vinner: # MarketView marketTitle = Aksjemarked From 49ca49fc71af548f5267880f3ed48a363b0a61bd Mon Sep 17 00:00:00 2001 From: Roar Date: Mon, 25 May 2026 20:20:31 +0200 Subject: [PATCH 131/142] Updated Player Changed .addListener to .subscribe and made updateNetWorth() private. --- millions/src/main/java/no/ntnu/gruppe53/model/Player.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Player.java b/millions/src/main/java/no/ntnu/gruppe53/model/Player.java index 7c2610d..21a9e9e 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Player.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Player.java @@ -30,7 +30,7 @@ public class Player { /** * Creates a player with a given starting balance. - *

Adds a listener to the netWorth property to notify of changes in the player's net worth.

+ *

Subscribes to the netWorth property to notify of changes in the player's net worth.

* * @param name the player's name; must not be {@code null} or blank * @param startingMoney the initial monetary balance of the @@ -55,8 +55,7 @@ public Player(String name, BigDecimal startingMoney) { this.portfolio = new Portfolio(); this.transactionArchive = new TransactionArchive(); - portfolio.netWorthProperty().addListener( - (obs, oldVal, newVal) -> updateNetWorth()); + portfolio.netWorthProperty().subscribe(price -> updateNetWorth()); updateNetWorth(); } @@ -72,7 +71,7 @@ public ObjectProperty getNetWorthProperty() { /** * Mirrors the change in the player's net worth to the observable property. */ - public void updateNetWorth() { + private void updateNetWorth() { netWorthProperty.set(getNetWorth()); } From 01f29b1ca8920846b159a90d02cb968f83b382a8 Mon Sep 17 00:00:00 2001 From: Roar Date: Mon, 25 May 2026 20:20:56 +0200 Subject: [PATCH 132/142] Updated Portfolio Made updateNetWorth() private. --- millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java b/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java index 1164f6b..6a3c5f9 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java @@ -159,7 +159,7 @@ public ObjectProperty netWorthProperty() { /** * Mirrors the portfolio's net worth to the observable. */ - public void updateNetWorth() { + private void updateNetWorth() { netWorthProperty.set(getNetWorth()); } } \ No newline at end of file From ec56ff638cc00cc1f943b45105c4f780dd7febf0 Mon Sep 17 00:00:00 2001 From: Roar Date: Mon, 25 May 2026 21:57:11 +0200 Subject: [PATCH 133/142] Updated SaleCalculator Added getProfitProperty() and a observable of the profit of a sale. --- .../main/java/no/ntnu/gruppe53/model/SaleCalculator.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java index e96015a..c6afe07 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java @@ -21,6 +21,7 @@ public class SaleCalculator implements TransactionCalculator { private final ObjectProperty commissionProperty = new SimpleObjectProperty<>(); private final ObjectProperty taxProperty = new SimpleObjectProperty<>(); private final ObjectProperty totalProperty = new SimpleObjectProperty<>(); + private final ObjectProperty profitProperty = new SimpleObjectProperty<>(); /** * Sets the quantity, purchase and sales price based on the given share. @@ -36,6 +37,7 @@ public SaleCalculator(Share share) { this.commissionProperty.set(calculateCommission()); this.taxProperty.set(calculateTax()); this.totalProperty.set(calculateTotal()); + this.profitProperty.set(calculateTotal().subtract(purchasePrice.multiply(quantity))); } /** @@ -135,4 +137,10 @@ public ObjectProperty getTaxProperty() { public ObjectProperty getTotalProperty() { return totalProperty; } + + /** + * Returns an observable of the calculated profit + * @return an observable of the calculated profit + */ + public ObjectProperty getProfitProperty() {return profitProperty;} } \ No newline at end of file From 02799c7c444f76ecfed0a8433590d4559be43259 Mon Sep 17 00:00:00 2001 From: Roar Date: Mon, 25 May 2026 21:58:18 +0200 Subject: [PATCH 134/142] Updated PortfolioView Added a label, textfield and subscriptipn related to the calculated profit of a sale. --- .../no/ntnu/gruppe53/view/PortfolioView.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java index 290d375..1d05348 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java @@ -55,6 +55,7 @@ public class PortfolioView extends BorderPane { private TextField commission; private TextField tax; private TextField total; + private TextField profit; //PortfolioListView private ListView portfolioListView; @@ -66,6 +67,7 @@ public class PortfolioView extends BorderPane { private Subscription commissionSubscription; private Subscription taxSubscription; private Subscription totalSubscription; + private Subscription profitSubscription; /** * Builds the view components and places them in the center pane. @@ -204,7 +206,13 @@ protected void updateItem(Share item, boolean empty) { total.setEditable(false); total.setMaxWidth(200); - totalBox.getChildren().addAll(totalLabel, total); + Label profitLabel = new Label(); + profitLabel.textProperty().bind(lm.bindString("portfolioProfit")); + profit = new TextField(); + profit.setEditable(false); + profit.setMaxWidth(200); + + totalBox.getChildren().addAll(totalLabel, total, profitLabel, profit); sellButton = new Button(); sellButton.textProperty().bind(lm.bindString("sellButton")); @@ -323,6 +331,13 @@ public void setSaleCalculator(SaleCalculator saleCalculator) { total.setText(""); } }); + profitSubscription = saleCalculator.getProfitProperty().subscribe(newVal -> { + if (newVal != null) { + profit.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + profit.setText(""); + } + }); } From 7524fe829f28ad07bc5b8f66c8ebc9e33ef7876c Mon Sep 17 00:00:00 2001 From: Roar Date: Mon, 25 May 2026 21:59:07 +0200 Subject: [PATCH 135/142] Updated TransactionArchiveView Increased the information a string of transaction info gives the player. Now the player is more informed about what the transaction entailed. --- .../gruppe53/view/TransactionArchiveView.java | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java index fb5e11b..91e64b4 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java @@ -12,10 +12,7 @@ import java.math.RoundingMode; import javafx.util.Subscription; -import no.ntnu.gruppe53.model.Player; -import no.ntnu.gruppe53.model.Transaction; -import no.ntnu.gruppe53.model.Purchase; -import no.ntnu.gruppe53.model.Sale; +import no.ntnu.gruppe53.model.*; import no.ntnu.gruppe53.service.FormatBigDecimal; import no.ntnu.gruppe53.service.LanguageManager; @@ -96,17 +93,28 @@ protected void updateItem(Transaction item, boolean empty) { } else { Runnable updateTextAction = () -> { String typeStr; + BigDecimal price = BigDecimal.ZERO; + BigDecimal profit = BigDecimal.ZERO; // Changed to allow for more transaction types in the future if (item instanceof Purchase) { typeStr = lm.getString("historyTypePurchase"); - } else if (item instanceof Sale) { + price = item.getShare().getPurchasePrice(); + } else if (item instanceof Sale sale) { typeStr = lm.getString("historyTypeSale"); + price = item.getShare().getStock().getSalesPrice(); + if (sale.getCalculator() instanceof SaleCalculator saleCalc) { + profit = saleCalc.getProfitProperty().getValue(); + } } else { typeStr = "ERROR"; } BigDecimal quantity = item.getShare().getQuantity(); + BigDecimal gross = item.getCalculator().calculateGross(); + BigDecimal commission = item.getCalculator().calculateCommission(); + BigDecimal tax = item.getCalculator().calculateTax(); BigDecimal total = item.getCalculator().calculateTotal(); + String symbol = item.getShare().getStock().getSymbol(); String template = lm.getString("historyCellFormat"); @@ -115,8 +123,13 @@ protected void updateItem(Transaction item, boolean empty) { item.getWeek(), typeStr, symbol, + FormatBigDecimal.formatNumber(price), FormatBigDecimal.formatNumber(quantity), - FormatBigDecimal.formatNumber(total) + FormatBigDecimal.formatNumber(gross), + FormatBigDecimal.formatNumber(commission), + FormatBigDecimal.formatNumber(tax), + FormatBigDecimal.formatNumber(total), + FormatBigDecimal.formatNumber(profit) )); }; From 2a1b6c75eca0de7b37585fbae961fff6ac30870a Mon Sep 17 00:00:00 2001 From: Roar Date: Mon, 25 May 2026 22:01:31 +0200 Subject: [PATCH 136/142] Updated lang_en.properties Added strings for the new ui elements. --- millions/src/main/resources/i18n/lang_en.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/millions/src/main/resources/i18n/lang_en.properties b/millions/src/main/resources/i18n/lang_en.properties index 0c7bae8..4ac2825 100644 --- a/millions/src/main/resources/i18n/lang_en.properties +++ b/millions/src/main/resources/i18n/lang_en.properties @@ -53,6 +53,7 @@ portfolioGross = Gross: portfolioCommission = Commission: portfolioTax = Tax: portfolioTotal = Total: +portfolioProfit = Profit: sellButton = Sell portfolioShareQuantity = Qty: portfolioBuyPrice = Buy price: @@ -62,4 +63,4 @@ portfolioCurrentPrice = Current Price: historyTitle=Transaction History historyTypePurchase=Purchase historyTypeSale=Sale -historyCellFormat=Week %d | %s | %s | Qty: %s | Total: $%s +historyCellFormat=Week %d | %s | %s | Price: %s | Qty: %s | Gross: %s | Commission: %s | Tax: %s | Total: $%s | Profit: %s From 0b51d200480e97e974bf9725bfa3e54461a5b060 Mon Sep 17 00:00:00 2001 From: Roar Date: Mon, 25 May 2026 22:01:49 +0200 Subject: [PATCH 137/142] Updated lang_no.properties Added strings for the new ui elements. --- millions/src/main/resources/i18n/lang_no.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/millions/src/main/resources/i18n/lang_no.properties b/millions/src/main/resources/i18n/lang_no.properties index b61e8e7..faf680f 100644 --- a/millions/src/main/resources/i18n/lang_no.properties +++ b/millions/src/main/resources/i18n/lang_no.properties @@ -53,6 +53,7 @@ portfolioGross = Brutto: portfolioCommission = Kurtasje: portfolioTax = Skatt: portfolioTotal = Total: +portfolioProfit = Profitt: sellButton = Selg portfolioShareQuantity = Ant: portfolioBuyPrice = Kjøpspris: @@ -62,4 +63,4 @@ portfolioCurrentPrice = Gjeldende pris: historyTitle=Transaksjonshistorikk historyTypePurchase=Kjøp historyTypeSale=Salg -historyCellFormat=Uke %d | %s | %s | Ant: %s | Total: $%s \ No newline at end of file +historyCellFormat=Uke %d | %s | %s | Pris: %s | Ant: %s | Brutto: %s | Kurtasje: %s | Skatt: %s | Total: $%s | Profit: %s \ No newline at end of file From b92223cb30f2c0506e9a44ab61c8f112266eca94 Mon Sep 17 00:00:00 2001 From: Roar Date: Mon, 25 May 2026 22:02:14 +0200 Subject: [PATCH 138/142] Updated lang.properties Mirrored changes in lang_en. --- millions/src/main/resources/i18n/lang.properties | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/millions/src/main/resources/i18n/lang.properties b/millions/src/main/resources/i18n/lang.properties index 61c65d9..4ac2825 100644 --- a/millions/src/main/resources/i18n/lang.properties +++ b/millions/src/main/resources/i18n/lang.properties @@ -1,4 +1,4 @@ -# Default file to use if there is an error with language locale +# File for the english language # StartView newGame = New Game @@ -53,6 +53,7 @@ portfolioGross = Gross: portfolioCommission = Commission: portfolioTax = Tax: portfolioTotal = Total: +portfolioProfit = Profit: sellButton = Sell portfolioShareQuantity = Qty: portfolioBuyPrice = Buy price: @@ -62,4 +63,4 @@ portfolioCurrentPrice = Current Price: historyTitle=Transaction History historyTypePurchase=Purchase historyTypeSale=Sale -historyCellFormat=Week %d | %s | %s | Qty: %s | Total: $%s +historyCellFormat=Week %d | %s | %s | Price: %s | Qty: %s | Gross: %s | Commission: %s | Tax: %s | Total: $%s | Profit: %s From 9315f8728803e936251f5fd6e704ce19562cb2a8 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Tue, 26 May 2026 07:07:11 +0200 Subject: [PATCH 139/142] Added initial game end view and functionality --- .../gruppe53/controller/GameController.java | 52 +++++- .../java/no/ntnu/gruppe53/view/EndView.java | 153 ++++++++++++++++++ .../no/ntnu/gruppe53/view/NavigationBar.java | 15 +- .../src/main/resources/i18n/lang.properties | 9 +- .../main/resources/i18n/lang_en.properties | 3 +- .../main/resources/i18n/lang_no.properties | 1 + millions/target/classes/i18n/lang.properties | 9 +- .../target/classes/i18n/lang_en.properties | 3 +- .../target/classes/i18n/lang_no.properties | 1 + .../compile/default-compile/createdFiles.lst | 1 + .../compile/default-compile/inputFiles.lst | 1 + 11 files changed, 241 insertions(+), 7 deletions(-) create mode 100644 millions/src/main/java/no/ntnu/gruppe53/view/EndView.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index 5228eb9..9acbb78 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -1,9 +1,13 @@ package no.ntnu.gruppe53.controller; import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.List; +import javafx.application.Platform; import javafx.scene.control.Alert; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; import javafx.stage.Stage; import javafx.util.Subscription; import no.ntnu.gruppe53.model.*; @@ -26,6 +30,7 @@ public class GameController { private MarketView marketView; private PortfolioView portfolioView; private TransactionArchiveView transactionArchiveView; + private EndView endView; private PurchaseCalculator purchaseCalculator; private SaleCalculator saleCalculator; @@ -71,6 +76,8 @@ public void startNewGame() { transactionArchiveView = new TransactionArchiveView(); transactionArchiveView.setPlayer(player); + endView = new EndView(); + sharedNav.bindPlayer(player); sharedNav.bindExchange(exchange); sharedFooter.setExchange(exchange); @@ -78,6 +85,7 @@ public void startNewGame() { vm.addView("portfolio", portfolioView); vm.addView("market", marketView); vm.addView("history", transactionArchiveView); + vm.addView("endGame", endView); initialize(sharedNav, sharedFooter); @@ -97,7 +105,7 @@ public void showMarketView() { /** * Switches the view to the {@link PortfolioView}. *

Contains the data from a player's {@link Portfolio}.

- *

PortfolioView lets the player perform {@link Sale} transactions.

+ *

PortfolioView lets theEndView player perform {@link Sale} transactions.

*/ public void showPortfolio() { if (portfolioView != null) vm.switchView("portfolio"); @@ -123,6 +131,7 @@ public void initialize(NavigationBar nav, FooterBar footer) { nav.onMarketButtonClick(() -> vm.switchView("market")); nav.onPortfolioButtonClick(() -> vm.switchView("portfolio")); nav.onHistoryButtonClick(() -> vm.switchView("history")); + nav.onEndGameButtonClick(() -> endGame()); footer.onLanguageChange(selected -> { lm.setLocale(selected.locale()); @@ -168,7 +177,6 @@ public void initialize(NavigationBar nav, FooterBar footer) { Stock stock = marketView.getSelectedStock(); if (stock != null) purchaseStock(stock.getSymbol(), marketView.getQuantityPurchase()); }); - marketView.onQuantitySelect(() -> { if (marketView.getSelectedStock() != null) { marketView.setPurchaseCalculator(calculatePurchase(marketView.getSelectedStock(), @@ -198,7 +206,11 @@ public void initialize(NavigationBar nav, FooterBar footer) { portfolioView.onSellButton(this::handleSell); + endView.onQuitButton(Platform::exit); + endView.onNewGameButton(() -> { + startNewGame(); + }); } /** @@ -270,6 +282,42 @@ private SaleCalculator calculateSale(Share share) { } + private void endGame() { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.setTitle("End Game?"); + alert.setHeaderText("Are you sure?"); + alert.setContentText("Do you want to end the current game?"); + + ButtonType yesButton = new ButtonType("Yes", ButtonBar.ButtonData.YES); + ButtonType noButton = new ButtonType("No", ButtonBar.ButtonData.NO); + + + alert.getButtonTypes().setAll(yesButton, noButton); + + alert.showAndWait().ifPresent(button -> { + if (button == yesButton) { + sellPortfolio(); + vm.setGlobalPanels(null, null); + vm.switchView("endGame"); + + endView.getStatusLabel().setText(player.getStatus(exchange)); + endView.getMoneyLabel().setText(player.getMoney().setScale(2, RoundingMode.HALF_EVEN).toString()); + + + + } + }); + + } + + private void sellPortfolio() { + List sharesToSell = List.copyOf(player.getPortfolio().getShares()); + + for (Share share : sharesToSell) { + exchange.sell(share, player); + } + } + /** * Creates a default error alert to be used for notifying the player of errors. * @param title title of the error alert diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java b/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java new file mode 100644 index 0000000..3f373e1 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java @@ -0,0 +1,153 @@ +package no.ntnu.gruppe53.view; + +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.layout.*; +import no.ntnu.gruppe53.service.LanguageManager; + +public class EndView extends BorderPane { + + + private Button newGameButton; + private Button quitButton; + + private Label statusLabel; + private Label moneyLabel; + + + private final LanguageManager lm = LanguageManager.getInstance(); + + public EndView() { + this.setCenter(centerPane()); + } + + private BorderPane centerPane() { + BorderPane pane = new BorderPane(); + pane.setPadding(new Insets(15, 12, 15, 12)); + //pane.setSpacing(10); + //Blue Background color + pane.setStyle("-fx-background-color: #00bcf0;"); + + VBox centerPane = new VBox(); + + HBox statusLabelBox = new HBox(); + Label statusLabelText = new Label(); + statusLabelText.textProperty().bind(lm.bindString("statusLabelText")); + statusLabel = new Label(); + + statusLabelBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + statusLabelBox.setPadding(new Insets(8, 14, 8, 14)); + statusLabelBox.setAlignment(Pos.CENTER); + statusLabelBox.setMinHeight(65); + + statusLabelBox.getChildren().addAll(statusLabelText, statusLabel); + + HBox moneyLabelBox = new HBox(); + Label moneyLabelText = new Label(); + moneyLabelText.textProperty().bind(lm.bindString("navMoneyLabelText")); + moneyLabel = new Label(); + + moneyLabelBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + moneyLabelBox.setPadding(new Insets(8, 14, 8, 14)); + moneyLabelBox.setAlignment(Pos.CENTER); + moneyLabelBox.setMinHeight(65); + + moneyLabelBox.getChildren().addAll(moneyLabelText, moneyLabel); + + centerPane.setPadding(new Insets(15, 12, 15, 12)); + centerPane.setAlignment(Pos.CENTER); + centerPane.setSpacing(10); + centerPane.setMaxWidth(700); + centerPane.getChildren().addAll(statusLabelBox, moneyLabelBox); + + HBox quitButtonBox = new HBox(); + quitButton = new Button(); + quitButton.textProperty().bind(lm.bindString("quitButton")); + quitButton.setPrefSize(200, 50); + quitButton.setStyle("-fx-text-fill: #303539;" + + "-fx-font-size: 18px;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + + "-fx-background-radius: 8;" + ); + + + quitButtonBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + quitButtonBox.setPadding(new Insets(8, 14, 8, 14)); + quitButtonBox.setAlignment(Pos.CENTER); + + + quitButtonBox.getChildren().addAll(quitButton); + + HBox newGameButtonBox = new HBox(); + newGameButton = new Button(); + newGameButton.textProperty().bind(lm.bindString("newGameButton")); + newGameButton.setPrefSize(200, 50); + newGameButton.setStyle("-fx-text-fill: #303539;" + + "-fx-font-size: 18px;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + + "-fx-background-radius: 8;" + ); + newGameButtonBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + newGameButtonBox.setPadding(new Insets(8, 14, 8, 14)); + newGameButtonBox.setAlignment(Pos.CENTER); + newGameButtonBox.getChildren().add(newGameButton); + + HBox buttons = new HBox(quitButtonBox, newGameButtonBox); + + + + Region spacerBottomLeft = new Region(); + Region spacerBottomRight = new Region(); + + HBox.setHgrow(spacerBottomLeft, Priority.ALWAYS); + HBox.setHgrow(spacerBottomRight, Priority.ALWAYS); + + HBox bottomPane = new HBox(spacerBottomLeft, buttons, spacerBottomRight); + + pane.setCenter(centerPane); + pane.setBottom(bottomPane); + + return pane; + } + + + public void onQuitButton(Runnable action) { + quitButton.setOnAction(e -> action.run()); + } + + public void onNewGameButton(Runnable action) { + newGameButton.setOnAction(e -> action.run()); + } + + public Label getStatusLabel() { + return statusLabel; + } + + public Label getMoneyLabel() { + return moneyLabel; + } +} diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java index c44680b..2360452 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java @@ -36,6 +36,7 @@ public class NavigationBar extends HBox { private final Button marketButton; private final Button portfolioButton; private final Button historyButton; + private final Button endGameButton; private Subscription netWorthSubscription; private Subscription moneySubscription; private Subscription currentWeekSubscription; @@ -151,7 +152,11 @@ public NavigationBar() { networthBox.setAlignment(Pos.CENTER); networthBox.getChildren().addAll(networthLabelText, netWorthLabel); - this.getChildren().addAll(marketButton, portfolioButton, historyButton, spacer, playerBox, statusBox, moneyBox, networthBox); + endGameButton = new Button(); + endGameButton.textProperty().bind(lm.bindString("endGameButton")); + endGameButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + + this.getChildren().addAll(marketButton, portfolioButton, historyButton, spacer, playerBox, statusBox, moneyBox, networthBox, endGameButton); } /** * Exposes the marketButton to the GameController. @@ -180,6 +185,14 @@ public void onHistoryButtonClick(Runnable action) { historyButton.setOnAction(e -> action.run()); } + /** + * Exposes the endGameButton to the GameController. + * + * @param action the action to be run when clicking the button + */ + public void onEndGameButtonClick(Runnable action) { + endGameButton.setOnAction(e -> action.run()); + } /** diff --git a/millions/src/main/resources/i18n/lang.properties b/millions/src/main/resources/i18n/lang.properties index 61c65d9..8c215d4 100644 --- a/millions/src/main/resources/i18n/lang.properties +++ b/millions/src/main/resources/i18n/lang.properties @@ -26,7 +26,8 @@ navPlayerLabelText = Player: marketButton = Market portfolioButton = Portfolio historyButton = History -statusLabelText = Status: +statusLabelText = Level: +endGameButton = End Game # FooterBar currentWeekLabelText = Current Week: @@ -63,3 +64,9 @@ historyTitle=Transaction History historyTypePurchase=Purchase historyTypeSale=Sale historyCellFormat=Week %d | %s | %s | Qty: %s | Total: $%s + +# EndView +newGameButton = New Game +quitButton = Quit + + diff --git a/millions/src/main/resources/i18n/lang_en.properties b/millions/src/main/resources/i18n/lang_en.properties index 0c7bae8..2cf482e 100644 --- a/millions/src/main/resources/i18n/lang_en.properties +++ b/millions/src/main/resources/i18n/lang_en.properties @@ -26,7 +26,8 @@ navPlayerLabelText = Player: marketButton = Market portfolioButton = Portfolio historyButton = History -statusLabelText = Status: +statusLabelText = Level: +endGameButton = End Game # FooterBar currentWeekLabelText = Current Week: diff --git a/millions/src/main/resources/i18n/lang_no.properties b/millions/src/main/resources/i18n/lang_no.properties index b61e8e7..e393ba3 100644 --- a/millions/src/main/resources/i18n/lang_no.properties +++ b/millions/src/main/resources/i18n/lang_no.properties @@ -27,6 +27,7 @@ marketButton = Marked portfolioButton = Portefølje historyButton = Historikk statusLabelText = Nivå: +endGameButton = Avslutt Spill # FooterBar currentWeekLabelText = Gjeldende Uke: diff --git a/millions/target/classes/i18n/lang.properties b/millions/target/classes/i18n/lang.properties index 61c65d9..8c215d4 100644 --- a/millions/target/classes/i18n/lang.properties +++ b/millions/target/classes/i18n/lang.properties @@ -26,7 +26,8 @@ navPlayerLabelText = Player: marketButton = Market portfolioButton = Portfolio historyButton = History -statusLabelText = Status: +statusLabelText = Level: +endGameButton = End Game # FooterBar currentWeekLabelText = Current Week: @@ -63,3 +64,9 @@ historyTitle=Transaction History historyTypePurchase=Purchase historyTypeSale=Sale historyCellFormat=Week %d | %s | %s | Qty: %s | Total: $%s + +# EndView +newGameButton = New Game +quitButton = Quit + + diff --git a/millions/target/classes/i18n/lang_en.properties b/millions/target/classes/i18n/lang_en.properties index 0c7bae8..2cf482e 100644 --- a/millions/target/classes/i18n/lang_en.properties +++ b/millions/target/classes/i18n/lang_en.properties @@ -26,7 +26,8 @@ navPlayerLabelText = Player: marketButton = Market portfolioButton = Portfolio historyButton = History -statusLabelText = Status: +statusLabelText = Level: +endGameButton = End Game # FooterBar currentWeekLabelText = Current Week: diff --git a/millions/target/classes/i18n/lang_no.properties b/millions/target/classes/i18n/lang_no.properties index b61e8e7..e393ba3 100644 --- a/millions/target/classes/i18n/lang_no.properties +++ b/millions/target/classes/i18n/lang_no.properties @@ -27,6 +27,7 @@ marketButton = Marked portfolioButton = Portefølje historyButton = Historikk statusLabelText = Nivå: +endGameButton = Avslutt Spill # FooterBar currentWeekLabelText = Gjeldende Uke: diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index 2f61388..b0b4c17 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -7,5 +7,6 @@ no/ntnu/gruppe53/controller/StartViewController.class no/ntnu/gruppe53/service/LanguageManager.class no/ntnu/gruppe53/model/SaleFactory.class no/ntnu/gruppe53/service/LanguageOption.class +no/ntnu/gruppe53/view/EndView.class no/ntnu/gruppe53/model/TransactionFactory.class no/ntnu/gruppe53/service/LanguageRegistry.class diff --git a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index 5cf1ce9..5fa44b0 100644 --- a/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/millions/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -25,6 +25,7 @@ /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/service/LanguageRegistry.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java +/home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java /home/alodgaard/Programmering/Skole/Prog 2/Millions/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java From b50ac32134b79e22bec5a30b45ae3b0d11376f88 Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Tue, 26 May 2026 07:45:20 +0200 Subject: [PATCH 140/142] Added localization --- .../gruppe53/controller/GameController.java | 15 +++-- .../java/no/ntnu/gruppe53/view/EndView.java | 58 +++++++++++++++++-- .../src/main/resources/i18n/lang.properties | 13 ++++- .../main/resources/i18n/lang_en.properties | 12 ++++ .../main/resources/i18n/lang_no.properties | 14 ++++- millions/target/classes/i18n/lang.properties | 13 ++++- .../target/classes/i18n/lang_en.properties | 12 ++++ .../target/classes/i18n/lang_no.properties | 14 ++++- 8 files changed, 136 insertions(+), 15 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index 9acbb78..d8ea407 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -282,14 +282,19 @@ private SaleCalculator calculateSale(Share share) { } + + /** + * Creates an alert where the Player can confirm if they want to end the game. + * If yes, then sells entire portfolio and displays the EndView. + */ private void endGame() { Alert alert = new Alert(Alert.AlertType.CONFIRMATION); - alert.setTitle("End Game?"); - alert.setHeaderText("Are you sure?"); - alert.setContentText("Do you want to end the current game?"); + alert.titleProperty().bind(lm.bindString("endGameAlertTitle")); + alert.headerTextProperty().bind(lm.bindString("endGameAlertHeaderText")); + alert.contentTextProperty().bind(lm.bindString("endGameAlertContentText")); - ButtonType yesButton = new ButtonType("Yes", ButtonBar.ButtonData.YES); - ButtonType noButton = new ButtonType("No", ButtonBar.ButtonData.NO); + ButtonType yesButton = new ButtonType(lm.getString("yes"), ButtonBar.ButtonData.YES); + ButtonType noButton = new ButtonType(lm.getString("no"), ButtonBar.ButtonData.NO); alert.getButtonTypes().setAll(yesButton, noButton); diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java b/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java index 3f373e1..9ac97de 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java @@ -5,8 +5,15 @@ import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.*; +import javafx.scene.text.TextAlignment; import no.ntnu.gruppe53.service.LanguageManager; + +/** + * Represents the End Game screen, showing the Level the Player reached, + * money earned in total after selling portfolio and gives the option + * to quit the program or start a new game. + */ public class EndView extends BorderPane { @@ -32,10 +39,30 @@ private BorderPane centerPane() { VBox centerPane = new VBox(); + HBox congratsBox = new HBox(); + Label congratsLabel = new Label(); + congratsLabel.textProperty().bind(lm.bindString("congratsLabel")); + congratsLabel.setStyle("-fx-font-size: 18"); + congratsLabel.setTextAlignment(TextAlignment.CENTER); + + congratsBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + congratsBox.setPadding(new Insets(8, 14, 8, 14)); + congratsBox.setAlignment(Pos.CENTER); + congratsBox.setMinHeight(65); + + congratsBox.getChildren().add(congratsLabel); + HBox statusLabelBox = new HBox(); Label statusLabelText = new Label(); statusLabelText.textProperty().bind(lm.bindString("statusLabelText")); + statusLabelText.setStyle("-fx-font-size: 18"); statusLabel = new Label(); + statusLabel.setStyle("-fx-font-size: 18"); statusLabelBox.setStyle("-fx-background-color: #ffe556;" + "-fx-text-fill: #303539;" + @@ -52,7 +79,9 @@ private BorderPane centerPane() { HBox moneyLabelBox = new HBox(); Label moneyLabelText = new Label(); moneyLabelText.textProperty().bind(lm.bindString("navMoneyLabelText")); + moneyLabelText.setStyle("-fx-font-size: 18"); moneyLabel = new Label(); + moneyLabel.setStyle("-fx-font-size: 18"); moneyLabelBox.setStyle("-fx-background-color: #ffe556;" + "-fx-text-fill: #303539;" + @@ -70,11 +99,13 @@ private BorderPane centerPane() { centerPane.setAlignment(Pos.CENTER); centerPane.setSpacing(10); centerPane.setMaxWidth(700); - centerPane.getChildren().addAll(statusLabelBox, moneyLabelBox); + + + centerPane.getChildren().addAll(congratsBox, statusLabelBox, moneyLabelBox); HBox quitButtonBox = new HBox(); quitButton = new Button(); - quitButton.textProperty().bind(lm.bindString("quitButton")); + quitButton.textProperty().bind(lm.bindString("quitGame")); quitButton.setPrefSize(200, 50); quitButton.setStyle("-fx-text-fill: #303539;" + "-fx-font-size: 18px;" + @@ -98,7 +129,7 @@ private BorderPane centerPane() { HBox newGameButtonBox = new HBox(); newGameButton = new Button(); - newGameButton.textProperty().bind(lm.bindString("newGameButton")); + newGameButton.textProperty().bind(lm.bindString("newGame")); newGameButton.setPrefSize(200, 50); newGameButton.setStyle("-fx-text-fill: #303539;" + "-fx-font-size: 18px;" + @@ -134,19 +165,38 @@ private BorderPane centerPane() { return pane; } - + /** + * Sets the action to be run when clicking the quit button + * + * @param action the action to run when clicking the quit button + */ public void onQuitButton(Runnable action) { quitButton.setOnAction(e -> action.run()); } + /** + * Sets the action to be run when clicking the New Game button + * + * @param action the action to run when clicking the New Game button + */ public void onNewGameButton(Runnable action) { newGameButton.setOnAction(e -> action.run()); } + /** + * Returns the statusLabel. + * + * @return the statusLabel + */ public Label getStatusLabel() { return statusLabel; } + /** + * Returns the moneyLabel. + * + * @return the moneyLabel + */ public Label getMoneyLabel() { return moneyLabel; } diff --git a/millions/src/main/resources/i18n/lang.properties b/millions/src/main/resources/i18n/lang.properties index 8c215d4..01a4ec3 100644 --- a/millions/src/main/resources/i18n/lang.properties +++ b/millions/src/main/resources/i18n/lang.properties @@ -1,5 +1,9 @@ # Default file to use if there is an error with language locale +# General +yes = Yes +no = No + # StartView newGame = New Game languageButtonLabel = Toggle Language @@ -66,7 +70,12 @@ historyTypeSale=Sale historyCellFormat=Week %d | %s | %s | Qty: %s | Total: $%s # EndView -newGameButton = New Game -quitButton = Quit +congratsLabel = Thank you for playing Millions!\nBelow you can see what level you reached and the amount of\nmoney you ended up with!\nYou can also choose to start a new game. + +# EndGameAlert +endGameAlertTitle = Ending Game +endGameAlertHeaderText=Warning, this ends the game! +endGameAlertContentText = Are your sure you want to end the game? + diff --git a/millions/src/main/resources/i18n/lang_en.properties b/millions/src/main/resources/i18n/lang_en.properties index 2cf482e..3cd4ac9 100644 --- a/millions/src/main/resources/i18n/lang_en.properties +++ b/millions/src/main/resources/i18n/lang_en.properties @@ -1,5 +1,9 @@ # File for the english language +# General +yes = Yes +no = No + # StartView newGame = New Game languageButtonLabel = Toggle Language @@ -64,3 +68,11 @@ historyTitle=Transaction History historyTypePurchase=Purchase historyTypeSale=Sale historyCellFormat=Week %d | %s | %s | Qty: %s | Total: $%s + +# EndView +congratsLabel = Thank you for playing Millions!\nBelow you can see what level you reachedand the amount of\nmoney you ended up with!\nYou can also choose to start a new game. + +# EndGameAlert +endGameAlertTitle = Ending Game +endGameAlertHeaderText=Warning, this ends the game! +endGameAlertContentText = Are your sure you want to end the game? diff --git a/millions/src/main/resources/i18n/lang_no.properties b/millions/src/main/resources/i18n/lang_no.properties index e393ba3..c8bdec5 100644 --- a/millions/src/main/resources/i18n/lang_no.properties +++ b/millions/src/main/resources/i18n/lang_no.properties @@ -1,5 +1,9 @@ # File for the norwegian language +# General +yes = Ja +no = Nei + # StartView newGame = Nytt Spill languageButtonLabel = Skift Språk @@ -63,4 +67,12 @@ portfolioCurrentPrice = Gjeldende pris: historyTitle=Transaksjonshistorikk historyTypePurchase=Kjøp historyTypeSale=Salg -historyCellFormat=Uke %d | %s | %s | Ant: %s | Total: $%s \ No newline at end of file +historyCellFormat=Uke %d | %s | %s | Ant: %s | Total: $%s + +# EndView +congratsLabel = Takk for at du spilte Millions!\nNedenfor kan du se hvilket nivå du nådde opp til\nog din endelige saldo!\nDu kan også velge å starte et nytt spill. + +# EndGameAlert +endGameAlertTitle = Avslutter Spill +endGameAlertHeaderText= Advarsel, dette vil avslutte spillet! +endGameAlertContentText = Er du sikker på at du vil avslutte spillet? \ No newline at end of file diff --git a/millions/target/classes/i18n/lang.properties b/millions/target/classes/i18n/lang.properties index 8c215d4..01a4ec3 100644 --- a/millions/target/classes/i18n/lang.properties +++ b/millions/target/classes/i18n/lang.properties @@ -1,5 +1,9 @@ # Default file to use if there is an error with language locale +# General +yes = Yes +no = No + # StartView newGame = New Game languageButtonLabel = Toggle Language @@ -66,7 +70,12 @@ historyTypeSale=Sale historyCellFormat=Week %d | %s | %s | Qty: %s | Total: $%s # EndView -newGameButton = New Game -quitButton = Quit +congratsLabel = Thank you for playing Millions!\nBelow you can see what level you reached and the amount of\nmoney you ended up with!\nYou can also choose to start a new game. + +# EndGameAlert +endGameAlertTitle = Ending Game +endGameAlertHeaderText=Warning, this ends the game! +endGameAlertContentText = Are your sure you want to end the game? + diff --git a/millions/target/classes/i18n/lang_en.properties b/millions/target/classes/i18n/lang_en.properties index 2cf482e..3cd4ac9 100644 --- a/millions/target/classes/i18n/lang_en.properties +++ b/millions/target/classes/i18n/lang_en.properties @@ -1,5 +1,9 @@ # File for the english language +# General +yes = Yes +no = No + # StartView newGame = New Game languageButtonLabel = Toggle Language @@ -64,3 +68,11 @@ historyTitle=Transaction History historyTypePurchase=Purchase historyTypeSale=Sale historyCellFormat=Week %d | %s | %s | Qty: %s | Total: $%s + +# EndView +congratsLabel = Thank you for playing Millions!\nBelow you can see what level you reachedand the amount of\nmoney you ended up with!\nYou can also choose to start a new game. + +# EndGameAlert +endGameAlertTitle = Ending Game +endGameAlertHeaderText=Warning, this ends the game! +endGameAlertContentText = Are your sure you want to end the game? diff --git a/millions/target/classes/i18n/lang_no.properties b/millions/target/classes/i18n/lang_no.properties index e393ba3..c8bdec5 100644 --- a/millions/target/classes/i18n/lang_no.properties +++ b/millions/target/classes/i18n/lang_no.properties @@ -1,5 +1,9 @@ # File for the norwegian language +# General +yes = Ja +no = Nei + # StartView newGame = Nytt Spill languageButtonLabel = Skift Språk @@ -63,4 +67,12 @@ portfolioCurrentPrice = Gjeldende pris: historyTitle=Transaksjonshistorikk historyTypePurchase=Kjøp historyTypeSale=Salg -historyCellFormat=Uke %d | %s | %s | Ant: %s | Total: $%s \ No newline at end of file +historyCellFormat=Uke %d | %s | %s | Ant: %s | Total: $%s + +# EndView +congratsLabel = Takk for at du spilte Millions!\nNedenfor kan du se hvilket nivå du nådde opp til\nog din endelige saldo!\nDu kan også velge å starte et nytt spill. + +# EndGameAlert +endGameAlertTitle = Avslutter Spill +endGameAlertHeaderText= Advarsel, dette vil avslutte spillet! +endGameAlertContentText = Er du sikker på at du vil avslutte spillet? \ No newline at end of file From ac1c1fd510071527c5757bcca75b2f645f1308cc Mon Sep 17 00:00:00 2001 From: Arelodgaard Date: Tue, 26 May 2026 08:36:41 +0200 Subject: [PATCH 141/142] added search functionality to market, portfolio, and history --- .../no/ntnu/gruppe53/view/MarketView.java | 45 +++++++++++++++--- .../no/ntnu/gruppe53/view/PortfolioView.java | 40 ++++++++++++---- .../gruppe53/view/TransactionArchiveView.java | 46 ++++++++++++++++--- .../src/main/resources/i18n/lang.properties | 1 + .../main/resources/i18n/lang_en.properties | 1 + .../main/resources/i18n/lang_no.properties | 1 + millions/target/classes/i18n/lang.properties | 6 ++- .../target/classes/i18n/lang_en.properties | 4 +- .../target/classes/i18n/lang_no.properties | 5 +- 9 files changed, 124 insertions(+), 25 deletions(-) diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java index bab3197..9dc83fe 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java @@ -3,15 +3,13 @@ import java.math.RoundingMode; import java.util.function.Consumer; +import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.SortedList; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.*; import javafx.scene.chart.XYChart; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.VBox; +import javafx.scene.layout.*; import javafx.scene.chart.LineChart; import javafx.util.Subscription; import no.ntnu.gruppe53.model.*; @@ -42,9 +40,11 @@ public class MarketView extends BorderPane { public TextField gross; public TextField commission; public TextField total; + private TextField searchField; //StockListView private ListView stocksListView; + private FilteredList filteredStocksList; //Input private Spinner quantitySpinner; @@ -154,6 +154,21 @@ protected void updateItem(Stock stock, boolean empty) { } }); + HBox searchBox = new HBox(); + searchField = new TextField(); + searchField.promptTextProperty().bind(lm.bindString("search")); + searchBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + searchBox.setPadding(new Insets(8, 14, 8, 14)); + searchBox.setAlignment(Pos.CENTER_LEFT); + searchBox.setMaxWidth(Region.USE_PREF_SIZE); + searchBox.getChildren().addAll(searchField); + // Container for list of stocks HBox stockListBox = new HBox(stocksListView); @@ -254,7 +269,7 @@ protected void updateItem(Stock stock, boolean empty) { - vBox.getChildren().addAll(titleBox, + vBox.getChildren().addAll(titleBox, searchBox, stockBox, purchaseBox); @@ -326,18 +341,34 @@ public void onQuantitySelect(Runnable action) { /** * Sets the exchange to use for the view. - *

Uses a {@link SortedList} to sort the stocks in the exchange alphabetically + *

Uses first a {@link FilteredList} to account for what the user has searched for.

+ *

Then utilises a {@link SortedList} to sort the stocks in the exchange alphabetically * on their symbol/ticker.

* * @param exchange the exchange to be used for the view */ public void setExchange(Exchange exchange) { this.exchange = exchange; + + if (exchange != null) { - SortedList sortedStocks = new SortedList<>(exchange.getObservableStocks()); + filteredStocksList = new FilteredList<>(exchange.getObservableStocks()); + + SortedList sortedStocks = new SortedList<>(filteredStocksList); sortedStocks.setComparator((s1, s2) -> s1.getSymbol().compareToIgnoreCase(s2.getSymbol())); + stocksListView.setItems(sortedStocks); + + searchField.textProperty().addListener((observable, oldText, newText) -> { + String search = newText == null ? "" : newText.toLowerCase(); + + filteredStocksList.setPredicate(stock -> search.isBlank() + || stock.getSymbol().toLowerCase().contains(search) + || stock.getCompany().toLowerCase().contains(search) + ); + }); } + } /** diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java index 1d05348..4c55fe5 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java @@ -5,6 +5,7 @@ import java.util.function.Consumer; import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; import javafx.geometry.Insets; import javafx.scene.control.Button; import javafx.scene.control.Label; @@ -12,10 +13,7 @@ import javafx.scene.control.ListView; import javafx.geometry.Pos; import javafx.scene.control.*; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.VBox; +import javafx.scene.layout.*; import javafx.scene.text.Text; import javafx.scene.text.TextFlow; import javafx.util.Subscription; @@ -56,9 +54,11 @@ public class PortfolioView extends BorderPane { private TextField tax; private TextField total; private TextField profit; + private TextField searchField; //PortfolioListView private ListView portfolioListView; + private FilteredList filteredPortfolioList; private Player player; private SaleCalculator saleCalculator; @@ -152,6 +152,21 @@ protected void updateItem(Share item, boolean empty) { } }); + HBox searchBox = new HBox(); + searchField = new TextField(); + searchField.promptTextProperty().bind(lm.bindString("search")); + searchBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + searchBox.setPadding(new Insets(8, 14, 8, 14)); + searchBox.setAlignment(Pos.CENTER_LEFT); + searchBox.setMaxWidth(Region.USE_PREF_SIZE); + searchBox.getChildren().addAll(searchField); + VBox portfolioListBox = new VBox(portfolioListView); portfolioListBox.setStyle("-fx-background-color: #ffe556;" + "-fx-text-fill: #303539;" + @@ -231,7 +246,7 @@ protected void updateItem(Share item, boolean empty) { saleBox.getChildren().addAll(selectedShareBox, calculationBox, totalBox, sellButton); - vBox.getChildren().addAll(titleBox, + vBox.getChildren().addAll(titleBox, searchBox, portfolioListBox, saleBox); return vBox; @@ -265,7 +280,7 @@ public Share getSelectedShare() { /** * Sets the player to be observed. *

The values observed are the players portfolio and name.

- *

Uses an {@link ObservableList} to observe the shares and + *

Uses a {@link FilteredList} to get and filter the shares and * binds it to the portfolioListView.

* * @param player the player to be observed @@ -274,10 +289,19 @@ public void setPlayer(Player player) { this.player = player; if (player != null && player.getPortfolio() != null) { - ObservableList observableShares = player.getPortfolio().getShares(); - portfolioListView.setItems(observableShares); + filteredPortfolioList = new FilteredList<>(player.getPortfolio().getShares()); + + portfolioListView.setItems(filteredPortfolioList); + + searchField.textProperty().addListener((observable, oldText, newText) -> { + String search = newText == null ? "" : newText.toLowerCase(); + filteredPortfolioList.setPredicate(share -> search.isBlank() + || share.getStock().getSymbol().toLowerCase().contains(search) + || share.getStock().getCompany().toLowerCase().contains(search) + ); + }); } else { diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java index 91e64b4..4c949a5 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java @@ -1,13 +1,14 @@ package no.ntnu.gruppe53.view; +import javafx.collections.transformation.FilteredList; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.Priority; -import javafx.scene.layout.VBox; +import javafx.scene.control.TextField; +import javafx.scene.layout.*; + import java.math.BigDecimal; import java.math.RoundingMode; @@ -31,8 +32,12 @@ public class TransactionArchiveView extends BorderPane { private final LanguageManager lm = LanguageManager.getInstance(); private ListView historyListView; + private FilteredList filteredTransactionList; + private Label titleLabel; + private TextField searchField; + /** * Builds the view components and places them in the center pane. */ @@ -146,6 +151,20 @@ protected void updateItem(Transaction item, boolean empty) { } }); + HBox searchBox = new HBox(); + searchField = new TextField(); + searchField.promptTextProperty().bind(lm.bindString("search")); + searchBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + searchBox.setPadding(new Insets(8, 14, 8, 14)); + searchBox.setAlignment(Pos.CENTER_LEFT); + searchBox.setMaxWidth(Region.USE_PREF_SIZE); + searchBox.getChildren().addAll(searchField); VBox historyListBox = new VBox(historyListView); historyListBox.setStyle("-fx-background-color: #ffe556;" + @@ -162,18 +181,33 @@ protected void updateItem(Transaction item, boolean empty) { VBox.setVgrow(historyListView, Priority.ALWAYS); VBox.setVgrow(historyListBox, Priority.ALWAYS); - vBox.getChildren().addAll(titleBox, historyListBox); + vBox.getChildren().addAll(titleBox, searchBox, historyListBox); return vBox; } /** * Sets the player to be observed. + *

And puts the {@link TransactionArchive} into a {@link FilteredList} for searching.

* * @param player the player to be observed */ public void setPlayer(Player player) { - if (player != null && player.getTransactionArchive() != null) { - historyListView.setItems(player.getTransactionArchive().getObservableTransactions()); + if (player != null && player.getTransactionArchive().getObservableTransactions() != null) { + + + filteredTransactionList = new FilteredList<>(player.getTransactionArchive().getObservableTransactions()); + + historyListView.setItems(filteredTransactionList); + + searchField.textProperty().addListener((observable, oldText, newText) -> { + String search = newText == null ? "" : newText.toLowerCase(); + + filteredTransactionList.setPredicate( transaction -> search.isBlank() + || transaction.getShare().getStock().getSymbol().toLowerCase().contains(search) + || transaction.getShare().getStock().getCompany().toLowerCase().contains(search) + ); + }); + } else { historyListView.setItems(null); } diff --git a/millions/src/main/resources/i18n/lang.properties b/millions/src/main/resources/i18n/lang.properties index 64cdd1f..87c134e 100644 --- a/millions/src/main/resources/i18n/lang.properties +++ b/millions/src/main/resources/i18n/lang.properties @@ -3,6 +3,7 @@ # General yes = Yes no = No +search = Search: # StartView newGame = New Game diff --git a/millions/src/main/resources/i18n/lang_en.properties b/millions/src/main/resources/i18n/lang_en.properties index 7ed958b..1c8ef53 100644 --- a/millions/src/main/resources/i18n/lang_en.properties +++ b/millions/src/main/resources/i18n/lang_en.properties @@ -3,6 +3,7 @@ # General yes = Yes no = No +search = Search: # StartView newGame = New Game diff --git a/millions/src/main/resources/i18n/lang_no.properties b/millions/src/main/resources/i18n/lang_no.properties index 00735e7..b5deee0 100644 --- a/millions/src/main/resources/i18n/lang_no.properties +++ b/millions/src/main/resources/i18n/lang_no.properties @@ -3,6 +3,7 @@ # General yes = Ja no = Nei +search = Søk: # StartView newGame = Nytt Spill diff --git a/millions/target/classes/i18n/lang.properties b/millions/target/classes/i18n/lang.properties index 01a4ec3..87c134e 100644 --- a/millions/target/classes/i18n/lang.properties +++ b/millions/target/classes/i18n/lang.properties @@ -1,8 +1,9 @@ -# Default file to use if there is an error with language locale +# File for the english language # General yes = Yes no = No +search = Search: # StartView newGame = New Game @@ -58,6 +59,7 @@ portfolioGross = Gross: portfolioCommission = Commission: portfolioTax = Tax: portfolioTotal = Total: +portfolioProfit = Profit: sellButton = Sell portfolioShareQuantity = Qty: portfolioBuyPrice = Buy price: @@ -67,7 +69,7 @@ portfolioCurrentPrice = Current Price: historyTitle=Transaction History historyTypePurchase=Purchase historyTypeSale=Sale -historyCellFormat=Week %d | %s | %s | Qty: %s | Total: $%s +historyCellFormat=Week %d | %s | %s | Price: %s | Qty: %s | Gross: %s | Commission: %s | Tax: %s | Total: $%s | Profit: %s # EndView congratsLabel = Thank you for playing Millions!\nBelow you can see what level you reached and the amount of\nmoney you ended up with!\nYou can also choose to start a new game. diff --git a/millions/target/classes/i18n/lang_en.properties b/millions/target/classes/i18n/lang_en.properties index 3cd4ac9..1c8ef53 100644 --- a/millions/target/classes/i18n/lang_en.properties +++ b/millions/target/classes/i18n/lang_en.properties @@ -3,6 +3,7 @@ # General yes = Yes no = No +search = Search: # StartView newGame = New Game @@ -58,6 +59,7 @@ portfolioGross = Gross: portfolioCommission = Commission: portfolioTax = Tax: portfolioTotal = Total: +portfolioProfit = Profit: sellButton = Sell portfolioShareQuantity = Qty: portfolioBuyPrice = Buy price: @@ -67,7 +69,7 @@ portfolioCurrentPrice = Current Price: historyTitle=Transaction History historyTypePurchase=Purchase historyTypeSale=Sale -historyCellFormat=Week %d | %s | %s | Qty: %s | Total: $%s +historyCellFormat=Week %d | %s | %s | Price: %s | Qty: %s | Gross: %s | Commission: %s | Tax: %s | Total: $%s | Profit: %s # EndView congratsLabel = Thank you for playing Millions!\nBelow you can see what level you reachedand the amount of\nmoney you ended up with!\nYou can also choose to start a new game. diff --git a/millions/target/classes/i18n/lang_no.properties b/millions/target/classes/i18n/lang_no.properties index c8bdec5..b5deee0 100644 --- a/millions/target/classes/i18n/lang_no.properties +++ b/millions/target/classes/i18n/lang_no.properties @@ -3,6 +3,7 @@ # General yes = Ja no = Nei +search = Søk: # StartView newGame = Nytt Spill @@ -58,6 +59,7 @@ portfolioGross = Brutto: portfolioCommission = Kurtasje: portfolioTax = Skatt: portfolioTotal = Total: +portfolioProfit = Profitt: sellButton = Selg portfolioShareQuantity = Ant: portfolioBuyPrice = Kjøpspris: @@ -67,7 +69,8 @@ portfolioCurrentPrice = Gjeldende pris: historyTitle=Transaksjonshistorikk historyTypePurchase=Kjøp historyTypeSale=Salg -historyCellFormat=Uke %d | %s | %s | Ant: %s | Total: $%s +historyCellFormat=Uke %d | %s | %s | Pris: %s | Ant: %s | Brutto: %s | Kurtasje: %s | Skatt: %s | Total: $%s | Profit: %s + # EndView congratsLabel = Takk for at du spilte Millions!\nNedenfor kan du se hvilket nivå du nådde opp til\nog din endelige saldo!\nDu kan også velge å starte et nytt spill. From 517518d9049188b956bd6cbc37d039e3c8b3d2f5 Mon Sep 17 00:00:00 2001 From: Roar Date: Wed, 27 May 2026 04:40:34 +0200 Subject: [PATCH 142/142] Updated all main java files Fixed google checkstyle, missing javadoc, and minor warnings. --- .../src/main/java/no/ntnu/gruppe53/App.java | 32 +- .../gruppe53/controller/GameController.java | 615 +++++++------- .../controller/GameSetupController.java | 165 ++-- .../controller/StartViewController.java | 74 +- .../gruppe53/controller/ViewController.java | 116 +-- .../java/no/ntnu/gruppe53/model/Exchange.java | 460 +++++----- .../java/no/ntnu/gruppe53/model/Player.java | 327 ++++---- .../no/ntnu/gruppe53/model/Portfolio.java | 253 +++--- .../java/no/ntnu/gruppe53/model/Purchase.java | 87 +- .../gruppe53/model/PurchaseCalculator.java | 207 +++-- .../ntnu/gruppe53/model/PurchaseFactory.java | 24 +- .../java/no/ntnu/gruppe53/model/Sale.java | 94 ++- .../ntnu/gruppe53/model/SaleCalculator.java | 237 +++--- .../no/ntnu/gruppe53/model/SaleFactory.java | 22 +- .../java/no/ntnu/gruppe53/model/Share.java | 109 +-- .../java/no/ntnu/gruppe53/model/Stock.java | 273 +++--- .../no/ntnu/gruppe53/model/Transaction.java | 153 ++-- .../gruppe53/model/TransactionArchive.java | 240 +++--- .../gruppe53/model/TransactionCalculator.java | 32 +- .../gruppe53/model/TransactionFactory.java | 43 +- .../no/ntnu/gruppe53/service/FileHandler.java | 214 ++--- .../gruppe53/service/FormatBigDecimal.java | 21 +- .../gruppe53/service/LanguageManager.java | 148 ++-- .../gruppe53/service/LanguageRegistry.java | 76 +- .../service/StockLineChartService.java | 101 +-- .../no/ntnu/gruppe53/view/CSVChooserView.java | 49 -- .../no/ntnu/gruppe53/view/CsvChooserView.java | 51 ++ .../java/no/ntnu/gruppe53/view/EndView.java | 384 ++++----- .../java/no/ntnu/gruppe53/view/FooterBar.java | 366 ++++---- .../no/ntnu/gruppe53/view/MarketView.java | 787 +++++++++--------- .../no/ntnu/gruppe53/view/NavigationBar.java | 416 +++++---- .../gruppe53/view/PlayerNameChooserView.java | 141 ++-- .../view/PlayerStartingMoneyChooserView.java | 156 ++-- .../no/ntnu/gruppe53/view/PortfolioView.java | 672 +++++++-------- .../java/no/ntnu/gruppe53/view/StartView.java | 117 ++- .../gruppe53/view/StockLineChartView.java | 196 ++--- .../gruppe53/view/TransactionArchiveView.java | 356 ++++---- 37 files changed, 4016 insertions(+), 3798 deletions(-) delete mode 100644 millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java create mode 100644 millions/src/main/java/no/ntnu/gruppe53/view/CsvChooserView.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/App.java b/millions/src/main/java/no/ntnu/gruppe53/App.java index b3c3b06..3170ab6 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/App.java +++ b/millions/src/main/java/no/ntnu/gruppe53/App.java @@ -8,28 +8,34 @@ /** * Represents the main app of the application. + * *

Sets up necessary components and shows the first view * of the application: {@link StartView}.

*/ public class App extends Application { - @Override - public void start(Stage stage) { - ViewController vm = new ViewController(stage); + @Override + public void start(Stage stage) { + ViewController vm = new ViewController(stage); - StartView startView = new StartView(vm); + StartView startView = new StartView(vm); - new StartViewController(startView, vm); + new StartViewController(startView, vm); - vm.addView("start", startView); + vm.addView("start", startView); - vm.switchView("start"); + vm.switchView("start"); - stage.setTitle("Millions - the Stock Game"); - stage.show(); - } + stage.setTitle("Millions - the Stock Game"); + stage.show(); + } - public static void main(String[] args) { - launch(); - } + /** + * Launches the program. + * + * @param args arguments + */ + static void main(String[] args) { + launch(); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index d8ea407..0514822 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -3,336 +3,375 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.List; - import javafx.application.Platform; import javafx.scene.control.Alert; import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; import javafx.stage.Stage; import javafx.util.Subscription; -import no.ntnu.gruppe53.model.*; +import no.ntnu.gruppe53.model.Exchange; +import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.model.PurchaseCalculator; +import no.ntnu.gruppe53.model.SaleCalculator; +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; import no.ntnu.gruppe53.service.LanguageManager; import no.ntnu.gruppe53.service.StockLineChartService; -import no.ntnu.gruppe53.view.*; +import no.ntnu.gruppe53.view.EndView; +import no.ntnu.gruppe53.view.FooterBar; +import no.ntnu.gruppe53.view.MarketView; +import no.ntnu.gruppe53.view.NavigationBar; +import no.ntnu.gruppe53.view.PortfolioView; +import no.ntnu.gruppe53.view.TransactionArchiveView; /** * Represents the main controller for the views of the stock game. + * *

Uses a {@link LanguageManager} for methods to change text dynamically.

*/ public class GameController { - private Player player; - private Exchange exchange; - private List stocks; - private final LanguageManager lm = LanguageManager.getInstance(); - - private final ViewController vm; - - private MarketView marketView; - private PortfolioView portfolioView; - private TransactionArchiveView transactionArchiveView; - private EndView endView; - private PurchaseCalculator purchaseCalculator; - private SaleCalculator saleCalculator; - - private Subscription selectedStockPriceSubscription; - private Subscription selectedSharePriceSubscription; - - /** - * Initializes the {@link ViewController} to be used for switching views. - * @param vm {@code ViewController} to be used when switching views - */ - public GameController(ViewController vm) { - this.vm = vm; + private Player player; + private Exchange exchange; + private final LanguageManager lm = LanguageManager.getInstance(); + + private final ViewController vm; + + private MarketView marketView; + private PortfolioView portfolioView; + private TransactionArchiveView transactionArchiveView; + private EndView endView; + + private Subscription selectedStockPriceSubscription; + private Subscription selectedSharePriceSubscription; + + /** + * Initializes the {@link ViewController} to be used for switching views. + * + * @param vm {@code ViewController} to be used when switching views + */ + public GameController(ViewController vm) { + this.vm = vm; + } + + /** + * Starts a new game by initializing all main views and navigation. + * + *

Creates the player and exchange to be used by the game from + * data from the {@link GameSetupController}.

+ */ + public void startNewGame() { + Stage primaryStage = vm.getStage(); + + GameSetupController setupController = new GameSetupController(primaryStage); + GameSetupController.SetupResult setupResult = setupController.setupNewGame(); + + if (setupResult == null) { + return; } - /** - * Starts a new game by initializing all main views and navigation. - *

Creates the player and exchange to be used by the game from - * data from the {@link GameSetupController}.

- */ - public void startNewGame() { - Stage primaryStage = vm.getStage(); - - GameSetupController setupController = new GameSetupController(primaryStage); - GameSetupController.SetupResult setupResult = setupController.setupNewGame(); - if (setupResult == null) return; + player = setupResult.getPlayer(); + List stocks = setupResult.getStocks(); - player = setupResult.getPlayer(); - stocks = setupResult.getStocks(); + exchange = new Exchange("Millions Exchange", stocks); - exchange = new Exchange("Millions Exchange", stocks); + NavigationBar sharedNav = new NavigationBar(); + FooterBar sharedFooter = new FooterBar(); + vm.setGlobalPanels(sharedNav, sharedFooter); - NavigationBar sharedNav = new NavigationBar(); - FooterBar sharedFooter = new FooterBar(); - vm.setGlobalPanels(sharedNav, sharedFooter); + marketView = new MarketView(); + // marketView.setPlayer(player); + marketView.setExchange(exchange); - marketView = new MarketView(); - //marketView.setPlayer(player); - marketView.setExchange(exchange); + portfolioView = new PortfolioView(); + portfolioView.setPlayer(player); - portfolioView = new PortfolioView(); - portfolioView.setPlayer(player); + transactionArchiveView = new TransactionArchiveView(); + transactionArchiveView.setPlayer(player); - transactionArchiveView = new TransactionArchiveView(); - transactionArchiveView.setPlayer(player); + endView = new EndView(); - endView = new EndView(); + sharedNav.bindPlayer(player); + sharedNav.bindExchange(exchange); + sharedFooter.setExchange(exchange); - sharedNav.bindPlayer(player); - sharedNav.bindExchange(exchange); - sharedFooter.setExchange(exchange); + vm.addView("portfolio", portfolioView); + vm.addView("market", marketView); + vm.addView("history", transactionArchiveView); + vm.addView("endGame", endView); - vm.addView("portfolio", portfolioView); - vm.addView("market", marketView); - vm.addView("history", transactionArchiveView); - vm.addView("endGame", endView); + initialize(sharedNav, sharedFooter); - initialize(sharedNav, sharedFooter); + vm.switchView("market"); + } - vm.switchView("market"); + /** + * Switches the view to the {@link MarketView}. + * + *

MarketView lets the player perform {@link no.ntnu.gruppe53.model.Purchase} transactions.

+ */ + public void showMarketView() { + if (marketView != null) { + vm.switchView("market"); } - - /** - * Switches the view to the {@link MarketView}. - *

MarketView lets the player perform {@link Purchase} transactions.

- */ - public void showMarketView() { - if (marketView != null) { - vm.switchView("market"); - } + } + + /** + * Switches the view to the {@link PortfolioView}. + * + *

Contains the data from a player's {@link no.ntnu.gruppe53.model.Portfolio}.

+ * + *

PortfolioView lets theEndView player perform + * {@link no.ntnu.gruppe53.model.Sale} transactions.

+ */ + public void showPortfolio() { + if (portfolioView != null) { + vm.switchView("portfolio"); } - - /** - * Switches the view to the {@link PortfolioView}. - *

Contains the data from a player's {@link Portfolio}.

- *

PortfolioView lets theEndView player perform {@link Sale} transactions.

- */ - public void showPortfolio() { - if (portfolioView != null) vm.switchView("portfolio"); + } + + /** + * Switches the view to the {@link TransactionArchiveView}. + * + *

TransactionArchiveView contains data from + * {@link no.ntnu.gruppe53.model.TransactionArchive}

+ */ + public void showTransactionHistory() { + if (transactionArchiveView != null) { + vm.switchView("history"); } - - /** - * Switches the view to the {@link TransactionArchiveView}. - *

TransactionArchiveView contains data from {@link TransactionArchive}

- */ - public void showTransactionHistory() { - if (transactionArchiveView != null) vm.switchView("history"); - } - - /** - * Initializes the {@link NavigationBar} and {@link FooterBar} for the controller. - *

Binds functionality to exposed buttons and lists in views.

- *

Sets subscriptions for observable values for views.

- *

Sets the method for setting the selected language locale for the footer.

- * @param nav the NavigationBar view - * @param footer the FooterBar view - */ - public void initialize(NavigationBar nav, FooterBar footer) { - nav.onMarketButtonClick(() -> vm.switchView("market")); - nav.onPortfolioButtonClick(() -> vm.switchView("portfolio")); - nav.onHistoryButtonClick(() -> vm.switchView("history")); - nav.onEndGameButtonClick(() -> endGame()); - - footer.onLanguageChange(selected -> { - lm.setLocale(selected.locale()); + } + + /** + * Initializes the {@link NavigationBar} and {@link FooterBar} for the controller. + * + *

Binds functionality to exposed buttons and lists in views.

+ * + *

Sets subscriptions for observable values for views.

+ * + *

Sets the method for setting the selected language locale for the footer.

+ * + * @param nav the NavigationBar view + * @param footer the FooterBar view + */ + public void initialize(NavigationBar nav, FooterBar footer) { + nav.onMarketButtonClick(() -> vm.switchView("market")); + nav.onPortfolioButtonClick(() -> vm.switchView("portfolio")); + nav.onHistoryButtonClick(() -> vm.switchView("history")); + nav.onEndGameButtonClick(this::endGame); + + footer.onLanguageChange(selected -> lm.setLocale(selected.locale())); + + footer.onAdvanceButtonClick(() -> { + if (exchange != null) { + exchange.advance(); + } + }); + + marketView.onStockSelection(stock -> { + if (selectedStockPriceSubscription != null) { + selectedStockPriceSubscription.unsubscribe(); + selectedStockPriceSubscription = null; + } + + if (stock != null) { + marketView.setSelectedStockLabel(stock.getSymbol() + " - " + stock.getCompany()); + marketView.setupPriceSubscription(stock, currentStock -> { + var series = StockLineChartService.buildSeries(currentStock, exchange); + marketView.updateChart(series, currentStock.getCompany()); }); - footer.onAdvanceButtonClick(() -> { - if (exchange != null) { - exchange.advance(); - } - }); - - marketView.onStockSelection(stock -> { - if (selectedStockPriceSubscription != null) { - selectedStockPriceSubscription.unsubscribe(); - selectedStockPriceSubscription = null; - } - - if (stock != null) { - marketView.setSelectedStockLabel(stock.getSymbol() + " - " + stock.getCompany()); - marketView.setupPriceSubscription(stock, currentStock -> { - var series = StockLineChartService.buildSeries(currentStock, exchange); - marketView.updateChart(series, currentStock.getCompany()); - }); - marketView.setPurchaseCalculator(calculatePurchase(marketView.getSelectedStock(), - marketView.getQuantityPurchase())); - - selectedStockPriceSubscription = stock.salesPriceProperty().subscribe(newPrice -> { - if (marketView.getSelectedStock() != null) { - marketView.setPurchaseCalculator(calculatePurchase( - marketView.getSelectedStock(), - marketView.getQuantityPurchase() - )); - } - }); + marketView.setPurchaseCalculator(calculatePurchase( + marketView.getSelectedStock(), marketView.getQuantityPurchase())); - } else { - marketView.setSelectedStockLabel(""); - marketView.clearChart(); - } + selectedStockPriceSubscription = stock.salesPriceProperty().subscribe(newPrice -> { + if (marketView.getSelectedStock() != null) { + marketView.setPurchaseCalculator(calculatePurchase( + marketView.getSelectedStock(), + marketView.getQuantityPurchase() + )); + } }); - marketView.onPurchaseButton(() -> { - Stock stock = marketView.getSelectedStock(); - if (stock != null) purchaseStock(stock.getSymbol(), marketView.getQuantityPurchase()); - }); - marketView.onQuantitySelect(() -> { - if (marketView.getSelectedStock() != null) { - marketView.setPurchaseCalculator(calculatePurchase(marketView.getSelectedStock(), - marketView.getQuantityPurchase())); - } - }); - - portfolioView.onShareSelection(share -> { - if (selectedSharePriceSubscription != null) { - selectedSharePriceSubscription.unsubscribe(); - selectedSharePriceSubscription = null; - } - if (share != null) { - portfolioView.setSelectedShareLabel(share.getStock().getSymbol() + " - " + share.getStock().getCompany()); - portfolioView.setSaleCalculator(calculateSale(portfolioView.getSelectedShare())); - - selectedSharePriceSubscription = share.getStock().salesPriceProperty().subscribe(newPrice -> { - if (portfolioView.getSelectedShare() != null) { - portfolioView.setSaleCalculator(calculateSale(portfolioView.getSelectedShare())); - } + } else { + marketView.setSelectedStockLabel(""); + marketView.clearChart(); + } + }); + + marketView.onPurchaseButton(() -> { + Stock stock = marketView.getSelectedStock(); + if (stock != null) { + purchaseStock(stock.getSymbol(), marketView.getQuantityPurchase()); + } + }); + + marketView.onQuantitySelect(() -> { + if (marketView.getSelectedStock() != null) { + marketView.setPurchaseCalculator(calculatePurchase( + marketView.getSelectedStock(), marketView.getQuantityPurchase())); + } + }); + + portfolioView.onShareSelection(share -> { + if (selectedSharePriceSubscription != null) { + selectedSharePriceSubscription.unsubscribe(); + selectedSharePriceSubscription = null; + } + + if (share != null) { + portfolioView.setSelectedShareLabel( + share.getStock().getSymbol() + " - " + share.getStock().getCompany()); + portfolioView.setSaleCalculator(calculateSale(portfolioView.getSelectedShare())); + + selectedSharePriceSubscription = + share.getStock().salesPriceProperty().subscribe(newPrice -> { + + if (portfolioView.getSelectedShare() != null) { + portfolioView.setSaleCalculator( + calculateSale(portfolioView.getSelectedShare())); + } }); - } else { - portfolioView.setSelectedShareLabel(""); - } - }); - - portfolioView.onSellButton(this::handleSell); - - endView.onQuitButton(Platform::exit); - - endView.onNewGameButton(() -> { - startNewGame(); - }); + } else { + portfolioView.setSelectedShareLabel(""); + } + }); + + portfolioView.onSellButton(this::handleSell); + + endView.onQuitButton(Platform::exit); + + endView.onNewGameButton(this::startNewGame); + } + + /** + * Lets a player purchase {@link Share}s in a stock. + * + *

Utilizes the buy method from {@link Exchange}.

+ * + * @param stockSymbol the symbol/ticker of the stock to purchase shares for + * @param quantity the quantity of shares to be purchased + */ + private void purchaseStock(String stockSymbol, int quantity) { + if (exchange == null || player == null || stockSymbol == null || quantity <= 0) { + return; } - /** - * Lets a player purchase {@link Share}s in a stock. - *

Utilizes the buy method from {@link Exchange}.

- * @param stockSymbol the symbol/ticker of the stock to purchase shares for - * @param quantity the quantity of shares to be purchased - */ - private void purchaseStock(String stockSymbol, int quantity) { - if (exchange == null || player == null || stockSymbol == null || quantity <= 0) return; - try { - exchange.buy(stockSymbol, BigDecimal.valueOf(quantity), player); - } catch (IllegalStateException | IllegalArgumentException ex) { - showErrorAlert("Purchase Failed", ex.getMessage()); - } + try { + exchange.buy(stockSymbol, BigDecimal.valueOf(quantity), player); + } catch (IllegalStateException | IllegalArgumentException ex) { + showErrorAlert("Purchase Failed", ex.getMessage()); } - - /** - * Handles sale of the selected share. - */ - private void handleSell() { - Share selectedShare = portfolioView.getSelectedShare(); - if (selectedShare != null) sellShare(selectedShare); + } + + /** + * Handles sale of the selected share. + */ + private void handleSell() { + Share selectedShare = portfolioView.getSelectedShare(); + if (selectedShare != null) { + sellShare(selectedShare); } - - /** - * Lets a player sell a share from their portfolio. - * @param selectedShare the currently selected share - */ - private void sellShare(Share selectedShare) { - if (exchange == null || player == null || selectedShare == null) return; - try { - exchange.sell(selectedShare, player); - } catch (IllegalStateException | IllegalArgumentException ex) { - showErrorAlert("Sale Failed", ex.getMessage()); - } + } + + /** + * Lets a player sell a share from their portfolio. + * + * @param selectedShare the currently selected share + */ + private void sellShare(Share selectedShare) { + if (exchange == null || player == null || selectedShare == null) { + return; } - - - /** - * Creates a {@link PurchaseCalculator} that can be used for the - * setPurchaseCalculator() function in the {@link MarketView}. - * @param stock the currently selected stock - * @param quantity the currently selected quantity - * @return {@link PurchaseCalculator} - */ - private PurchaseCalculator calculatePurchase(Stock stock, int quantity) { - stock = marketView.getSelectedStock(); - quantity = marketView.getQuantityPurchase(); - - Share share = new Share(stock, BigDecimal.valueOf(quantity), stock.getSalesPrice()); - - - - return purchaseCalculator = new PurchaseCalculator(share); - } - - /** - * Creates a {@link SaleCalculator} that can be used for the - * setSaleCalculator() function in the {@link PortfolioView}. - * @param share the share to use the calculator on - * @return {@link SaleCalculator} - */ - private SaleCalculator calculateSale(Share share) { - - share = portfolioView.getSelectedShare(); - - return saleCalculator = new SaleCalculator(share); - - } - - - /** - * Creates an alert where the Player can confirm if they want to end the game. - * If yes, then sells entire portfolio and displays the EndView. - */ - private void endGame() { - Alert alert = new Alert(Alert.AlertType.CONFIRMATION); - alert.titleProperty().bind(lm.bindString("endGameAlertTitle")); - alert.headerTextProperty().bind(lm.bindString("endGameAlertHeaderText")); - alert.contentTextProperty().bind(lm.bindString("endGameAlertContentText")); - - ButtonType yesButton = new ButtonType(lm.getString("yes"), ButtonBar.ButtonData.YES); - ButtonType noButton = new ButtonType(lm.getString("no"), ButtonBar.ButtonData.NO); - - - alert.getButtonTypes().setAll(yesButton, noButton); - - alert.showAndWait().ifPresent(button -> { - if (button == yesButton) { - sellPortfolio(); - vm.setGlobalPanels(null, null); - vm.switchView("endGame"); - - endView.getStatusLabel().setText(player.getStatus(exchange)); - endView.getMoneyLabel().setText(player.getMoney().setScale(2, RoundingMode.HALF_EVEN).toString()); - - - - } - }); - - } - - private void sellPortfolio() { - List sharesToSell = List.copyOf(player.getPortfolio().getShares()); - - for (Share share : sharesToSell) { - exchange.sell(share, player); - } + try { + exchange.sell(selectedShare, player); + } catch (IllegalStateException | IllegalArgumentException ex) { + showErrorAlert("Sale Failed", ex.getMessage()); } - - /** - * Creates a default error alert to be used for notifying the player of errors. - * @param title title of the error alert - * @param content the message content of the error alert - */ - private void showErrorAlert(String title, String content) { - Alert alert = new Alert(Alert.AlertType.ERROR); - alert.setTitle(title); - alert.setHeaderText(null); - alert.setContentText(content); - alert.showAndWait(); + } + + /** + * Creates a {@link PurchaseCalculator} that can be used for the + * setPurchaseCalculator() function in the {@link MarketView}. + * + * @param stock the currently selected stock + * @param quantity the currently selected quantity + * @return {@link PurchaseCalculator} + */ + private PurchaseCalculator calculatePurchase(Stock stock, int quantity) { + stock = marketView.getSelectedStock(); + quantity = marketView.getQuantityPurchase(); + + Share share = new Share(stock, BigDecimal.valueOf(quantity), stock.getSalesPrice()); + PurchaseCalculator purchaseCalculator; + return purchaseCalculator = new PurchaseCalculator(share); + } + + /** + * Creates a {@link SaleCalculator} that can be used for the + * setSaleCalculator() function in the {@link PortfolioView}. + * + * @param share the share to use the calculator on + * @return {@link SaleCalculator} + */ + private SaleCalculator calculateSale(Share share) { + + share = portfolioView.getSelectedShare(); + + SaleCalculator saleCalculator; + return saleCalculator = new SaleCalculator(share); + } + + /** + * Creates an alert where the Player can confirm if they want to end the game. + * If yes, then sells entire portfolio and displays the EndView. + */ + private void endGame() { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.titleProperty().bind(lm.bindString("endGameAlertTitle")); + alert.headerTextProperty().bind(lm.bindString("endGameAlertHeaderText")); + alert.contentTextProperty().bind(lm.bindString("endGameAlertContentText")); + + ButtonType yesButton = new ButtonType(lm.getString("yes"), ButtonBar.ButtonData.YES); + ButtonType noButton = new ButtonType(lm.getString("no"), ButtonBar.ButtonData.NO); + + alert.getButtonTypes().setAll(yesButton, noButton); + + alert.showAndWait().ifPresent(button -> { + if (button == yesButton) { + sellPortfolio(); + vm.setGlobalPanels(null, null); + vm.switchView("endGame"); + + endView.getStatusLabel().setText(player.getStatus(exchange)); + endView.getMoneyLabel().setText( + player.getMoney().setScale(2, RoundingMode.HALF_EVEN).toString()); + } + }); + } + + /** + * Sells a player's entire portfolio. + */ + private void sellPortfolio() { + List sharesToSell = List.copyOf(player.getPortfolio().getShares()); + + for (Share share : sharesToSell) { + exchange.sell(share, player); } + } + + /** + * Creates a default error alert to be used for notifying the player of errors. + * + * @param title title of the error alert + * @param content the message content of the error alert + */ + private void showErrorAlert(String title, String content) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(content); + alert.showAndWait(); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameSetupController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameSetupController.java index 85432b9..feced72 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameSetupController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameSetupController.java @@ -1,119 +1,122 @@ package no.ntnu.gruppe53.controller; +import java.io.File; +import java.math.BigDecimal; +import java.util.List; +import javafx.stage.Stage; +import no.ntnu.gruppe53.model.Exchange; import no.ntnu.gruppe53.model.Player; import no.ntnu.gruppe53.model.Stock; -import no.ntnu.gruppe53.model.Exchange; import no.ntnu.gruppe53.service.FileHandler; -import no.ntnu.gruppe53.view.CSVChooserView; +import no.ntnu.gruppe53.view.CsvChooserView; import no.ntnu.gruppe53.view.PlayerNameChooserView; import no.ntnu.gruppe53.view.PlayerStartingMoneyChooserView; -import javafx.stage.Stage; - -import java.io.File; -import java.math.BigDecimal; -import java.util.List; /** * Controller for setting up relevant game data to start a new game. + * *

Lets the {@link Player} set their name, starting money * and provide a .csv file of {@link Stock} data to populate * the {@link Exchange}.

*/ public class GameSetupController { + /** + * Nested class to pass the player and stocks values over to the + * {@link GameController}. + */ + public static class SetupResult { + private final Player player; + private final List stocks; + /** - * Nested class to pass the player and stocks values over to the - * {@link GameController}. + * Initializes the player and stock variables. + * + * @param player the player that will play the stock game + * @param stocks the list of stocks to populate the exchange */ - public static class SetupResult { - private final Player player; - private final List stocks; - - /** - * Initializes the player and stock variables. - * @param player the player that will play the stock game - * @param stocks the list of stocks to populate the exchange - */ - public SetupResult(Player player, List stocks) { - this.player = player; - this.stocks = stocks; - } - - /** - * Returns the player object containing the set name and starting money. - * - * @return the player object for the player playing the game - */ - public Player getPlayer() { - return player; - } - - /** - * A list of stocks that will be used for buying and selling on the exchange. - * - * @return a list of stock objects - */ - public List getStocks() { - return stocks; - } + public SetupResult(Player player, List stocks) { + this.player = player; + this.stocks = stocks; } - private final Stage stage; - /** - * Initializes the initial stage for the view setting up the relevant game data. + * Returns the player object containing the set name and starting money. * - * @param stage the initial stage to be used + * @return the player object for the player playing the game */ - public GameSetupController(Stage stage) { - this.stage = stage; + public Player getPlayer() { + return player; } /** - * Sets up the initial data for the {@code Player} and {@code Exchange}. - *

Uses default values if the player does not enter their desired values: - *

    - *
  • Player name: Player
  • - *
  • Player starting money: 5000
  • - *
  • CSV file: resources/sp500.csv
  • - *

+ * A list of stocks that will be used for buying and selling on the exchange. * - * @return a SetupResult object containing data for the player and the exchange + * @return a list of stock objects */ - public SetupResult setupNewGame() { - PlayerNameChooserView playerNameDialog = new PlayerNameChooserView(); - // Player name - String playerName = - playerNameDialog.getDialog() - .orElse("Player"); - - if (playerName.isBlank()) { - playerName = "Player"; - } + public List getStocks() { + return stocks; + } + } + + private final Stage stage; + + /** + * Initializes the initial stage for the view setting up the relevant game data. + * + * @param stage the initial stage to be used + */ + public GameSetupController(Stage stage) { + this.stage = stage; + } + + /** + * Sets up the initial data for the {@code Player} and {@code Exchange}. + * + *

Uses default values if the player does not enter their desired values: + *

    + *
  • Player name: Player
  • + *
  • Player starting money: 5000
  • + *
  • CSV file: resources/sp500.csv
  • + *

+ * + * @return a SetupResult object containing data for the player and the exchange + */ + public SetupResult setupNewGame() { + PlayerNameChooserView playerNameDialog = new PlayerNameChooserView(); + // Player name + String playerName = + playerNameDialog.getDialog().orElse("Player"); + + if (playerName.isBlank()) { + playerName = "Player"; + } - // Starting money - PlayerStartingMoneyChooserView playerStartingMoneyDialog = new PlayerStartingMoneyChooserView(); + // Starting money + PlayerStartingMoneyChooserView playerStartingMoneyDialog = new PlayerStartingMoneyChooserView(); - BigDecimal startingMoney = + BigDecimal startingMoney = playerStartingMoneyDialog.getDialog() .orElse(new BigDecimal("5000")); - // .csv File - CSVChooserView csvChooser = new CSVChooserView(); - File csvFile = csvChooser.getDialog(stage); + // .csv File + CsvChooserView csvChooser = new CsvChooserView(); + File csvFile = csvChooser.getDialog(stage); - List stocks; - if (csvFile == null) { - stocks = FileHandler.readStocksFromFile("src/main/resources", "sp500.csv"); - } else { - stocks = FileHandler.readStocksFromFile(csvFile.getParent(), csvFile.getName()); - if (stocks.isEmpty()) { - stocks = FileHandler.readStocksFromFile("src/main/resources", "sp500.csv"); - } - } + List stocks; - Player player = new Player(playerName, startingMoney); + if (csvFile == null) { + stocks = FileHandler.readStocksFromFile("src/main/resources", "sp500.csv"); + } else { + stocks = FileHandler.readStocksFromFile(csvFile.getParent(), csvFile.getName()); - return new SetupResult(player, stocks); + if (stocks.isEmpty()) { + stocks = FileHandler.readStocksFromFile("src/main/resources", "sp500.csv"); + } } + + Player player = new Player(playerName, startingMoney); + + return new SetupResult(player, stocks); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java index 82d8fde..5b743a9 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java @@ -1,56 +1,58 @@ package no.ntnu.gruppe53.controller; +import java.util.List; +import javafx.application.Platform; import no.ntnu.gruppe53.service.LanguageManager; import no.ntnu.gruppe53.service.LanguageOption; import no.ntnu.gruppe53.service.LanguageRegistry; import no.ntnu.gruppe53.view.StartView; -import javafx.application.Platform; - -import java.util.List; /** * The controller for {@link StartView}, * the first view for the player upon launching the app. + * *

Contains three choices: *

    *
  • New Game
  • *
  • Language Select
  • *
  • Quit
  • *

+ * *

Uses a {@link LanguageManager} to change the UI language.

*/ public class StartViewController { - /** - * Handles the events when "New Game" and "Quit" is pressed. - * - * @param view the StartView to be displayed - * @param vm the {@link ViewController} to handle switching of views - */ - public StartViewController(StartView view, ViewController vm) { - view.setOnNewGame(() -> { - GameController gameController = new GameController(vm); - gameController.startNewGame(); - }); - - view.setOnLanguage(() -> { - LanguageManager lm = LanguageManager.getInstance(); - List availableLanguages = LanguageRegistry.getLanguages(); - - int currentIndex = 0; - for (int i = 0; i < availableLanguages.size(); i++) { - if (availableLanguages.get(i).locale().equals(lm.getLocale())) { - currentIndex = i; - break; - } - } - - int nextIndex = (currentIndex + 1) % availableLanguages.size(); - LanguageOption nextLanguage = availableLanguages.get(nextIndex); - - lm.setLocale(nextLanguage.locale()); - }); - - - view.setOnQuit(Platform::exit); - } + /** + * Handles the events when "New Game" and "Quit" is pressed. + * + * @param view the StartView to be displayed + * @param vm the {@link ViewController} to handle switching of views + */ + public StartViewController(StartView view, ViewController vm) { + view.setOnNewGame(() -> { + GameController gameController = new GameController(vm); + gameController.startNewGame(); + }); + + view.setOnLanguage(() -> { + LanguageManager lm = LanguageManager.getInstance(); + List availableLanguages = LanguageRegistry.getLanguages(); + + int currentIndex = 0; + + for (int i = 0; i < availableLanguages.size(); i++) { + if (availableLanguages.get(i).locale().equals(lm.getLocale())) { + currentIndex = i; + break; + } + } + + int nextIndex = (currentIndex + 1) % availableLanguages.size(); + LanguageOption nextLanguage = availableLanguages.get(nextIndex); + + lm.setLocale(nextLanguage.locale()); + }); + + + view.setOnQuit(Platform::exit); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java index 1bf8d20..f24cfeb 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java @@ -1,5 +1,7 @@ package no.ntnu.gruppe53.controller; +import java.util.HashMap; +import java.util.Map; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.layout.BorderPane; @@ -7,74 +9,74 @@ import no.ntnu.gruppe53.view.FooterBar; import no.ntnu.gruppe53.view.NavigationBar; -import java.util.HashMap; -import java.util.Map; - /** * Controller for setting and switching between views. + * *

Uses a {@link HashMap} as an archive for registered views that * are possible to switch between.

*/ public class ViewController { - private final Stage stage; - private final Scene scene; - private final Map views = new HashMap<>(); - private final BorderPane mainLayout; + private final Stage stage; + private final Map views = new HashMap<>(); + private final BorderPane mainLayout; - /** - * Initializes a ViewController using the {@link Stage} as the first default view. - *

Uses a {@link BorderPane} layout for views.

- */ - public ViewController(Stage stage) { - this.stage = stage; - this.mainLayout = new BorderPane(); - this.scene = new Scene(mainLayout, 1200, 900); - this.stage.setScene(scene); - } + /** + * Initializes a ViewController using the {@link Stage} as the first default view. + * + *

Uses a {@link BorderPane} layout for views.

+ */ + public ViewController(Stage stage) { + this.stage = stage; + this.mainLayout = new BorderPane(); + Scene scene = new Scene(mainLayout, 1200, 900); + this.stage.setScene(scene); + } - /** - * Returns the stage of the ViewController. - * - * @return the stage of the ViewController. - */ - public Stage getStage() { - return this.stage; - } + /** + * Returns the stage of the ViewController. + * + * @return the stage of the ViewController. + */ + public Stage getStage() { + return this.stage; + } - /** - * Sets the top and bottom part of the BorderPane. - *

Uses {@link NavigationBar} as the top bar, - * and {@link FooterBar} as the bottom bar.

- */ - public void setGlobalPanels(NavigationBar navigationBar, FooterBar footerBar) { - this.mainLayout.setTop(navigationBar); - this.mainLayout.setBottom(footerBar); - } + /** + * Sets the top and bottom part of the BorderPane. + * + *

Uses {@link NavigationBar} as the top bar, + * and {@link FooterBar} as the bottom bar.

+ */ + public void setGlobalPanels(NavigationBar navigationBar, FooterBar footerBar) { + this.mainLayout.setTop(navigationBar); + this.mainLayout.setBottom(footerBar); + } - /** - * Adds a view to the stored archive of possible views. - * - * @param name the name/key to refer to the view when switching view - * @param view the view to be added - */ - public void addView(String name, Parent view) { - views.put(name, view); - } + /** + * Adds a view to the stored archive of possible views. + * + * @param name the name/key to refer to the view when switching view + * @param view the view to be added + */ + public void addView(String name, Parent view) { + views.put(name, view); + } - /** - * Switches the to the given view based on the string key - * stored in the view archive. - *

Only switches the center pane.

- * - * @param name the name/key of the view to switch to - */ - public void switchView(String name) { - Parent view = views.get(name); + /** + * Switches the to the given view based on the string key + * stored in the view archive. + * + *

Only switches the center pane.

+ * + * @param name the name/key of the view to switch to + */ + public void switchView(String name) { + Parent view = views.get(name); - if (view == null) { - throw new IllegalArgumentException("No views found matching: " + name); - } - - mainLayout.setCenter(view); + if (view == null) { + throw new IllegalArgumentException("No views found matching: " + name); } + + mainLayout.setCenter(view); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java b/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java index f20919c..86fef14 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java @@ -1,252 +1,284 @@ package no.ntnu.gruppe53.model; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.stream.Collectors; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.*; -import java.util.stream.Collectors; - /** - * Represents an exchange/market containing {@link Stock}s and allows a {@link Player} to buy {@link Share}s - * of said stocks. - *

Contains method to advance the week of the market, finding stocks, gainers, and losers on the exchange.

+ * Represents an exchange/market containing {@link Stock}s and allows + * a {@link Player} to buy {@link Share}s of said stocks. + * + *

Contains method to advance the week of the market, finding stocks, + * gainers, and losers on the exchange.

*/ public class Exchange { - private final String name; - private int week; - private final Map stockMap; - private final Random random; - private final ObservableList stocks = FXCollections.observableArrayList(); - private final ObjectProperty currentWeekProperty = new SimpleObjectProperty<>(); - private final TransactionFactory purchaseFactory; - private final TransactionFactory saleFactory; - - /** - * Sets the name of the exchange and the list of stocks to populate it. - *

Validates that the values are proper before creating the exchange.

- *

Initializes factories for creating a {@link Transaction}.

- * - * @param name the name of the exchange - * @param stocks the list of stocks to be tradeable on the exchange - * @throws IllegalArgumentException if exchange is null or blank, if list of stocks is null, or if - * the list of stocks contains a null or duplicate symbol stock - */ - public Exchange(String name, List stocks) { - if (name == null || name.isBlank()) { - throw new IllegalArgumentException("Exchange name cannot be null or blank"); - } - - if (stocks == null) { - throw new IllegalArgumentException("Stock list cannot be null"); - } - - this.name = name; - this.week = 1; - this.currentWeekProperty.set(week); - this.stockMap = new HashMap<>(); - this.random = new Random(); - - for (Stock stock : stocks) { - if (stock == null) { - throw new IllegalArgumentException("Stock cannot be null"); - } - - if (stockMap.containsKey(stock.getSymbol())) { - throw new IllegalArgumentException("Duplicate stock symbol: " + stock.getSymbol()); - } - - stockMap.put(stock.getSymbol(), stock); - this.stocks.add(stock); - } - - this.purchaseFactory = new PurchaseFactory(); - this.saleFactory = new SaleFactory(); + private final String name; + private int week; + private final Map stockMap; + private final Random random; + private final ObservableList stocks = FXCollections.observableArrayList(); + private final ObjectProperty currentWeekProperty = new SimpleObjectProperty<>(); + private final TransactionFactory purchaseFactory; + private final TransactionFactory saleFactory; + + /** + * Sets the name of the exchange and the list of stocks to populate it. + * + *

Validates that the values are proper before creating the exchange.

+ * + *

Initializes factories for creating a {@link Transaction}.

+ * + * @param name the name of the exchange + * @param stocks the list of stocks to be tradeable on the exchange + * @throws IllegalArgumentException if exchange is null or blank, if list of stocks is null, or if + * the list of stocks contains a null or duplicate symbol stock + */ + public Exchange(String name, List stocks) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("Exchange name cannot be null or blank"); } - /** - * Returns the name of the exchange - * @return the name of the exchange - */ - public String getName() { - return name; + if (stocks == null) { + throw new IllegalArgumentException("Stock list cannot be null"); } - /** - * Returns the current week of the exchange - * - * @return the current week of the exchange - */ - public int getWeek() { - return week; - } + this.name = name; + this.week = 1; + this.currentWeekProperty.set(week); + this.stockMap = new HashMap<>(); + this.random = new Random(); - /** - * Checks whether a stock with the given symbol/ticker exists in the exchange. - *

Returns {@code true} if it does,v{@code false} otherwise.

- * - * @param symbol the symbol/ticker of the stock - * @return {@code true} if the exchange contains the stock, otherwise {@code false} - */ - public boolean hasStock(String symbol) { - return stockMap.containsKey(symbol); - } + for (Stock stock : stocks) { + if (stock == null) { + throw new IllegalArgumentException("Stock cannot be null"); + } - /** - * Returns the stock object matching the given symbol/ticker. - * - * @param symbol the stock symbol/ticker - * @return the stock object matching the symbol/ticker - * @throws IllegalArgumentException if search symbol is null or symbol is not in stockMap - */ - public Stock getStock(String symbol) { - if (symbol == null || !stockMap.containsKey(symbol)) { - throw new IllegalArgumentException("Stock not found: " + symbol); - } - return stockMap.get(symbol); + if (stockMap.containsKey(stock.getSymbol())) { + throw new IllegalArgumentException("Duplicate stock symbol: " + stock.getSymbol()); + } + + stockMap.put(stock.getSymbol(), stock); + this.stocks.add(stock); } - /** - * Returns an {@link ObservableList} of the list of stocks for JavaFX to observe. - * - * @return an observable list of the stocks in the exchange - */ - public ObservableList getObservableStocks() { - return stocks; + this.purchaseFactory = new PurchaseFactory(); + this.saleFactory = new SaleFactory(); + } + + /** + * Returns the name of the exchange. + * + * @return the name of the exchange + */ + public String getName() { + return name; + } + + /** + * Returns the current week of the exchange. + * + * @return the current week of the exchange + */ + public int getWeek() { + return week; + } + + /** + * Checks whether a stock with the given symbol/ticker exists in the exchange. + * + *

Returns {@code true} if it does,v{@code false} otherwise.

+ * + * @param symbol the symbol/ticker of the stock + * @return {@code true} if the exchange contains the stock, otherwise {@code false} + */ + public boolean hasStock(String symbol) { + return stockMap.containsKey(symbol); + } + + /** + * Returns the stock object matching the given symbol/ticker. + * + * @param symbol the stock symbol/ticker + * @return the stock object matching the symbol/ticker + * @throws IllegalArgumentException if search symbol is null or symbol is not in stockMap + */ + public Stock getStock(String symbol) { + if (symbol == null || !stockMap.containsKey(symbol)) { + throw new IllegalArgumentException("Stock not found: " + symbol); + } + return stockMap.get(symbol); + } + + /** + * Returns an {@link ObservableList} of the list of stocks for JavaFX to observe. + * + * @return an observable list of the stocks in the exchange + */ + public ObservableList getObservableStocks() { + return stocks; + } + + /** + * Returns a list of stocks matching the search term in either the symbol/ticker or company name. + * + * @param searchTerm the string to search for + * @return a list of stocks matching the search term + */ + public List findStocks(String searchTerm) { + if (searchTerm == null || searchTerm.isBlank()) { + return Collections.emptyList(); } - /** - * Returns a list of stocks matching the search term in either the symbol/ticker or company name. - * - * @param searchTerm the string to search for - * @return a list of stocks matching the search term - */ - public List findStocks(String searchTerm) { - if (searchTerm == null || searchTerm.isBlank()) return Collections.emptyList(); - - String search = searchTerm.toLowerCase(); - return stockMap.values().stream() - .filter(stock -> stock.getSymbol().toLowerCase().contains(search) || + String search = searchTerm.toLowerCase(); + return stockMap.values().stream() + .filter(stock -> stock.getSymbol().toLowerCase().contains(search) + || stock.getCompany().toLowerCase().contains(search)) .collect(Collectors.toList()); + } + + /** + * Represents a player buying a share on the exchange. + * + *

Creates a {@link Purchase} transaction for the + * purchase.

+ * + * @param symbol the symbol/ticker of the stock + * @param quantity the quantity of shares to buy for said stock + * @param player the player making the purchase + * @return a purchase transaction containing all details of the purchase + * @throws IllegalArgumentException if player or quantity is null + */ + public Transaction buy(String symbol, BigDecimal quantity, Player player) { + if (player == null) { + throw new IllegalArgumentException("Player cannot be null"); } - /** - * Represents a player buying a share on the exchange. - *

Creates a {@link Purchase} transaction for the - * purchase.

- * - * @param symbol the symbol/ticker of the stock - * @param quantity the quantity of shares to buy for said stock - * @param player the player making the purchase - * @return a purchase transaction containing all details of the purchase - * @throws IllegalArgumentException if player or quantity is null - */ - public Transaction buy(String symbol, BigDecimal quantity, Player player) { - if (player == null) throw new IllegalArgumentException("Player cannot be null"); - if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) - throw new IllegalArgumentException("Quantity must be positive"); - - Stock stock = getStock(symbol); - Share share = new Share(stock, quantity, stock.getSalesPrice()); - Transaction transaction = purchaseFactory.create(share, week); - transaction.commit(player); - - return transaction; + if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Quantity must be positive"); } - /** - * Represents a player selling a share on the exchange. - *

Creates a {@link Sale} transaction for the sale.

- * - * @param share the share to be sold - * @param player the player selling the share - * @return a sale transaction containing all details of the sale - * @throws IllegalArgumentException if player or share is null - */ - public Transaction sell(Share share, Player player) { - if (player == null) throw new IllegalArgumentException("Player cannot be null"); - if (share == null) throw new IllegalArgumentException("Share cannot be null"); - - Transaction transaction = saleFactory.create(share, week); - transaction.commit(player); - return transaction; + Stock stock = getStock(symbol); + Share share = new Share(stock, quantity, stock.getSalesPrice()); + Transaction transaction = purchaseFactory.create(share, week); + transaction.commit(player); + + return transaction; + } + + /** + * Represents a player selling a share on the exchange. + * + *

Creates a {@link Sale} transaction for the sale.

+ * + * @param share the share to be sold + * @param player the player selling the share + * @return a sale transaction containing all details of the sale + * @throws IllegalArgumentException if player or share is null + */ + public Transaction sell(Share share, Player player) { + if (player == null) { + throw new IllegalArgumentException("Player cannot be null"); } - /** - * Advances the timeline of the exchange by 1 week. - *

Uses a randomized value for the increase and decrease of a stocks sales price, simulating market - * fluctuations on a weekly basis.

- */ - public void advance() { - this.week++; - this.currentWeekProperty.set(week); - - double negativePriceChange = -0.05; - double positivePriceChange = 0.1; - - for (Stock stock : stockMap.values()) { - BigDecimal currentPrice = stock.getSalesPrice(); - double randomPriceMultiplier = negativePriceChange + (positivePriceChange * random.nextDouble()); - BigDecimal bdRandomPriceMultiplier = BigDecimal.valueOf(1 + randomPriceMultiplier); + if (share == null) { + throw new IllegalArgumentException("Share cannot be null"); + } - BigDecimal changedPrice = currentPrice.multiply(bdRandomPriceMultiplier) + Transaction transaction = saleFactory.create(share, week); + transaction.commit(player); + return transaction; + } + + /** + * Advances the timeline of the exchange by 1 week. + * + *

Uses a randomized value for the increase and decrease + * of a stocks sales price, simulating market fluctuations on a weekly basis.

+ */ + public void advance() { + this.week++; + this.currentWeekProperty.set(week); + + double negativePriceChange = -0.05; + double positivePriceChange = 0.1; + + for (Stock stock : stockMap.values()) { + BigDecimal currentPrice = stock.getSalesPrice(); + double randomPriceMultiplier = negativePriceChange + + (positivePriceChange * random.nextDouble()); + BigDecimal bdRandomPriceMultiplier = BigDecimal.valueOf(1 + randomPriceMultiplier); + + BigDecimal changedPrice = currentPrice.multiply(bdRandomPriceMultiplier) .setScale(2, RoundingMode.HALF_UP); - if (changedPrice.compareTo(BigDecimal.ZERO) <= 0) { - changedPrice = new BigDecimal(1); - } + if (changedPrice.compareTo(BigDecimal.ZERO) <= 0) { + changedPrice = new BigDecimal(1); + } - stock.addNewSalesPrice(changedPrice); - } + stock.addNewSalesPrice(changedPrice); + } + } + + /** + * Sorts the stocks in descending order and returns a list of stocks. + * + * @param entries amount of entries in the list to display + * @param comparator the comparator used to compare the stocks + * @return a list of stocks + * @throws IllegalArgumentException if the amount of entries is negative + */ + private List getTopStocks(int entries, Comparator comparator) { + if (entries < 0) { + throw new IllegalArgumentException("Amount of entries in list cannot be negative."); } - /** - * Sorts the stocks in descending order and returns a list of stocks. - * - * @param entries amount of entries in the list to display - * @param comparator the comparator used to compare the stocks - * @return a list of stocks - * @throws IllegalArgumentException if the amount of entries is negative - */ - private List getTopStocks(int entries, Comparator comparator) { - if (entries < 0) throw new IllegalArgumentException("Amount of entries in list cannot be negative."); - return stockMap.values().stream() + return stockMap.values().stream() .sorted(comparator) .limit(entries) .collect(Collectors.toList()); - } - - /** - * Lists the given amount of stocks sorted by highest increase in sales price since last week. - * @param entries amount of stocks to be shown in list - * @return a descending list of stocks with the highest price increase since last week - * @throws IllegalArgumentException if number of entries in list is negative - */ - public List getGainers(int entries) { - return getTopStocks(entries, Comparator.comparing(Stock::getLatestPriceChange).reversed()); - } - - /** - * Lists the given amount of stocks sorted by the stocks with the lowest price increase (or - * highest price decrease) - * @param entries amount of stocks to be shown in the list - * @return a descending list of stocks with the highest price decrease or - * lowest price increase - * @throws IllegalArgumentException if number of entries in list is negative - */ - public List getLosers(int entries) { - return getTopStocks(entries, Comparator.comparing(Stock::getLatestPriceChange)); - } - - /** - * Returns an observable of the current week - * @return an observable of the current week - */ - public ObjectProperty getCurrentWeekProperty() { - return currentWeekProperty; - } + } + + /** + * Lists the given amount of stocks sorted by highest increase in sales price since last week. + * + * @param entries amount of stocks to be shown in list + * @return a descending list of stocks with the highest price increase since last week + * @throws IllegalArgumentException if number of entries in list is negative + */ + public List getGainers(int entries) { + return getTopStocks(entries, Comparator.comparing(Stock::getLatestPriceChange).reversed()); + } + + /** + * Lists the given amount of stocks sorted by the stocks with the lowest price increase (or + * highest price decrease). + * + * @param entries amount of stocks to be shown in the list + * @return a descending list of stocks with the highest price decrease or + * lowest price increase + * @throws IllegalArgumentException if number of entries in list is negative + */ + public List getLosers(int entries) { + return getTopStocks(entries, Comparator.comparing(Stock::getLatestPriceChange)); + } + + /** + * Returns an observable of the current week. + * + * @return an observable of the current week + */ + public ObjectProperty getCurrentWeekProperty() { + return currentWeekProperty; + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Player.java b/millions/src/main/java/no/ntnu/gruppe53/model/Player.java index 21a9e9e..6489d53 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Player.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Player.java @@ -7,181 +7,198 @@ /** * Represents a participant in the trading game. - *

- * A {@code Player} has a name, a monetary balance, a {@link Portfolio} + * + *

A {@code Player} has a name, a monetary balance, a {@link Portfolio} * of owned {@link Share}s, and a {@link TransactionArchive} containing - * a history of the player's transactions. - *

+ * a history of the player's transactions.

* - *

- * The player starts with an initial amount of money and may gain or - * lose funds through transactions. - *

+ *

The player starts with an initial amount of money and may gain or + * lose funds through transactions.

*/ public class Player { - private final String name; - private final BigDecimal startingMoney; - private BigDecimal money; - private final Portfolio portfolio; - private final TransactionArchive transactionArchive; - - private final ObjectProperty moneyProperty = new SimpleObjectProperty<>(); - private final ObjectProperty netWorthProperty = new SimpleObjectProperty<>(); - - /** - * Creates a player with a given starting balance. - *

Subscribes to the netWorth property to notify of changes in the player's net worth.

- * - * @param name the player's name; must not be {@code null} or blank - * @param startingMoney the initial monetary balance of the - * player; must not be {@code null} or negative - * @throws IllegalArgumentException if {@code name} is {@code null} or - * blank, or if {@code startingMoney} - * is {@code null}, negative, or 0 - */ - public Player(String name, BigDecimal startingMoney) { - if (name == null || name.isBlank()) { - throw new IllegalArgumentException("Player name must not be null or empty."); - } - - if (startingMoney == null || startingMoney.signum() <= 0 ) { - throw new IllegalArgumentException("Starting money must not be null, negative, nor 0."); - } - - this.name = name; - this.startingMoney = startingMoney; - this.money = startingMoney; - this.moneyProperty.set(startingMoney); - this.portfolio = new Portfolio(); - this.transactionArchive = new TransactionArchive(); - - portfolio.netWorthProperty().subscribe(price -> updateNetWorth()); - updateNetWorth(); + private final String name; + private final BigDecimal startingMoney; + private BigDecimal money; + private final Portfolio portfolio; + private final TransactionArchive transactionArchive; + + private final ObjectProperty moneyProperty = new SimpleObjectProperty<>(); + private final ObjectProperty netWorthProperty = new SimpleObjectProperty<>(); + + /** + * Creates a player with a given starting balance. + * + *

Subscribes to the netWorth property to notify of changes in the player's net worth.

+ * + * @param name the player's name; must not be {@code null} or blank + * @param startingMoney the initial monetary balance of the + * player; must not be {@code null} or negative + * @throws IllegalArgumentException if {@code name} is {@code null} or + * blank, or if {@code startingMoney} + * is {@code null}, negative, or 0 + */ + public Player(String name, BigDecimal startingMoney) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("Player name must not be null or empty."); } - /** - * Returns an {@link ObjectProperty} of the player's net worth. - * - * @return an observable of the player's net worth - */ - public ObjectProperty getNetWorthProperty() { - return netWorthProperty; + if (startingMoney == null || startingMoney.signum() <= 0) { + throw new IllegalArgumentException("Starting money must not be null, negative, nor 0."); } - /** - * Mirrors the change in the player's net worth to the observable property. - */ - private void updateNetWorth() { - netWorthProperty.set(getNetWorth()); + this.name = name; + this.startingMoney = startingMoney; + this.money = startingMoney; + this.moneyProperty.set(startingMoney); + this.portfolio = new Portfolio(); + this.transactionArchive = new TransactionArchive(); + + portfolio.netWorthProperty().subscribe(price -> updateNetWorth()); + updateNetWorth(); + } + + /** + * Returns an {@link ObjectProperty} of the player's net worth. + * + * @return an observable of the player's net worth + */ + public ObjectProperty getNetWorthProperty() { + return netWorthProperty; + } + + /** + * Mirrors the change in the player's net worth to the observable property. + */ + private void updateNetWorth() { + netWorthProperty.set(getNetWorth()); + } + + /** + * Returns the full name of the player. + * + * @return the player's name + */ + public String getName() { + return this.name; + } + + /** + * Returns the player's current monetary balance. + * + * @return the player's current balance. + */ + public BigDecimal getMoney() { + return this.money; + } + + /** + * Increases the players monetary balance by the given amount. + * + *

Money added must be a {@code positive} number.

+ * + * @param amount the amount to add; must be positive and not {@code null} + * @throws IllegalArgumentException if {@code amount} is {@code null} + * or not positive + */ + public void addMoney(BigDecimal amount) { + if (amount == null) { + throw new IllegalArgumentException("Amount must not be null."); } - - /** - * Returns the full name of the player. - * - * @return the player's name - */ - public String getName() { - return this.name; + if (amount.signum() <= 0) { + throw new IllegalArgumentException("Amount must be positive."); } - /** - * Returns the player's current monetary balance. - * - * @return the player's current balance. - */ - public BigDecimal getMoney() { - return this.money; + this.money = this.money.add(amount); + this.moneyProperty.set(this.money); + updateNetWorth(); + } + + /** + * Decreases the player's monetary balance by the given amount. + * + *

The player's monetary balance must not decrease below {@code 0} + * (overdrawn not possible).

+ * + * @param amount the amount to subtract; must not be negative or {@code null} + * @throws IllegalArgumentException if the amount is not positive, + * {@code null}, or insufficient funds are available + */ + public void withdrawMoney(BigDecimal amount) { + if (amount == null) { + throw new IllegalArgumentException("Amount must not be null."); } - /** - * Increases the players monetary balance by the given amount. - *

- * Money added must be a {@code positive} number. - *

- * - * @param amount the amount to add; must be positive and not {@code null} - * @throws IllegalArgumentException if {@code amount] is {@code null} - * or not positive - */ - public void addMoney(BigDecimal amount) { - if (amount == null) throw new IllegalArgumentException("Amount must not be null."); - if (amount.signum() <= 0) throw new IllegalArgumentException("Amount must be positive."); - this.money = this.money.add(amount); - this.moneyProperty.set(this.money); - updateNetWorth(); + if (amount.signum() <= 0) { + throw new IllegalArgumentException("Amount must be positive."); } - /** - * Decreases the player's monetary balance by the given amount. - *

- * The player's monetary balance must not decrease below {@code 0} - * (overdrawn not possible). - *

- * - * @param amount the amount to subtract; must not be negative or {@code null} - * @throws IllegalArgumentException if the amount is not positive, {@code null} - * , or insufficient funds are available - */ - public void withdrawMoney(BigDecimal amount) { - if (amount == null) throw new IllegalArgumentException("Amount must not be null."); - if (amount.signum() <= 0) throw new IllegalArgumentException("Amount must be positive."); - if (this.money.subtract(amount).signum() < 0) throw new IllegalArgumentException("Insufficient funds."); - this.money = this.money.subtract(amount); - this.moneyProperty.set(this.money); - updateNetWorth(); + if (this.money.subtract(amount).signum() < 0) { + throw new IllegalArgumentException("Insufficient funds."); } - - /** - * Returns an observable of the player's current monetary funds - * @return an observable of the player's current money - */ - public ObjectProperty getMoneyProperty() { - return moneyProperty; + this.money = this.money.subtract(amount); + this.moneyProperty.set(this.money); + updateNetWorth(); + } + + /** + * Returns an observable of the player's current monetary funds. + * + * @return an observable of the player's current money + */ + public ObjectProperty getMoneyProperty() { + return moneyProperty; + } + + /** + * Return's the player's portfolio of shares. + * + * @return the player's portfolio + */ + public Portfolio getPortfolio() { + return this.portfolio; + } + + /** + * Returns the archive of the player's transaction history. + * + * @return the player's transaction archive + */ + public TransactionArchive getTransactionArchive() { + return this.transactionArchive; + } + + /** + * Returns the player's current net worth. + * + *

The net worth is the sum of the player's current money and + * all the value of their shares in their portfolio.

+ * + * @return the player's current net worth + */ + public BigDecimal getNetWorth() { + return money.add(portfolio.getNetWorth()); + } + + /** + * Returns the current status/title of the player, representing their achievements. + * + * @param exchange the exchange the player is trading at + * @return a string representing the players current status + */ + public String getStatus(Exchange exchange) { + String status = "Novice"; + BigDecimal ratio = getNetWorth().divide(this.startingMoney, RoundingMode.HALF_EVEN); + + if (ratio.compareTo(new BigDecimal("1.20")) >= 0 && exchange.getWeek() >= 10) { + status = "Investor"; } - - /** - * Return's the player's portfolio of shares. - * - * @return the player's portfolio - */ - public Portfolio getPortfolio() { - return this.portfolio; + if (ratio.compareTo(new BigDecimal("2")) >= 0 && exchange.getWeek() >= 20) { + status = "Speculator"; } - /** - * Returns the archive of the player's transaction history. - * - * @return the player's transaction archive - */ - public TransactionArchive getTransactionArchive() { - return this.transactionArchive; - } - - /** - * Returns the player's current net worth. - *

The net worth is the sum of the player's current money and all the value of their shares in their - * portfolio.

- * - * @return the player's current net worth - */ - public BigDecimal getNetWorth() { - return money.add(portfolio.getNetWorth()); - } - - /** - * Returns the current status/title of the player, representing their achievements. - * - * @param exchange the exchange the player is trading at - * @return a string representing the players current status - */ - public String getStatus(Exchange exchange) { - String status = "Novice"; - BigDecimal ratio = getNetWorth().divide(this.startingMoney, RoundingMode.HALF_EVEN); - if (ratio.compareTo(new BigDecimal("1.20")) >= 0 && exchange.getWeek() >= 10) status = "Investor"; - if (ratio.compareTo(new BigDecimal("2")) >= 0 && exchange.getWeek() >= 20) status = "Speculator"; - return status; - } + return status; + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java b/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java index 6a3c5f9..31fda6d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java @@ -11,155 +11,152 @@ /** * Represents the portfolio of the Player, containing information * about which {@link Share}s are owned. - *

- * A {@code Portfolio} manages a mutable collection of shares, allowing - * shares to be added, removed, and queried. - *

* - *

- * Shares are associated with a unique Transaction, however, multiple - * shares from the same stock can exist in the portfolio. - *

+ *

A {@code Portfolio} manages a mutable collection of shares, allowing + * shares to be added, removed, and queried.

+ * + *

Shares are associated with a unique Transaction, however, multiple + * shares from the same stock can exist in the portfolio.

*/ public class Portfolio { - private final ObservableList shares; - private final ObjectProperty netWorthProperty = + private final ObservableList shares; + private final ObjectProperty netWorthProperty = new SimpleObjectProperty<>(BigDecimal.ZERO); - /** - * Creates an empty portfolio for the player. - */ - public Portfolio() { - this.shares = FXCollections.observableArrayList(); + /** + * Creates an empty portfolio for the player. + */ + public Portfolio() { + this.shares = FXCollections.observableArrayList(); + } + + /** + * Adds a share to the player's portfolio. + * + *

The {@link Stock} must not be {@code null} and the share quantity + * and purchase price must be above zero.

+ * + * @param share the share owned by the player; must not be {@code null} + * @return {@code true} if the share was added successfully, + * {@code false} otherwise + * @throws IllegalArgumentException if {@code share} is {@code null} + */ + public boolean addShare(Share share) { + if (share == null) { + throw new IllegalArgumentException("Share cannot be null"); } - /** - * Adds a share to the player's portfolio. - *

- * The {@link Stock} must not be {@code null} and the share quantity - * and purchase price must be above zero. - *

- * - * @param share the share owned by the player; must not be {@code null} - * @return {@code true} if the share was added successfully, - * {@code false} otherwise - * @throws IllegalArgumentException if {@code share} is {@code null} - */ - public boolean addShare(Share share) { - if (share == null) { - throw new IllegalArgumentException("Share cannot be null"); - } - - this.shares.add(share); - - share.getStock().salesPriceProperty() + this.shares.add(share); + + share.getStock().salesPriceProperty() .subscribe(price -> updateNetWorth()); - updateNetWorth(); - return true; + updateNetWorth(); + + return true; + } + + /** + * Removes a share from the player's portfolio. + * + *

The list must already exist in the portfolio to be successfully + * removed.

+ * + * @param share the share being removed; must not be {@code null} + * @return {@code true} if the share removal was successful, + * {@code false} otherwise. + * @throws IllegalArgumentException if {@code share} is {@code null} + */ + public boolean removeShare(Share share) { + if (share == null) { + throw new IllegalArgumentException("Share cannot be null"); } - /** - * Removes a share from the player's portfolio. - *

- * The list must already exist in the portfolio to be successfully - * removed. - *

- * - * @param share the share being removed; must not be {@code null} - * @return {@code true} if the share removal was successful, - * {@code false} otherwise. - * @throws IllegalArgumentException if {@code share} is {@code null} - */ - public boolean removeShare(Share share) { - if (share == null) { - throw new IllegalArgumentException("Share cannot be null"); - } - - boolean removed = this.shares.removeIf(s -> s.equals(share)); - - if (removed) { - updateNetWorth(); - } - - return removed; - } + boolean removed = this.shares.removeIf(s -> s.equals(share)); - /** - * Returns a list of the shares currently in the player's portfolio. - * - * @return a list of the shares in this portfolio - */ - public ObservableList getShares() { - return this.shares; + if (removed) { + updateNetWorth(); } - /** - * Returns a list of the shares that belongs to the stock with the - * given ticker symbol. - * - * @param symbol the ticker symbol of the stock to search for; must not be - * {@code null} or blank - * @return a list of the player's shares in the stock with the given - * stock symbol; an empty list if none are found - * @throws IllegalArgumentException if {@code symbol} is {@code null} or blank - */ - public List getShares(String symbol) { - if (symbol == null || symbol.isBlank()) { - throw new IllegalArgumentException("Symbol cannot be null or blank"); - } - - var tempSymbols = new ArrayList(); - for (Share s : this.shares) { - if (s.getStock().getSymbol().equals(symbol)) { - tempSymbols.add(s); - } - } - return tempSymbols; + return removed; + } + + /** + * Returns a list of the shares currently in the player's portfolio. + * + * @return a list of the shares in this portfolio + */ + public ObservableList getShares() { + return this.shares; + } + + /** + * Returns a list of the shares that belongs to the stock with the + * given ticker symbol. + * + * @param symbol the ticker symbol of the stock to search for; must not be + * {@code null} or blank + * @return a list of the player's shares in the stock with the given + * stock symbol; an empty list if none are found + * @throws IllegalArgumentException if {@code symbol} is {@code null} or blank + */ + public List getShares(String symbol) { + if (symbol == null || symbol.isBlank()) { + throw new IllegalArgumentException("Symbol cannot be null or blank"); } - /** - * Determines whether the player's portfolio contains the specified - * share. - * - * @param share the share to search for in this portfolio - * @return {@code true} if the player's portfolio contains the share - * {@code false} otherwise - */ - public boolean contains(Share share) { - return shares.stream().anyMatch(s -> s.equals(share)); + var tempSymbols = new ArrayList(); + for (Share s : this.shares) { + + if (s.getStock().getSymbol().equals(symbol)) { + tempSymbols.add(s); + } } - /** - * Calculates the net total value of the shares in the portfolio. - *

- * Portfolio value is based on current market price, not transaction rules. - *

- * - * @return the total market value of the portfolio - */ - public BigDecimal getNetWorth() { - return this.shares.stream() + return tempSymbols; + } + + /** + * Determines whether the player's portfolio contains the specified + * share. + * + * @param share the share to search for in this portfolio + * @return {@code true} if the player's portfolio contains the share + * {@code false} otherwise + */ + public boolean contains(Share share) { + return shares.stream().anyMatch(s -> s.equals(share)); + } + + /** + * Calculates the net total value of the shares in the portfolio. + * + *

Portfolio value is based on current market price, not transaction rules.

+ * + * @return the total market value of the portfolio + */ + public BigDecimal getNetWorth() { + return this.shares.stream() .map(share -> share.getStock().getSalesPrice() .multiply(share.getQuantity()) ) .reduce(BigDecimal.ZERO, BigDecimal::add); - } - - /** - * Returns an {@link ObjectProperty} of the net worth of the portfolio - * - * @return an observable of the portfolio's net worth - */ - public ObjectProperty netWorthProperty() { - return netWorthProperty; - } - - /** - * Mirrors the portfolio's net worth to the observable. - */ - private void updateNetWorth() { - netWorthProperty.set(getNetWorth()); - } + } + + /** + * Returns an {@link ObjectProperty} of the net worth of the portfolio. + * + * @return an observable of the portfolio's net worth + */ + public ObjectProperty netWorthProperty() { + return netWorthProperty; + } + + /** + * Mirrors the portfolio's net worth to the observable. + */ + private void updateNetWorth() { + netWorthProperty.set(getNetWorth()); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java b/millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java index dbc9a5d..ddb3ada 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java @@ -4,9 +4,9 @@ /** * Represents a purchase {@link Transaction} where a {@link Player} purchases - * a {@link Share} - *

- * A purchase can only be commited if: + * a {@link Share}. + * + *

A purchase can only be commited if: *

    *
  • The player has sufficient funds.
  • *
  • The transaction has not already been committed.
  • @@ -14,49 +14,50 @@ *

    */ -public class Purchase extends Transaction{ - - /** - * Constructs a purchase transaction for the given share and week. - * - * @param share the share to be purchased; must not be {@code null} - * @param week the week when the purchase transaction occurs; must be 1 or greater - */ - - public Purchase(Share share, int week) { - super(share, week, new PurchaseCalculator(share)); +public class Purchase extends Transaction { + + /** + * Constructs a purchase transaction for the given share and week. + * + * @param share the share to be purchased; must not be {@code null} + * @param week the week when the purchase transaction occurs; must be 1 or greater + */ + + public Purchase(Share share, int week) { + super(share, week, new PurchaseCalculator(share)); + } + + /** + * Commits the purchase to the given player, making the + * transaction unique. + * + *

    If the transaction is not already committed, and the player + * has sufficient funds, the transaction will: + *

      + *
    • Withdraw money from the player
    • + *
    • Add the share to the player's {@link Portfolio}
    • + *
    • Archive the transaction in the player's + * {@link TransactionArchive}
    • + *
    + *

    + * + * @param player the player performing the transaction; must not be {@code null} + * @throws IllegalArgumentException if {@code player} is {@code null} + */ + + @Override + public void commit(Player player) { + if (player == null) { + throw new IllegalArgumentException("Player cannot be null."); } - /** - * Commits the purchase to the given player, making the - * transaction unique. - *

    - * If the transaction is not already committed, and the player - * has sufficient funds, the transaction will: - *

      - *
    • Withdraw money from the player
    • - *
    • Add the share to the player's {@link Portfolio}
    • - *
    • Archive the transaction in the player's - * {@link TransactionArchive}
    • - *
    - *

    - * @param player the player performing the transaction; must not be {@code null} - * @throws IllegalArgumentException if {@code player} is {@code null} - */ - - @Override - public void commit(Player player) { - if (player == null) { - throw new IllegalArgumentException("Player cannot be null."); - } - - if (!this.isCommitted() && ((player.getMoney().subtract(this.getCalculator().calculateTotal())) + if (!this.isCommitted() && ((player.getMoney().subtract(this.getCalculator().calculateTotal())) .compareTo(BigDecimal.ZERO) >= 0)) { - player.withdrawMoney(this.getCalculator().calculateTotal()); - player.getPortfolio().addShare(this.getShare()); - player.getTransactionArchive().add(this); - super.commit(player); - } + player.withdrawMoney(this.getCalculator().calculateTotal()); + player.getPortfolio().addShare(this.getShare()); + player.getTransactionArchive().add(this); + super.commit(player); } + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java index a98e809..08865b9 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java @@ -1,115 +1,110 @@ package no.ntnu.gruppe53.model; +import java.math.BigDecimal; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; -import java.math.BigDecimal; - /** - * Calculates the costs associated with a {@link Purchase} of a given amount of {@link Share}s on the - * {@link Exchange}. + * Calculates the costs associated with a {@link Purchase} of a given + * amount of {@link Share}s on the {@link Exchange}. */ -public class PurchaseCalculator implements TransactionCalculator{ - private final BigDecimal purchasePrice; - private final BigDecimal quantity; - - //Objectproperties - private final ObjectProperty grossProperty = new SimpleObjectProperty<>(); - private final ObjectProperty commissionProperty = new SimpleObjectProperty<>(); - private final ObjectProperty totalProperty = new SimpleObjectProperty<>(); - - - /** - * Sets the purchase price and quantity based on data from the given share - *

    Also sets up the ObjectProperties of the different calculations such - * that Subscriptions can listen to the calculations

    - * @param share the share to calculate purchase costs for - */ - public PurchaseCalculator(Share share) { - - this.purchasePrice = share.getPurchasePrice(); - this.quantity = share.getQuantity(); - - this.grossProperty.set(calculateGross()); - this.commissionProperty.set(calculateCommission()); - this.totalProperty.set(calculateTotal()); - - } - - /** - * Calculates the gross purchase price of the given share. - * - * @return the gross associated cost of purchasing the share - */ - @Override - public BigDecimal calculateGross(){ - - return this.quantity.multiply(this.purchasePrice); - } - - /** - * Calculates the commission of the exchange for purchasing a share. - *

    Commission is set to a fixed 0.5% of gross purchase price.

    - * - * @return the commission taken by the exchange - */ - @Override +public class PurchaseCalculator implements TransactionCalculator { + private final BigDecimal purchasePrice; + private final BigDecimal quantity; + + // Objectproperties + private final ObjectProperty grossProperty = new SimpleObjectProperty<>(); + private final ObjectProperty commissionProperty = new SimpleObjectProperty<>(); + private final ObjectProperty totalProperty = new SimpleObjectProperty<>(); + + /** + * Sets the purchase price and quantity based on data from the given share. + * + *

    Also sets up the ObjectProperties of the different calculations such + * that Subscriptions can listen to the calculations

    + * + * @param share the share to calculate purchase costs for + */ + public PurchaseCalculator(Share share) { + this.purchasePrice = share.getPurchasePrice(); + this.quantity = share.getQuantity(); + + this.grossProperty.set(calculateGross()); + this.commissionProperty.set(calculateCommission()); + this.totalProperty.set(calculateTotal()); + + } + + /** + * Calculates the gross purchase price of the given share. + * + * @return the gross associated cost of purchasing the share + */ + @Override + public BigDecimal calculateGross() { + return this.quantity.multiply(this.purchasePrice); + } + + /** + * Calculates the commission of the exchange for purchasing a share. + * + *

    Commission is set to a fixed 0.5% of gross purchase price.

    + * + * @return the commission taken by the exchange + */ + @Override public BigDecimal calculateCommission() { - BigDecimal commission = new BigDecimal("0.005"); - - - - return calculateGross().multiply(commission); - } - - /** - * Calculates the tax for a purchase. - *

    For a purchase, there is no tax.

    - * - * @return the tax for a purchase, in this case zero - */ - @Override - public BigDecimal calculateTax() { - return new BigDecimal(0); - } - - /** - * Calculates the total costs associated with the purchase of a share - * - * @return the net total cost of purchasing a share - */ - @Override - public BigDecimal calculateTotal() { - - - return calculateGross().add(calculateCommission()).add(calculateTax()); - } - - /** - * Returns an observable of the calculated gross - * @return an observable of the calculated gross - */ - public ObjectProperty getGrossProperty() { - return grossProperty; - } - - /** - * Returns an observable of the calculated commission - * @return an observable of the calculated commission - */ - public ObjectProperty getCommissionProperty() { - return commissionProperty; - } - - /** - * Returns an observable of the calculated total - * @return an observable of the calculated total - */ - public ObjectProperty getTotalProperty() { - return totalProperty; - } - - - - + BigDecimal commission = new BigDecimal("0.005"); + + return calculateGross().multiply(commission); + } + + /** + * Calculates the tax for a purchase. + * + *

    For a purchase, there is no tax.

    + * + * @return the tax for a purchase, in this case zero + */ + @Override + public BigDecimal calculateTax() { + return new BigDecimal(0); + } + + /** + * Calculates the total costs associated with the purchase of a share. + * + * @return the net total cost of purchasing a share + */ + @Override + public BigDecimal calculateTotal() { + return calculateGross().add(calculateCommission()).add(calculateTax()); + } + + /** + * Returns an observable of the calculated gross. + * + * @return an observable of the calculated gross + */ + public ObjectProperty getGrossProperty() { + return grossProperty; + } + + /** + * Returns an observable of the calculated commission. + * + * @return an observable of the calculated commission + */ + public ObjectProperty getCommissionProperty() { + return commissionProperty; + } + + /** + * Returns an observable of the calculated total. + * + * @return an observable of the calculated total + */ + public ObjectProperty getTotalProperty() { + return totalProperty; + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseFactory.java b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseFactory.java index ffce90a..a05169f 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseFactory.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseFactory.java @@ -3,17 +3,17 @@ /** * Creates a {@link Purchase} {@link Transaction} of a {@link Share}. */ -public class PurchaseFactory extends TransactionFactory{ +public class PurchaseFactory extends TransactionFactory { - /** - * Returns the purchase transaction for the given share and week. - * - * @param share the share to be purchased - * @param week the week of purchase - * @return the purchase transaction - */ - @Override - protected Transaction createTransaction(Share share, int week) { - return new Purchase(share, week); - } + /** + * Returns the purchase transaction for the given share and week. + * + * @param share the share to be purchased + * @param week the week of purchase + * @return the purchase transaction + */ + @Override + protected Transaction createTransaction(Share share, int week) { + return new Purchase(share, week); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java b/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java index 732ec1f..ff418ee 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java @@ -2,9 +2,9 @@ /** * Represents a sale {@link Transaction} where a {@link Player} sells - * a {@link Share} - *

    - * A sale can only be commited if: + * a {@link Share}. + * + *

    A sale can only be commited if: *

      *
    • The share is in the players {@link Portfolio}
    • *
    • The transaction has not already committed
    • @@ -12,52 +12,54 @@ *

      */ -public class Sale extends Transaction{ +public class Sale extends Transaction { - /** - * Constructs a sale transaction for the given share and week. - * - * @param share the share to be sold; must not be {@code null} - * @param week the week when the sale transaction occurs; must be greater than 0 - */ + /** + * Constructs a sale transaction for the given share and week. + * + * @param share the share to be sold; must not be {@code null} + * @param week the week when the sale transaction occurs; must be greater than 0 + */ - public Sale(Share share, int week) { - super(share, week, new SaleCalculator(share)); + public Sale(Share share, int week) { + super(share, week, new SaleCalculator(share)); + } + + /** + * Commits the sale to the given player, making the + * transaction unique. + * + *

      If the player ows the share, the transaction will: + *

        + *
      • Add money to the player
      • + *
      • Remove the share from the player's portfolio
      • + *
      • Archive the transaction in the player's {@link TransactionArchive}
      • + *
      + *

      + * + * @param player the player performing the transaction; must not be {@code null} + * @throws IllegalArgumentException if {@code player} is {@code null} + * @throws IllegalStateException if trying to sell a share not in the player's portfolio or + * if transactions has already been committed. + */ + + @Override + public void commit(Player player) { + if (player == null) { + throw new IllegalArgumentException("Player cannot be null"); } - /** - * Commits the sale to the given player, making the - * transaction unique. - *

      - * If the player ows the share, the transaction will: - *

        - *
      • Add money to the player
      • - *
      • Remove the share from the player's portfolio
      • - *
      • Archive the transaction in the player's {@link TransactionArchive}
      • - *
      - *

      - * @param player the player performing the transaction; must not be {@code null} - * @throws IllegalArgumentException if {@code player} is {@code null} - * @throws IllegalStateException if trying to sell a share not in the player's portfolio or if transactions - * has already been committed. - */ - - @Override - public void commit(Player player) { - if (player == null) { - throw new IllegalArgumentException("Player cannot be null"); - } - - if (this.isCommitted()) { - throw new IllegalStateException("Transaction has already been committed."); - } - if (!player.getPortfolio().contains(this.getShare())) { - throw new IllegalStateException("You do not own this share in your portfolio."); - } - player.addMoney(this.getCalculator().calculateTotal()); - player.getPortfolio().removeShare(this.getShare()); - player.getTransactionArchive().add(this); - super.commit(player); + if (this.isCommitted()) { + throw new IllegalStateException("Transaction has already been committed."); + } + if (!player.getPortfolio().contains(this.getShare())) { + throw new IllegalStateException("You do not own this share in your portfolio."); } -} + + player.addMoney(this.getCalculator().calculateTotal()); + player.getPortfolio().removeShare(this.getShare()); + player.getTransactionArchive().add(this); + super.commit(player); + } +} \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java index c6afe07..a108d86 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java @@ -1,146 +1,153 @@ package no.ntnu.gruppe53.model; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; - import java.math.BigDecimal; import java.math.RoundingMode; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; /** * Calculates the costs associated with the {@link Sale} of a {@link Share} on the {@link Exchange}, * and the net total gain of selling said share. */ public class SaleCalculator implements TransactionCalculator { - - private final BigDecimal purchasePrice; - private final BigDecimal salesPrice; - private final BigDecimal quantity; - - //Objectproperties - private final ObjectProperty grossProperty = new SimpleObjectProperty<>(); - private final ObjectProperty commissionProperty = new SimpleObjectProperty<>(); - private final ObjectProperty taxProperty = new SimpleObjectProperty<>(); - private final ObjectProperty totalProperty = new SimpleObjectProperty<>(); - private final ObjectProperty profitProperty = new SimpleObjectProperty<>(); - - /** - * Sets the quantity, purchase and sales price based on the given share. - * - * @param share the share to calculate the costs and net total of a sale - */ - public SaleCalculator(Share share) { - this.purchasePrice = share.getPurchasePrice(); - this.quantity = share.getQuantity(); - this.salesPrice = share.getStock().getSalesPrice(); - - this.grossProperty.set(calculateGross()); - this.commissionProperty.set(calculateCommission()); - this.taxProperty.set(calculateTax()); - this.totalProperty.set(calculateTotal()); - this.profitProperty.set(calculateTotal().subtract(purchasePrice.multiply(quantity))); - } - - /** - * Calculates the gross sales price of the given share. - * - * @return the gross associated gain from selling the share - */ - @Override - public BigDecimal calculateGross() { - return quantity.multiply(salesPrice) + private final BigDecimal purchasePrice; + private final BigDecimal salesPrice; + private final BigDecimal quantity; + + // Objectproperties + private final ObjectProperty grossProperty = new SimpleObjectProperty<>(); + private final ObjectProperty commissionProperty = new SimpleObjectProperty<>(); + private final ObjectProperty taxProperty = new SimpleObjectProperty<>(); + private final ObjectProperty totalProperty = new SimpleObjectProperty<>(); + private final ObjectProperty profitProperty = new SimpleObjectProperty<>(); + + /** + * Sets the quantity, purchase and sales price based on the given share. + * + * @param share the share to calculate the costs and net total of a sale + */ + public SaleCalculator(Share share) { + this.purchasePrice = share.getPurchasePrice(); + this.quantity = share.getQuantity(); + this.salesPrice = share.getStock().getSalesPrice(); + + this.grossProperty.set(calculateGross()); + this.commissionProperty.set(calculateCommission()); + this.taxProperty.set(calculateTax()); + this.totalProperty.set(calculateTotal()); + this.profitProperty.set(calculateTotal().subtract(purchasePrice.multiply(quantity))); + } + + /** + * Calculates the gross sales price of the given share. + * + * @return the gross associated gain from selling the share + */ + @Override + public BigDecimal calculateGross() { + return quantity.multiply(salesPrice) .setScale(2, RoundingMode.HALF_UP); - } - - /** - * Calculates the exchange's commission for selling the share. - *

      The commission is set to a fixed 1% of the gross total sale value.

      - * - * @return the commission taken by the exchange - */ - @Override + } + + /** + * Calculates the exchange's commission for selling the share. + * + *

      The commission is set to a fixed 1% of the gross total sale value.

      + * + * @return the commission taken by the exchange + */ + @Override public BigDecimal calculateCommission() { - BigDecimal commissionRate = new BigDecimal("0.01"); + BigDecimal commissionRate = new BigDecimal("0.01"); - return calculateGross() + return calculateGross() .multiply(commissionRate) .setScale(2, RoundingMode.HALF_UP); - } + } - /** - * Calculates the taxable profit from the sale. - *

      Only calculates tax for profitable sales.

      > - * - * @return the tax amount - */ - @Override - public BigDecimal calculateTax() { + /** + * Calculates the taxable profit from the sale. + * + *

      Only calculates tax for profitable sales.

      > + * + * @return the tax amount + */ + @Override + public BigDecimal calculateTax() { - BigDecimal taxRate = new BigDecimal("0.30"); + BigDecimal taxRate = new BigDecimal("0.30"); - BigDecimal originalCost = purchasePrice.multiply(quantity); + BigDecimal originalCost = purchasePrice.multiply(quantity); - BigDecimal profit = calculateGross() + BigDecimal profit = calculateGross() .subtract(calculateCommission()) .subtract(originalCost); - if (profit.compareTo(BigDecimal.ZERO) <= 0) { - return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); - } + if (profit.compareTo(BigDecimal.ZERO) <= 0) { + return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); + } - return profit.multiply(taxRate) + return profit.multiply(taxRate) .setScale(2, RoundingMode.HALF_UP); - } + } - /** - * Returns the net total gain for selling the share. - * - * @return the net total winnings of selling the share - */ - @Override - public BigDecimal calculateTotal() { + /** + * Returns the net total gain for selling the share. + * + * @return the net total winnings of selling the share + */ + @Override + public BigDecimal calculateTotal() { - return calculateGross() + return calculateGross() .subtract(calculateCommission()) .subtract(calculateTax()) .setScale(2, RoundingMode.HALF_UP); - } - - /** - * Returns an observable of the calculated gross - * @return an observable of the calculated gross - */ - public ObjectProperty getGrossProperty() { - return grossProperty; - } - - /** - * Returns an observable of the calculated commission - * @return an observable of the calculated commission - */ - public ObjectProperty getCommissionProperty() { - return commissionProperty; - } - - /** - * Returns an observable of the calculated tax - * @return an observable of the calculated tax - */ - public ObjectProperty getTaxProperty() { - return taxProperty; - } - - /** - * Returns an observable of the calculated total - * @return an observable of the calculated total - */ - public ObjectProperty getTotalProperty() { - return totalProperty; - } - - /** - * Returns an observable of the calculated profit - * @return an observable of the calculated profit - */ - public ObjectProperty getProfitProperty() {return profitProperty;} + } + + /** + * Returns an observable of the calculated gross. + * + * @return an observable of the calculated gross + */ + public ObjectProperty getGrossProperty() { + return grossProperty; + } + + /** + * Returns an observable of the calculated commission. + * + * @return an observable of the calculated commission + */ + public ObjectProperty getCommissionProperty() { + return commissionProperty; + } + + /** + * Returns an observable of the calculated tax. + * + * @return an observable of the calculated tax + */ + public ObjectProperty getTaxProperty() { + return taxProperty; + } + + /** + * Returns an observable of the calculated total. + * + * @return an observable of the calculated total + */ + public ObjectProperty getTotalProperty() { + return totalProperty; + } + + /** + * Returns an observable of the calculated profit. + * + * @return an observable of the calculated profit + */ + public ObjectProperty getProfitProperty() { + return profitProperty; + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/SaleFactory.java b/millions/src/main/java/no/ntnu/gruppe53/model/SaleFactory.java index 2340e5c..9128a74 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/SaleFactory.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/SaleFactory.java @@ -5,15 +5,15 @@ */ public class SaleFactory extends TransactionFactory { - /** - * Returns the sale transaction of the given share and week. - * - * @param share the share to be sold - * @param week the week of the sale - * @return the sale transaction - */ - @Override - protected Transaction createTransaction(Share share, int week) { - return new Sale(share, week); - } + /** + * Returns the sale transaction of the given share and week. + * + * @param share the share to be sold + * @param week the week of the sale + * @return the sale transaction + */ + @Override + protected Transaction createTransaction(Share share, int week) { + return new Sale(share, week); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Share.java b/millions/src/main/java/no/ntnu/gruppe53/model/Share.java index f2ec731..229c473 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Share.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Share.java @@ -4,72 +4,73 @@ /** * Represents a holding of a specific {@link Stock}. - *

      - * A {@code Share} contains the quantity of shares owned and + * + *

      A {@code Share} contains the quantity of shares owned and * the price per share at the time of purchase. Future price * fluctuations are not included in share. *

      */ public class Share { - private final Stock stock; - private final BigDecimal quantity; - private final BigDecimal purchasePrice; + private final Stock stock; + private final BigDecimal quantity; + private final BigDecimal purchasePrice; - /** - * Creates a share for a given stock. - * - * @param stock the stock associated with the share; must not be {@code null} - * @param quantity the number of shares purchased; must not be {@code null}, negative, or zero - * @param purchasePrice price per share at the time of purchase; must not be {@code null}, negative or zero - * @throws IllegalArgumentException if {@code stock} is {@code null}, or {@code quantity} is {@code null}, - * negative, or zero, or {@code purchasePrice} is {@code null}, negative, - * or zero - */ - - public Share(Stock stock, BigDecimal quantity, BigDecimal purchasePrice) { - if (stock == null) { - throw new IllegalArgumentException("Stock cannot be null."); - } - - if (quantity == null || quantity.signum() <= 0) { - throw new IllegalArgumentException("Quantity cannot be null, negative, or zero."); - } - - if (purchasePrice == null || purchasePrice.signum() <= 0) { - throw new IllegalArgumentException("PurchasePrice cannot be null, negative, or zero."); - } + /** + * Creates a share for a given stock. + * + * @param stock the stock associated with the share; must not be {@code null} + * @param quantity the number of shares purchased; must not be {@code null}, negative, or zero + * @param purchasePrice price per share at the time of purchase; + * must not be {@code null}, negative or zero + * @throws IllegalArgumentException if {@code stock} is {@code null}, + * or {@code quantity} is {@code null}, + * negative, or zero, or {@code purchasePrice} + * is {@code null}, negative, or zero + */ + public Share(Stock stock, BigDecimal quantity, BigDecimal purchasePrice) { + if (stock == null) { + throw new IllegalArgumentException("Stock cannot be null."); + } - this.stock = stock; - this.quantity = quantity; - this.purchasePrice = purchasePrice; + if (quantity == null || quantity.signum() <= 0) { + throw new IllegalArgumentException("Quantity cannot be null, negative, or zero."); } - /** - * Returns the stock the share is associated with. - * - * @return the stock - */ - public Stock getStock() { - return stock; + if (purchasePrice == null || purchasePrice.signum() <= 0) { + throw new IllegalArgumentException("PurchasePrice cannot be null, negative, or zero."); } - /** - * Returns the quantity of shares owned in the stock. - * - * @return the quantity of shares - */ + this.stock = stock; + this.quantity = quantity; + this.purchasePrice = purchasePrice; + } - public BigDecimal getQuantity() { - return quantity; - } + /** + * Returns the stock the share is associated with. + * + * @return the stock + */ + public Stock getStock() { + return stock; + } - /** - * Returns the purchase price per share for the stock at time of purchase. - * - * @return the purchase price of the shares. - */ - public BigDecimal getPurchasePrice() { - return purchasePrice; - } + /** + * Returns the quantity of shares owned in the stock. + * + * @return the quantity of shares + */ + + public BigDecimal getQuantity() { + return quantity; + } + + /** + * Returns the purchase price per share for the stock at time of purchase. + * + * @return the purchase price of the shares. + */ + public BigDecimal getPurchasePrice() { + return purchasePrice; + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java b/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java index 6fe5424..04c53c8 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java @@ -1,162 +1,163 @@ package no.ntnu.gruppe53.model; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; - import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; /** * Represents a stock with its ticker symbol, company name, * and a list of sales prices. - *

      - * The list is a historical representation of price changes + * + *

      The list is a historical representation of price changes * over time for the given stock. *

      - *

      - * The most recently added price represents the current market price. - *

      * - *

      - * A {@code Stock} instance always contains at least one price, - * instantiated at construction. + *

      The most recently added price represents the current market price. *

      + * + *

      A {@code Stock} instance always contains at least one price, + * instantiated at construction.

      */ public class Stock { - private final String symbol; - private final String company; - private final List prices; - - private final ObjectProperty salesPrice; - - /** - * Creates a stock with an already defined sales price. - * - * @param symbol the stock's unique ticker symbol (e.g. "AAPL"); must not be {@code null} or blank - * @param company the full name of the company; must not be {@code null} or blank - * @param salesPrice the current price per share; must not be {@code null}, negative, or zero - * @throws IllegalArgumentException if {@code symbol} is {@code null} or blank, {@code company} is - * {@code null} or blank, or {@code salesPrice} is {@code null}, - * negative, or zero - */ - public Stock(String symbol, String company, BigDecimal salesPrice) { - if (symbol == null || symbol.isBlank()) { - throw new IllegalArgumentException("Symbol cannot be null or empty."); - } - if (company == null || company.isBlank()) { - throw new IllegalArgumentException("Company cannot be null or empty."); - } - if (salesPrice == null || salesPrice.signum() <= 0) { - throw new IllegalArgumentException("SalesPrice cannot be null, negative, or zero."); - } - - this.symbol = symbol; - this.company = company; - this.prices = new ArrayList<>(); - this.prices.add(salesPrice); - - this.salesPrice = new SimpleObjectProperty<>(salesPrice); - } - - /** - * Returns the stock's unique ticker symbol. - * - * @return the stock's ticker symbol - */ - public String getSymbol() { - return symbol; - } - - /** - * Returns the full name of the company associated with the stock. - * - * @return the full name of the stock's company - */ - public String getCompany() { - return company; - } - - /** - * Returns the current sale price per share of the stock. - *

      - * The current price is defined as the most recent price in the price history. - *

      - * @return the current sale price of the stock - */ - public BigDecimal getSalesPrice() { - return salesPrice.get(); - } - - /** - * Adds a new sales price to the stock's price history. - *

      - * The added price becomes the new current market price. - *

      - * - *

      - * The new price must be positive. - *

      - * @param price the new market price per share of the stock; must not be {@code null}, negative or zero - * @throws IllegalArgumentException if {@code price} is {@code null}, negative, or zero - */ - public void addNewSalesPrice(BigDecimal price) { - if (price == null || price.signum() <= 0) { - throw new IllegalArgumentException("Price cannot be null, negative, or zero."); - } - this.prices.add(price); - this.salesPrice.set(price); - } - - - /** - * Returns an {@link ObjectProperty} of the sales price. - * - * @return an observable of the sales price - */ - public ObjectProperty salesPriceProperty() { - return salesPrice; + private final String symbol; + private final String company; + private final List prices; + + private final ObjectProperty salesPrice; + + /** + * Creates a stock with an already defined sales price. + * + * @param symbol the stock's unique ticker symbol (e.g. "AAPL"); must not be {@code null} or blank + * @param company the full name of the company; must not be {@code null} or blank + * @param salesPrice the current price per share; must not be {@code null}, negative, or zero + * @throws IllegalArgumentException if {@code symbol} is {@code null} or blank, {@code company} is + * {@code null} or blank, or {@code salesPrice} is {@code null}, + * negative, or zero + */ + public Stock( + String symbol, String company, BigDecimal salesPrice) { + + if (symbol == null || symbol.isBlank()) { + throw new IllegalArgumentException("Symbol cannot be null or empty."); } - /** - * Returns a list of all the sales prices for this stock since (and including) week 1. - * - * @return a list of all the stock's sales prices - */ - public List getHistoricalPrices() { - return prices; + if (company == null || company.isBlank()) { + throw new IllegalArgumentException("Company cannot be null or empty."); } - /** - * Returns the maximum historical sales price since (and including) week 1 for the stock. - * - * @return the stock's highest sales price - */ - public BigDecimal getHighestPrice() { - return Collections.max(prices); + if (salesPrice == null || salesPrice.signum() <= 0) { + throw new IllegalArgumentException("SalesPrice cannot be null, negative, or zero."); } - /** - * Returns the minimum historical sales price since (and including) week 1 for the stock. - * - * @return the stock's lowest sales price - */ - public BigDecimal getLowestPrice() { - return Collections.min(prices); + this.symbol = symbol; + this.company = company; + this.prices = new ArrayList<>(); + this.prices.add(salesPrice); + + this.salesPrice = new SimpleObjectProperty<>(salesPrice); + } + + /** + * Returns the stock's unique ticker symbol. + * + * @return the stock's ticker symbol + */ + public String getSymbol() { + return symbol; + } + + /** + * Returns the full name of the company associated with the stock. + * + * @return the full name of the stock's company + */ + public String getCompany() { + return company; + } + + /** + * Returns the current sale price per share of the stock. + * + *

      The current price is defined as the most recent price + * in the price history.

      + * + * @return the current sale price of the stock + */ + public BigDecimal getSalesPrice() { + return salesPrice.get(); + } + + /** + * Adds a new sales price to the stock's price history. + * + *

      The added price becomes the new current market price.

      + * + *

      The new price must be positive.

      + * + * @param price the new market price per share of the stock; + * must not be {@code null}, negative or zero + * @throws IllegalArgumentException if {@code price} is {@code null}, negative, or zero + */ + public void addNewSalesPrice(BigDecimal price) { + if (price == null || price.signum() <= 0) { + throw new IllegalArgumentException("Price cannot be null, negative, or zero."); } - /** - * Returns the sales price increase/decrease from previous week to this week. Returns 0 if - * the price list only has 1 value. - * - * @return the net difference in sales price between the last and second last week - */ - public BigDecimal getLatestPriceChange() { - if (prices.size() >= 2) { - return prices.getLast().subtract(prices.get(prices.size() - 2)); - } else { - return BigDecimal.ZERO; - } + this.prices.add(price); + this.salesPrice.set(price); + } + + /** + * Returns an {@link ObjectProperty} of the sales price. + * + * @return an observable of the sales price + */ + public ObjectProperty salesPriceProperty() { + return salesPrice; + } + + /** + * Returns a list of all the sales prices for this stock since (and including) week 1. + * + * @return a list of all the stock's sales prices + */ + public List getHistoricalPrices() { + return prices; + } + + /** + * Returns the maximum historical sales price since (and including) week 1 for the stock. + * + * @return the stock's highest sales price + */ + public BigDecimal getHighestPrice() { + return Collections.max(prices); + } + + /** + * Returns the minimum historical sales price since (and including) week 1 for the stock. + * + * @return the stock's lowest sales price + */ + public BigDecimal getLowestPrice() { + return Collections.min(prices); + } + + /** + * Returns the sales price increase/decrease from previous week to this week. Returns 0 if + * the price list only has 1 value. + * + * @return the net difference in sales price between the last and second last week + */ + public BigDecimal getLatestPriceChange() { + if (prices.size() >= 2) { + return prices.getLast().subtract(prices.get(prices.size() - 2)); + } else { + return BigDecimal.ZERO; } + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java b/millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java index bb661c0..ee2f11c 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java @@ -2,98 +2,93 @@ /** * Represents a financial transaction of a {@link Share}. - *

      - * A transaction occurs in a specific week and uses a + * + *

      A transaction occurs in a specific week and uses a * {@link TransactionCalculator} to determine the monetary change. * Transactions are unique and is committed to a {@link Player}'s - * {@link Portfolio}. - *

      + * {@link Portfolio}.

      * - *

      - * Subclasses define the nature of the transaction (e.g. purchase - * or sale) by implementing the {@link #commit(Player)} - *

      + *

      Subclasses define the nature of the transaction (e.g. purchase + * or sale) by implementing the {@link #commit(Player)}

      */ +public abstract class Transaction { + private final Share share; + private final int week; + private final TransactionCalculator calculator; + protected boolean committed; -abstract public class Transaction { - private final Share share; - private final int week; - private final TransactionCalculator calculator; - protected boolean committed; - - /** - * Constructor for a transaction. - * - * @param share the share for the transaction; must not be {@code null} - * @param week the week the transaction takes place; must be greater than 0 - * @param calculator the transaction calculator used to - * compute the {@link Transaction} - * @throws IllegalArgumentException if {@code share} is {@code null} or {@code week} - * is less than 1 - */ - - protected Transaction(Share share, int week, TransactionCalculator calculator) { - if (week <= 0) { - throw new IllegalArgumentException("Week cannot be negative or zero"); - } + /** + * Constructor for a transaction. + * + * @param share the share for the transaction; must not be {@code null} + * @param week the week the transaction takes place; must be greater than 0 + * @param calculator the transaction calculator used to + * compute the {@link Transaction} + * @throws IllegalArgumentException if {@code share} is {@code null} or {@code week} + * is less than 1 + */ - this.share = share; - this.week = week; - this.calculator = calculator; - this.committed = false; + protected Transaction(Share share, int week, TransactionCalculator calculator) { + if (week <= 0) { + throw new IllegalArgumentException("Week cannot be negative or zero"); } - /** - * Returns the share for the current transaction. - * - * @return the share belonging to the transaction - */ - public Share getShare() { - return this.share; - } + this.share = share; + this.week = week; + this.calculator = calculator; + this.committed = false; + } - /** - * Returns the week the transaction took place. - * - * @return the week for the transaction - */ - public int getWeek() { - return this.week; - } + /** + * Returns the share for the current transaction. + * + * @return the share belonging to the transaction + */ + public Share getShare() { + return this.share; + } - /** - * Returns the transaction calculator used to calculate - * the transaction. - * - * @return the transaction calculator used for the transaction - */ + /** + * Returns the week the transaction took place. + * + * @return the week for the transaction + */ + public int getWeek() { + return this.week; + } - public TransactionCalculator getCalculator() { - return this.calculator; - } + /** + * Returns the transaction calculator used to calculate + * the transaction. + * + * @return the transaction calculator used for the transaction + */ - /** - * Returns whether the transaction has been committed to a player. - * - * @return {@code true} if committed, {@code false} otherwise - */ + public TransactionCalculator getCalculator() { + return this.calculator; + } - public boolean isCommitted() { - return this.committed; - } + /** + * Returns whether the transaction has been committed to a player. + * + * @return {@code true} if committed, {@code false} otherwise + */ - /** - * Commits the transaction to a player, making it unique. - * - *

      - * Subclasses should define the business logic required before - * committing. Then {@code super.commit(player)} should be called - * once the transaction has been successfully applied. - *

      - * @param player the player performing the transaction; must not be {@code null} - */ + public boolean isCommitted() { + return this.committed; + } - public void commit(Player player) { - this.committed = true; - } + /** + * Commits the transaction to a player, making it unique. + * + *

      Subclasses should define the business logic required before + * committing. Then {@code super.commit(player)} should be called + * once the transaction has been successfully applied.

      + * + * @param player the player performing the transaction; must not be {@code null} + */ + + public void commit(Player player) { + this.committed = true; + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java index 70bba67..88e5f7b 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java @@ -1,143 +1,145 @@ package no.ntnu.gruppe53.model; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; /** * Maintains a history of all completed {@link Transaction}s. - *

      - * A {@code TransactionArchive} stores transactions in the order + * + *

      A {@code TransactionArchive} stores transactions in the order * they are added and provides methods to retrieve transactions - * filtered by type and week. - *

      + * filtered by type and week.

      */ public class TransactionArchive { - private final ObservableList transactions; - - /** - * Creates an empty transaction archive. - */ - - public TransactionArchive() { - this.transactions = FXCollections.observableArrayList(); + private final ObservableList transactions; + + /** + * Creates an empty transaction archive. + */ + + public TransactionArchive() { + this.transactions = FXCollections.observableArrayList(); + } + + /** + * Adds a transaction to the archive. + * + * @param transaction the transaction being added; must not be {@code null} + * @return {@code true} if the transaction was successfully added + * {@code false} otherwise + * @throws IllegalArgumentException if {@code transaction} is {@code null} + */ + + public boolean add(Transaction transaction) { + if (transaction == null) { + throw new IllegalArgumentException("Transaction cannot be null."); } - /** - * Adds a transaction to the archive. - * - * @param transaction the transaction being added; must not be {@code null} - * @return {@code true} if the transaction was successfully added - * {@code false} otherwise - * @throws IllegalArgumentException if {@code transaction} is {@code null} - */ - - public boolean add(Transaction transaction) { - if (transaction == null) { - throw new IllegalArgumentException("Transaction cannot be null."); - } - - this.transactions.add(transaction); - - return this.getTransactions().get(transactions.size()-1).equals(transaction); + this.transactions.add(transaction); + + return this.getTransactions().get(transactions.size() - 1).equals(transaction); + } + + /** + * Checks whether there are no recorded transactions in the archive. + * + * @return {@code true} if the archive is empty. + * {@code false} otherwise + */ + + public boolean isEmpty() { + return this.transactions.isEmpty(); + } + + /** + * Returns all the transactions contained in the archive. + * + * @return the list of transactions + */ + + public ObservableList getObservableTransactions() { + return this.transactions; + } + + /** + * Returns the transactions in the transaction archive. + * + * @return the transactions in the transaction archive + */ + public List getTransactions() { + return this.transactions; + } + + /** + * Returns all {@link Purchase} transactions for the given week. + * + * @param week the week number for transactions; must be greater than 0 + * @return a list of purchase transactions for the given week; + * empty if none exist + * @throws IllegalArgumentException if {@code week} is not greater than 0 + */ + + public List getPurchases(int week) { + if (week <= 0) { + throw new IllegalArgumentException("Week must be greater than 0."); } + var purchaseList = new ArrayList(); - /** - * Checks whether there are no recorded transactions in the archive. - * - * @return {@code true} if the archive is empty. - * {@code false} otherwise - */ - - public boolean isEmpty() { - return this.transactions.isEmpty(); + for (Transaction t : this.transactions) { + if (t instanceof Purchase && t.getWeek() == week) { + purchaseList.add((Purchase) t); + } } - - /** - * Returns all the transactions contained in the archive. - * - * @return the list of transactions - */ - - public ObservableList getObservableTransactions() { - return this.transactions; + return purchaseList; + } + + /** + * Returns all {@link Sale} transactions for the given week. + * + * @param week the week number for transactions; must be greater than 0 + * @return a list of sale transactions for the given week; + * empty if none exist + * @throws IllegalArgumentException if {@code week} is not greater than 0 + */ + + public List getSales(int week) { + if (week <= 0) { + throw new IllegalArgumentException("Week must be greater than 0."); } - public List getTransactions() { - return this.transactions; - } + var saleList = new ArrayList(); - /** - * Returns all {@link Purchase} transactions for the given week. - * - * @param week the week number for transactions; must be greater than 0 - * @return a list of purchase transactions for the given week; - * empty if none exist - * @throws IllegalArgumentException if {@code week} is not greater than 0 - */ - - public List getPurchases(int week) { - if (week <= 0) { - throw new IllegalArgumentException("Week must be greater than 0."); - } - var purchaseList = new ArrayList(); - - for (Transaction t : this.transactions) { - if (t instanceof Purchase && t.getWeek() == week) { - purchaseList.add((Purchase) t); - } - } - return purchaseList; + for (Transaction t : this.transactions) { + if (t instanceof Sale && t.getWeek() == week) { + saleList.add((Sale) t); + } } - - /** - * Returns all {@link Sale} transactions for the given week. - * - * @param week the week number for transactions; must be greater than 0 - * @return a list of sale transactions for the given week; - * empty if none exist - * @throws IllegalArgumentException if {@code week} is not greater than 0 - */ - - public List getSales(int week) { - if (week <= 0) { - throw new IllegalArgumentException("Week must be greater than 0."); - } - - var saleList = new ArrayList(); - - for (Transaction t : this.transactions) { - if (t instanceof Sale && t.getWeek() == week) { - saleList.add((Sale) t); - } - } - return saleList; + return saleList; + } + + /** + * Counts the number of distinct week numbers represented in the + * transaction archive. + * + *

      A week is considered distinct if at least one transaction has + * occurred within that week. Multiple transactions in the span of the + * same week will only be counted as one distinct week.

      + * + * @return the number of unique week values in the archive; + * {@code 0} if the archive contains no transactions + */ + + public int countDistinctWeeks() { + var distinctWeeks = new HashSet(); + + for (Transaction t : this.transactions) { + distinctWeeks.add(t.getWeek()); } - /** - * Counts the number of distinct week numbers represented in the - * transaction archive. - *

      - * A week is considered distinct if at least one transaction has - * occurred within that week. Multiple transactions in the span of the - * same week will only be counted as one distinct week. - *

      - * - * @return the number of unique week values in the archive; - * {@code 0} if the archive contains no transactions - */ - - public int countDistinctWeeks() { - var distinctWeeks = new HashSet(); - - for (Transaction t : this.transactions) { - distinctWeeks.add(t.getWeek()); - } - - return distinctWeeks.size(); - } + return distinctWeeks.size(); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java index 511cdfc..c9f2097 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java @@ -4,12 +4,36 @@ /** * Represents the necessary calculations to perform a transaction. + * *

      {@link Purchase} and {@link Sale} are examples of implementations of a transaction.

      */ public interface TransactionCalculator { - BigDecimal calculateGross(); - BigDecimal calculateCommission(); - BigDecimal calculateTax(); - BigDecimal calculateTotal(); + /** + * Calculates the gross value of a transaction. + * + * @return the gross value of a transaction + */ + BigDecimal calculateGross(); + + /** + * Calculates the commission for the exchange of a transaction. + * + * @return the commission of a transaction + */ + BigDecimal calculateCommission(); + + /** + * Calculates the tax of a transaction. + * + * @return the tax of a transaction + */ + BigDecimal calculateTax(); + + /** + * Returns the total value of a transaction. + * + * @return the total value of a transaction + */ + BigDecimal calculateTotal(); } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionFactory.java b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionFactory.java index 9ddb766..cb27b9f 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionFactory.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionFactory.java @@ -2,31 +2,38 @@ /** * Factory for creating a {@link Transaction} for a {@link Share} in an {@link Exchange}. + * *

      Subclasses implement the specific type of transaction to be used.

      */ public abstract class TransactionFactory { - /** - * Creates a transaction. - * - * @param share the share involved - * @param week the transaction week - * @return a transaction - */ - public Transaction create(Share share, int week) { - - if (share == null) { - throw new IllegalArgumentException("Share cannot be null."); - } - - if (week <= 0) { - throw new IllegalArgumentException("Week must be positive whole number."); - } + /** + * Creates a transaction. + * + * @param share the share involved + * @param week the transaction week + * @return a transaction + */ + public Transaction create(Share share, int week) { + if (share == null) { + throw new IllegalArgumentException("Share cannot be null."); + } - return createTransaction(share, week); + if (week <= 0) { + throw new IllegalArgumentException("Week must be positive whole number."); } - protected abstract Transaction createTransaction( + return createTransaction(share, week); + } + + /** + * Creates a transaction from a share and week. + * + * @param share the share for the transaction + * @param week the week of the transaction + * @return a transaction for the share and week + */ + protected abstract Transaction createTransaction( Share share, int week ); diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java b/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java index 8ed2b0d..a587c09 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java +++ b/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java @@ -1,8 +1,5 @@ package no.ntnu.gruppe53.service; -import no.ntnu.gruppe53.model.Stock; - - import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; @@ -13,131 +10,146 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import no.ntnu.gruppe53.model.Stock; /** * Handles reading and writing of .csv files for stocks. */ public class FileHandler { + /** + * Writes a list of {@link Stock}s to a CSV file in UTF-8 encoding. + * + * @param stockList the list of stocks to be written + * @param path directory path for the file + * @param filename name of the file (without or with .csv extension) + * @throws IllegalArgumentException if stockList, path, + * or filename is null, or if stockList is empty + */ + public static void writeStocksToFile(List stockList, String path, String filename) { + if (stockList == null) { + throw new IllegalArgumentException("StockList cannot be null"); + } + if (path == null || path.isBlank()) { + throw new IllegalArgumentException("Path cannot be null or blank"); + } - /** - * Writes a list of {@link Stock}s to a CSV file in UTF-8 encoding. - * - * @param stockList the list of stocks to be written - * @param path directory path for the file - * @param filename name of the file (without or with .csv extension) - * @throws IllegalArgumentException if stockList, path, or filename is null, or if stockList is empty - */ - public static void writeStocksToFile(List stockList, String path, String filename) { - if (stockList == null) throw new IllegalArgumentException("StockList cannot be null"); - if (path == null || path.isBlank()) throw new IllegalArgumentException("Path cannot be null or blank"); - if (filename == null || filename.isBlank()) throw new IllegalArgumentException( - "Filename cannot be null or blank"); - - - path = path.trim(); - filename = filename.trim(); - String filenameWithExtension = filename.endsWith(".csv") ? filename : filename + ".csv"; - - Path filePath = Paths.get(path, filenameWithExtension); - - Path dirPath = filePath.getParent(); - if (!Files.exists(dirPath)) { - try { - Files.createDirectories(dirPath); - } catch (IOException e) { - System.out.println("ERROR: Could not create directories."); - e.printStackTrace(); - return; - } - } + if (filename == null || filename.isBlank()) { + throw new IllegalArgumentException( + "Filename cannot be null or blank"); + } + + path = path.trim(); + filename = filename.trim(); + String filenameWithExtension = filename.endsWith(".csv") ? filename : filename + ".csv"; + + Path filePath = Paths.get(path, filenameWithExtension); + + Path dirPath = filePath.getParent(); + + if (!Files.exists(dirPath)) { + try { + Files.createDirectories(dirPath); + } catch (IOException e) { + System.out.println("ERROR: Could not create directories."); + e.printStackTrace(); + return; + } + } + + try (BufferedWriter writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8)) { + writer.write("#Ticker,Name,Price"); + writer.newLine(); - try (BufferedWriter writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8)) { - writer.write("#Ticker,Name,Price"); - writer.newLine(); + for (Stock stock : stockList) { + if (stock == null) { + continue; + } - for (Stock stock : stockList) { - if (stock == null) continue; - String line = String.join(",", + String line = String.join(",", stock.getSymbol(), stock.getCompany(), stock.getSalesPrice().toString()); - writer.write(line); - writer.newLine(); - } + writer.write(line); + writer.newLine(); + } - } catch (IOException e) { - System.out.println("ERROR: Something went wrong while writing the CSV file."); - e.printStackTrace(); - } + } catch (IOException e) { + System.out.println("ERROR: Something went wrong while writing the CSV file."); + e.printStackTrace(); + } + } + + /** + * Reads a CSV file and converts it to a list of {@link Stock} objects. + * + * @param path directory path of the CSV file + * @param filename CSV file name (with or without .csv extension) + * @return list of Stock objects + * @throws IllegalArgumentException if path or filename is null or blank + */ + public static List readStocksFromFile(String path, String filename) { + if (path == null || path.isBlank()) { + throw new IllegalArgumentException("Path cannot be null or blank."); } - /** - * Reads a CSV file and converts it to a list of {@link Stock} objects. - * - * @param path directory path of the CSV file - * @param filename CSV file name (with or without .csv extension) - * @return list of Stock objects - * @throws IllegalArgumentException if path or filename is null or blank - */ - public static List readStocksFromFile(String path, String filename) { - List stocks = new ArrayList<>(); - if (path == null || path.isBlank()) { - throw new IllegalArgumentException("Path cannot be null or blank."); - } - if (filename == null || filename.isBlank()) { - throw new IllegalArgumentException("Filename cannot be null or blank."); - } - - path = path.trim(); - filename = filename.trim(); - String filenameWithExtension = filename.endsWith(".csv") ? filename : filename + ".csv"; + if (filename == null || filename.isBlank()) { + throw new IllegalArgumentException("Filename cannot be null or blank."); + } - Path filePath = Paths.get(path, filenameWithExtension); + path = path.trim(); + filename = filename.trim(); + String filenameWithExtension = filename.endsWith(".csv") ? filename : filename + ".csv"; - if (!Files.exists(filePath)) { - System.out.println("ERROR: File not found: " + filePath.toAbsolutePath()); - return stocks; - } + Path filePath = Paths.get(path, filenameWithExtension); - try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) { - String line; + List stocks = new ArrayList<>(); + if (!Files.exists(filePath)) { + System.out.println("ERROR: File not found: " + filePath.toAbsolutePath()); + return stocks; + } - while ((line = reader.readLine()) != null) { - line = line.trim(); + try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) { + String line; - if (line.isBlank() || line.startsWith("#")) continue; + while ((line = reader.readLine()) != null) { + line = line.trim(); - String[] values = line.split(",", -1); - if (values.length != 3) { - System.out.println("ERROR: Bad line in CSV (expected 3 values): " + line); - continue; - } + if (line.isBlank() || line.startsWith("#")) { + continue; + } - String symbol = values[0].trim(); - String name = values[1].trim(); - BigDecimal price; + String[] values = line.split(",", -1); - try { - price = new BigDecimal(values[2].trim()); - } catch (NumberFormatException e) { - System.out.println("ERROR: Price is not a valid number: " + values[2]); - continue; - } + if (values.length != 3) { + System.out.println("ERROR: Bad line in CSV (expected 3 values): " + line); + continue; + } - if (symbol.isBlank() || name.isBlank() || price.compareTo(BigDecimal.ZERO) <= 0) { - System.out.println("ERROR: Invalid stock data, skipping: " + line); - continue; - } + String symbol = values[0].trim(); + String name = values[1].trim(); + BigDecimal price; - stocks.add(new Stock(symbol, name, price)); - } + try { + price = new BigDecimal(values[2].trim()); + } catch (NumberFormatException e) { + System.out.println("ERROR: Price is not a valid number: " + values[2]); + continue; + } - } catch (IOException e) { - System.out.println("ERROR: Something went wrong while reading the CSV file."); - e.printStackTrace(); + if (symbol.isBlank() || name.isBlank() || price.compareTo(BigDecimal.ZERO) <= 0) { + System.out.println("ERROR: Invalid stock data, skipping: " + line); + continue; } - return stocks; + stocks.add(new Stock(symbol, name, price)); + } + + } catch (IOException e) { + System.out.println("ERROR: Something went wrong while reading the CSV file."); + e.printStackTrace(); } + + return stocks; + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/FormatBigDecimal.java b/millions/src/main/java/no/ntnu/gruppe53/service/FormatBigDecimal.java index 5db1ea5..422b321 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/service/FormatBigDecimal.java +++ b/millions/src/main/java/no/ntnu/gruppe53/service/FormatBigDecimal.java @@ -7,14 +7,15 @@ * Service/helper class that formats a {@link BigDecimal} to a string with 2 decimal accuracy. */ public class FormatBigDecimal { - /** - * Static method used to format a Big Decimal to a string representation with 2 decimals accuracy. - *

      Returns "0.00" if the BigDecimal is null.

      - * - * @param number the BigDecimal to be formated - * @return the string representation of the BigDecimal with 2 decimals accuracy. - */ - public static String formatNumber(BigDecimal number) { - return number == null ? "0.00" : number.setScale(2, RoundingMode.HALF_UP).toPlainString(); - } + /** + * Static method used to format a Big Decimal to a string representation with 2 decimals accuracy. + * + *

      Returns "0.00" if the BigDecimal is null.

      + * + * @param number the BigDecimal to be formated + * @return the string representation of the BigDecimal with 2 decimals accuracy. + */ + public static String formatNumber(BigDecimal number) { + return number == null ? "0.00" : number.setScale(2, RoundingMode.HALF_UP).toPlainString(); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java index 776f15a..6d6356d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java +++ b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java @@ -1,97 +1,101 @@ package no.ntnu.gruppe53.service; +import java.util.Locale; +import java.util.ResourceBundle; import javafx.beans.binding.Bindings; import javafx.beans.binding.StringBinding; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; -import java.util.Locale; -import java.util.ResourceBundle; - /** * Manages the language used in the user-interface. + * *

      The possible languages to be used are set by {@link LanguageRegistry}.

      */ public class LanguageManager { - /** - * Sets a default language and creates an observable {@link ResourceBundle}. - */ - private final ObjectProperty bundleProperty = - new SimpleObjectProperty<>(); + /** + * Sets a default language and creates an observable {@link ResourceBundle}. + */ + private final ObjectProperty bundleProperty = + new SimpleObjectProperty<>(); - private LanguageManager() { - setLocale(LanguageRegistry.getDefaultLocale()); - } + /** + * Sets the language to the default locale at first instantiation. + */ + private LanguageManager() { + setLocale(LanguageRegistry.getDefaultLocale()); + } - /** - * Static Inner Class to lazily create the instance - */ - private static class LanguageManagerInner { - private static final LanguageManager INSTANCE = - new LanguageManager(); - } + /** + * Static Inner Class to lazily create the instance. + */ + private static class LanguageManagerInner { + private static final LanguageManager INSTANCE = + new LanguageManager(); + } - /** - * Returns the instance of the language manager - * - * @return the instance of the language manager - */ - public static LanguageManager getInstance() { - return LanguageManagerInner.INSTANCE; - } + /** + * Returns the instance of the language manager. + * + * @return the instance of the language manager + */ + public static LanguageManager getInstance() { + return LanguageManagerInner.INSTANCE; + } - /** - * Returns the observable resource bundle. - * - * @return an observable resource bundle - */ - public ObjectProperty getBundleProperty() { - return bundleProperty; - } + /** + * Returns the observable resource bundle. + * + * @return an observable resource bundle + */ + public ObjectProperty getBundleProperty() { + return bundleProperty; + } - /** - * Sets the locale of the instance to a given locale which determines the properties file to load - * values from. - * - * @param locale the locale to change to - */ - public void setLocale(Locale locale) { - ResourceBundle bundle = - ResourceBundle.getBundle("i18n.lang", locale); + /** + * Sets the locale of the instance to a given locale which determines the properties file to load + * values from. + * + * @param locale the locale to change to + */ + public void setLocale(Locale locale) { + ResourceBundle bundle = + ResourceBundle.getBundle("i18n.lang", locale); - bundleProperty.set(bundle); - } + bundleProperty.set(bundle); + } - /** - * Returns the string value of the given key in a .properties file. - * - * @param key the key to look for in the .properties file - * @return the value of the key - */ - public String getString(String key) { - return bundleProperty.get().getString(key); - } + /** + * Returns the string value of the given key in a .properties file. + * + * @param key the key to look for in the .properties file + * @return the value of the key + */ + public String getString(String key) { + return bundleProperty.get().getString(key); + } - /** - * Returns the current locale. - * - * @return the current locale - */ - public Locale getLocale() { - return bundleProperty.get().getLocale(); - } + /** + * Returns the current locale. + * + * @return the current locale + */ + public Locale getLocale() { + return bundleProperty.get().getLocale(); + } - /** - * Creates a {@link StringBinding} to a given string and associates it with a given key in a .properties file - * - * @param key the key to associate the string binding with - * @return a string binding observable - */ - public StringBinding bindString(String key) { - return Bindings.createStringBinding( + /** + * Creates a {@link StringBinding} to a given string and associates it + * with a given key in a .properties file. + * + * @param key the key to associate the string binding with + * @return a string binding observable + */ + public StringBinding bindString(String key) { + return Bindings.createStringBinding( () -> getString(key), getBundleProperty() - ); - } + ); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/LanguageRegistry.java b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageRegistry.java index 1122d2a..490ab9f 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/service/LanguageRegistry.java +++ b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageRegistry.java @@ -8,47 +8,49 @@ */ public class LanguageRegistry { - /** - * Sets all possible languages that can be used. - */ - private static final List languages = List.of( + /** + * Sets all possible languages that can be used. + */ + private static final List languages = List.of( new LanguageOption("English", new Locale("en")), new LanguageOption("Norsk", new Locale("no")) - ); + ); - /** - * Sets the default language to be used. - *

      Searches through the registry to find english and sets it as default.

      - */ - private static final LanguageOption defaultLang = languages.stream() - .filter(lang -> lang.locale().getLanguage().equals("en")) - .findFirst() - .orElse(languages.getFirst()); + /** + * Sets the default language to be used. + * + *

      Searches through the registry to find english and sets it as default.

      + */ + private static final LanguageOption defaultLang = + languages.stream() + .filter(lang -> lang.locale().getLanguage().equals("en")) + .findFirst() + .orElse(languages.getFirst()); - /** - * Returns all registered languages - * - * @return a list of registered languages - */ - public static List getLanguages() { - return languages; - } + /** + * Returns all registered languages. + * + * @return a list of registered languages + */ + public static List getLanguages() { + return languages; + } - /** - * Returns the default language. - * - * @return the default language - */ - public static LanguageOption getDefaultLanguage() { - return defaultLang; - } + /** + * Returns the default language. + * + * @return the default language + */ + public static LanguageOption getDefaultLanguage() { + return defaultLang; + } - /** - * Returns the default locale of the default language. - * - * @return the default locale - */ - public static Locale getDefaultLocale() { - return defaultLang.locale(); - } + /** + * Returns the default locale of the default language. + * + * @return the default locale + */ + public static Locale getDefaultLocale() { + return defaultLang.locale(); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java b/millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java index 28076e3..5e785cd 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java +++ b/millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java @@ -1,72 +1,79 @@ package no.ntnu.gruppe53.service; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; import javafx.scene.chart.XYChart; import no.ntnu.gruppe53.model.Exchange; import no.ntnu.gruppe53.model.Stock; import no.ntnu.gruppe53.view.StockLineChartView; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; - /** * Builds the {@link XYChart.Series} values for the {@link StockLineChartView}. */ public class StockLineChartService { - /** - * Builds the {@code XYChart.Series} for the {@code LineChart} of a single {@link Stock}. - *

      Returns the {@code XYChart.Series}.

      - * - * @param stock the stock whose price values will be shown in the LineChart - * @param exchange the exchange containing the stock whose week number will be shown in the LineChart - * @return the {@code XYChart.Series} with the y-values from the price history of the stock, - * and the x-values from the week number of the exchange - * @throws IllegalArgumentException if there are more x-values than y-values or vice versa - */ - - public static XYChart.Series buildSeries(Stock stock, Exchange exchange) { - XYChart.Series series = new XYChart.Series<>(); - series.setName(stock.getSymbol()); + /** + * Builds the {@code XYChart.Series} for the {@code LineChart} of a single {@link Stock}. + * + *

      Returns the {@code XYChart.Series}.

      + * + * @param stock the stock whose price values will be shown in the LineChart + * @param exchange the exchange containing the stock whose + * week number will be shown in the LineChart + * @return the {@code XYChart.Series} with + * the y-values from the price history of the stock, + * and the x-values from the week number of the exchange + * @throws IllegalArgumentException if there are more x-values than y-values or vice versa + */ - // Check for 1-1 ratio of price and week list size for plotting - if (stock.getHistoricalPrices().size() != exchange.getWeek()) { - throw new IllegalArgumentException("Invalid Data: mismatch between prices and weeks. " + - "Should have 1-1 ratio."); - } + public static XYChart.Series buildSeries(Stock stock, Exchange exchange) { + XYChart.Series series = new XYChart.Series<>(); + series.setName(stock.getSymbol()); - int week = 1; + // Check for 1-1 ratio of price and week list size for plotting + if (stock.getHistoricalPrices().size() != exchange.getWeek()) { + throw new IllegalArgumentException("Invalid Data: mismatch between prices and weeks. " + + "Should have 1-1 ratio."); + } - for (BigDecimal price : stock.getHistoricalPrices()) { - series.getData().add( - new XYChart.Data<>(week, price.doubleValue())); - week++; - } + int week = 1; - return series; + for (BigDecimal price : stock.getHistoricalPrices()) { + series.getData().add( + new XYChart.Data<>(week, price.doubleValue())); + week++; } - /** - * Builds the {@code XYChart.Series} for the {@code LineChart} of a list of {@code Stock}s. - *

      Returns a list of the {@code XYChart.Series} for each stock.

      - * - * @param stocks the list of stocks whose price values will be shown in the LineChart - * @param exchange the exchange containing the stock whose week number will be shown in the LineChart - * @return the list of {@code XYChart.Series} with the y-values from the price history of the stock, - * and the x-values from the week number of the exchange - */ + return series; + } - public static List> buildSeries(List stocks, Exchange exchange) { - List> seriesList = new ArrayList<>(); + /** + * Builds the {@code XYChart.Series} for the {@code LineChart} of a list of {@code Stock}s. + * + *

      Returns a list of the {@code XYChart.Series} for each stock.

      + * + * @param stocks the list of stocks whose price values will be shown + * in the LineChart + * @param exchange the exchange containing the stock whose week number + * will be shown in the LineChart + * @return the list of {@code XYChart.Series} with + * the y-values from the price history of the stock, + * and the x-values from the week number of the exchange + */ - for (Stock stock : stocks) { - XYChart.Series series = - buildSeries(stock, exchange); + public static List> buildSeries( + List stocks, Exchange exchange) { + List> seriesList = new ArrayList<>(); - seriesList.add(series); - } + for (Stock stock : stocks) { + XYChart.Series series = + buildSeries(stock, exchange); - return seriesList; + seriesList.add(series); } + + return seriesList; + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java b/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java deleted file mode 100644 index a60d295..0000000 --- a/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java +++ /dev/null @@ -1,49 +0,0 @@ -package no.ntnu.gruppe53.view; - -import javafx.scene.control.Alert; -import javafx.stage.FileChooser; -import javafx.stage.Stage; -import no.ntnu.gruppe53.service.LanguageManager; - -import java.io.File; - -/** - * Represents an {@link Alert} and {@link FileChooser} for informing user of .csv requirement and picking said file. - *

      Uses a {@link LanguageManager} to insert set text to target language.

      - */ -public class CSVChooserView { - private final FileChooser fileChooser = new FileChooser(); - Alert newGameAlert = new Alert(Alert.AlertType.INFORMATION); - private final LanguageManager lm = LanguageManager.getInstance(); - - /** - * Returns the dialog for the alert and file chooser and exposes it to controllers. - * - * @param stage the stage for the dialog to be shown - * @return the dialog for the alert and file chooser - */ - public File getDialog(Stage stage) { - // Notifies the user about .csv selection - newGameAlert.setTitle(null); - newGameAlert.headerTextProperty().bind(lm.bindString("csvHeader")); - newGameAlert.contentTextProperty().bind(lm.bindString("csvContent")); - - // Sets the dimensions of the alert to show all text, and as a band-aid, be resizable - newGameAlert.setResizable(true); - - newGameAlert.getDialogPane().setPrefWidth(450); - newGameAlert.getDialogPane().setPrefHeight(350); - - newGameAlert.showAndWait(); - - // File chooser to choose .csv file - fileChooser.setTitle(lm.getString("csvFileChooserTitle")); - - // Limits to .csv file only - fileChooser.getExtensionFilters().clear(); - fileChooser.getExtensionFilters().add( - new FileChooser.ExtensionFilter(lm.getString("csvFileFilterLabel"), "*.csv") - ); - - return fileChooser.showOpenDialog(stage); } -} \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/CsvChooserView.java b/millions/src/main/java/no/ntnu/gruppe53/view/CsvChooserView.java new file mode 100644 index 0000000..4264887 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/view/CsvChooserView.java @@ -0,0 +1,51 @@ +package no.ntnu.gruppe53.view; + +import java.io.File; +import javafx.scene.control.Alert; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import no.ntnu.gruppe53.service.LanguageManager; + +/** + * Represents an {@link Alert} and {@link FileChooser} for + * informing user of .csv requirement and picking said file. + * + *

      Uses a {@link LanguageManager} to insert set text to target language.

      + */ +public class CsvChooserView { + private final FileChooser fileChooser = new FileChooser(); + Alert newGameAlert = new Alert(Alert.AlertType.INFORMATION); + private final LanguageManager lm = LanguageManager.getInstance(); + + /** + * Returns the dialog for the alert and file chooser and exposes it to controllers. + * + * @param stage the stage for the dialog to be shown + * @return the dialog for the alert and file chooser + */ + public File getDialog(Stage stage) { + // Notifies the user about .csv selection + newGameAlert.setTitle(null); + newGameAlert.headerTextProperty().bind(lm.bindString("csvHeader")); + newGameAlert.contentTextProperty().bind(lm.bindString("csvContent")); + + // Sets the dimensions of the alert to show all text, and as a band-aid, be resizable + newGameAlert.setResizable(true); + + newGameAlert.getDialogPane().setPrefWidth(450); + newGameAlert.getDialogPane().setPrefHeight(350); + + newGameAlert.showAndWait(); + + // File chooser to choose .csv file + fileChooser.setTitle(lm.getString("csvFileChooserTitle")); + + // Limits to .csv file only + fileChooser.getExtensionFilters().clear(); + fileChooser.getExtensionFilters().add( + new FileChooser.ExtensionFilter(lm.getString("csvFileFilterLabel"), "*.csv") + ); + + return fileChooser.showOpenDialog(stage); + } +} \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java b/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java index 9ac97de..611e997 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java @@ -4,200 +4,210 @@ import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.Label; -import javafx.scene.layout.*; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import javafx.scene.text.TextAlignment; import no.ntnu.gruppe53.service.LanguageManager; - /** * Represents the End Game screen, showing the Level the Player reached, * money earned in total after selling portfolio and gives the option * to quit the program or start a new game. */ public class EndView extends BorderPane { - - - private Button newGameButton; - private Button quitButton; - - private Label statusLabel; - private Label moneyLabel; - - - private final LanguageManager lm = LanguageManager.getInstance(); - - public EndView() { - this.setCenter(centerPane()); - } - - private BorderPane centerPane() { - BorderPane pane = new BorderPane(); - pane.setPadding(new Insets(15, 12, 15, 12)); - //pane.setSpacing(10); - //Blue Background color - pane.setStyle("-fx-background-color: #00bcf0;"); - - VBox centerPane = new VBox(); - - HBox congratsBox = new HBox(); - Label congratsLabel = new Label(); - congratsLabel.textProperty().bind(lm.bindString("congratsLabel")); - congratsLabel.setStyle("-fx-font-size: 18"); - congratsLabel.setTextAlignment(TextAlignment.CENTER); - - congratsBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - congratsBox.setPadding(new Insets(8, 14, 8, 14)); - congratsBox.setAlignment(Pos.CENTER); - congratsBox.setMinHeight(65); - - congratsBox.getChildren().add(congratsLabel); - - HBox statusLabelBox = new HBox(); - Label statusLabelText = new Label(); - statusLabelText.textProperty().bind(lm.bindString("statusLabelText")); - statusLabelText.setStyle("-fx-font-size: 18"); - statusLabel = new Label(); - statusLabel.setStyle("-fx-font-size: 18"); - - statusLabelBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - statusLabelBox.setPadding(new Insets(8, 14, 8, 14)); - statusLabelBox.setAlignment(Pos.CENTER); - statusLabelBox.setMinHeight(65); - - statusLabelBox.getChildren().addAll(statusLabelText, statusLabel); - - HBox moneyLabelBox = new HBox(); - Label moneyLabelText = new Label(); - moneyLabelText.textProperty().bind(lm.bindString("navMoneyLabelText")); - moneyLabelText.setStyle("-fx-font-size: 18"); - moneyLabel = new Label(); - moneyLabel.setStyle("-fx-font-size: 18"); - - moneyLabelBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - moneyLabelBox.setPadding(new Insets(8, 14, 8, 14)); - moneyLabelBox.setAlignment(Pos.CENTER); - moneyLabelBox.setMinHeight(65); - - moneyLabelBox.getChildren().addAll(moneyLabelText, moneyLabel); - - centerPane.setPadding(new Insets(15, 12, 15, 12)); - centerPane.setAlignment(Pos.CENTER); - centerPane.setSpacing(10); - centerPane.setMaxWidth(700); - - - centerPane.getChildren().addAll(congratsBox, statusLabelBox, moneyLabelBox); - - HBox quitButtonBox = new HBox(); - quitButton = new Button(); - quitButton.textProperty().bind(lm.bindString("quitGame")); - quitButton.setPrefSize(200, 50); - quitButton.setStyle("-fx-text-fill: #303539;" + - "-fx-font-size: 18px;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" + - "-fx-background-radius: 8;" - ); - - - quitButtonBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - quitButtonBox.setPadding(new Insets(8, 14, 8, 14)); - quitButtonBox.setAlignment(Pos.CENTER); - - - quitButtonBox.getChildren().addAll(quitButton); - - HBox newGameButtonBox = new HBox(); - newGameButton = new Button(); - newGameButton.textProperty().bind(lm.bindString("newGame")); - newGameButton.setPrefSize(200, 50); - newGameButton.setStyle("-fx-text-fill: #303539;" + - "-fx-font-size: 18px;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" + - "-fx-background-radius: 8;" - ); - newGameButtonBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - newGameButtonBox.setPadding(new Insets(8, 14, 8, 14)); - newGameButtonBox.setAlignment(Pos.CENTER); - newGameButtonBox.getChildren().add(newGameButton); - - HBox buttons = new HBox(quitButtonBox, newGameButtonBox); - - - - Region spacerBottomLeft = new Region(); - Region spacerBottomRight = new Region(); - - HBox.setHgrow(spacerBottomLeft, Priority.ALWAYS); - HBox.setHgrow(spacerBottomRight, Priority.ALWAYS); - - HBox bottomPane = new HBox(spacerBottomLeft, buttons, spacerBottomRight); - - pane.setCenter(centerPane); - pane.setBottom(bottomPane); - - return pane; - } - - /** - * Sets the action to be run when clicking the quit button - * - * @param action the action to run when clicking the quit button - */ - public void onQuitButton(Runnable action) { - quitButton.setOnAction(e -> action.run()); - } - - /** - * Sets the action to be run when clicking the New Game button - * - * @param action the action to run when clicking the New Game button - */ - public void onNewGameButton(Runnable action) { - newGameButton.setOnAction(e -> action.run()); - } - - /** - * Returns the statusLabel. - * - * @return the statusLabel - */ - public Label getStatusLabel() { - return statusLabel; - } - - /** - * Returns the moneyLabel. - * - * @return the moneyLabel - */ - public Label getMoneyLabel() { - return moneyLabel; - } + private Button newGameButton; + private Button quitButton; + + private Label statusLabel; + private Label moneyLabel; + + private final LanguageManager lm = LanguageManager.getInstance(); + + /** + * Sets the centerPane to the constructed centerPane. + */ + public EndView() { + this.setCenter(centerPane()); + } + + /** + * Constructs the centerPane using a {@link BorderPane}. + * + * @return a constructed borderpane + */ + private BorderPane centerPane() { + BorderPane pane = new BorderPane(); + pane.setPadding(new Insets(15, 12, 15, 12)); + // pane.setSpacing(10); + // Blue Background color + pane.setStyle("-fx-background-color: #00bcf0;"); + + Label congratsLabel = new Label(); + congratsLabel.textProperty().bind(lm.bindString("congratsLabel")); + congratsLabel.setStyle("-fx-font-size: 18"); + congratsLabel.setTextAlignment(TextAlignment.CENTER); + + HBox congratsBox = new HBox(); + congratsBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + congratsBox.setPadding(new Insets(8, 14, 8, 14)); + congratsBox.setAlignment(Pos.CENTER); + congratsBox.setMinHeight(65); + + congratsBox.getChildren().add(congratsLabel); + + Label statusLabelText = new Label(); + statusLabelText.textProperty().bind(lm.bindString("statusLabelText")); + statusLabelText.setStyle("-fx-font-size: 18"); + statusLabel = new Label(); + statusLabel.setStyle("-fx-font-size: 18"); + + HBox statusLabelBox = new HBox(); + statusLabelBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + statusLabelBox.setPadding(new Insets(8, 14, 8, 14)); + statusLabelBox.setAlignment(Pos.CENTER); + statusLabelBox.setMinHeight(65); + + statusLabelBox.getChildren().addAll(statusLabelText, statusLabel); + + Label moneyLabelText = new Label(); + moneyLabelText.textProperty().bind(lm.bindString("navMoneyLabelText")); + moneyLabelText.setStyle("-fx-font-size: 18"); + moneyLabel = new Label(); + moneyLabel.setStyle("-fx-font-size: 18"); + + HBox moneyLabelBox = new HBox(); + moneyLabelBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + moneyLabelBox.setPadding(new Insets(8, 14, 8, 14)); + moneyLabelBox.setAlignment(Pos.CENTER); + moneyLabelBox.setMinHeight(65); + + moneyLabelBox.getChildren().addAll(moneyLabelText, moneyLabel); + + VBox centerPane = new VBox(); + centerPane.setPadding(new Insets(15, 12, 15, 12)); + centerPane.setAlignment(Pos.CENTER); + centerPane.setSpacing(10); + centerPane.setMaxWidth(700); + + + centerPane.getChildren().addAll(congratsBox, statusLabelBox, moneyLabelBox); + + quitButton = new Button(); + quitButton.textProperty().bind(lm.bindString("quitGame")); + quitButton.setPrefSize(200, 50); + quitButton.setStyle("-fx-text-fill: #303539;" + + "-fx-font-size: 18px;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + + "-fx-background-radius: 8;" + ); + + HBox quitButtonBox = new HBox(); + quitButtonBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + quitButtonBox.setPadding(new Insets(8, 14, 8, 14)); + quitButtonBox.setAlignment(Pos.CENTER); + + + quitButtonBox.getChildren().addAll(quitButton); + + newGameButton = new Button(); + newGameButton.textProperty().bind(lm.bindString("newGame")); + newGameButton.setPrefSize(200, 50); + newGameButton.setStyle("-fx-text-fill: #303539;" + + "-fx-font-size: 18px;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + + "-fx-background-radius: 8;" + ); + + HBox newGameButtonBox = new HBox(); + newGameButtonBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + newGameButtonBox.setPadding(new Insets(8, 14, 8, 14)); + newGameButtonBox.setAlignment(Pos.CENTER); + newGameButtonBox.getChildren().add(newGameButton); + + HBox buttons = new HBox(quitButtonBox, newGameButtonBox); + + Region spacerBottomLeft = new Region(); + Region spacerBottomRight = new Region(); + + HBox.setHgrow(spacerBottomLeft, Priority.ALWAYS); + HBox.setHgrow(spacerBottomRight, Priority.ALWAYS); + + HBox bottomPane = new HBox(spacerBottomLeft, buttons, spacerBottomRight); + + pane.setCenter(centerPane); + pane.setBottom(bottomPane); + + return pane; + } + + /** + * Sets the action to be run when clicking the quit button. + * + * @param action the action to run when clicking the quit button + */ + public void onQuitButton(Runnable action) { + quitButton.setOnAction(e -> action.run()); + } + + /** + * Sets the action to be run when clicking the New Game button. + * + * @param action the action to run when clicking the New Game button + */ + public void onNewGameButton(Runnable action) { + newGameButton.setOnAction(e -> action.run()); + } + + /** + * Returns the statusLabel. + * + * @return the statusLabel + */ + public Label getStatusLabel() { + return statusLabel; + } + + /** + * Returns the moneyLabel. + * + * @return the moneyLabel + */ + public Label getMoneyLabel() { + return moneyLabel; + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java index 8785d28..dd9857b 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java @@ -2,7 +2,6 @@ import java.util.Locale; import java.util.function.Consumer; - import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Button; @@ -14,13 +13,12 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.util.Subscription; - import no.ntnu.gruppe53.controller.GameController; import no.ntnu.gruppe53.model.Exchange; +import no.ntnu.gruppe53.model.Stock; import no.ntnu.gruppe53.service.LanguageManager; import no.ntnu.gruppe53.service.LanguageOption; import no.ntnu.gruppe53.service.LanguageRegistry; -import no.ntnu.gruppe53.model.Stock; /* Yellow Color, Hex: #ffe556 RGB(255, 229, 86) @@ -28,208 +26,208 @@ Black Color, Hex: #303539 RGB(48, 53, 57) */ - /** * A {@link HBox} view representing the footer/bottom to be used in a {@link BorderPane} layout. + * *

      Uses a {@link LanguageManager} to change text to target language dynamically.

      */ public class FooterBar extends HBox { - - private final Button advanceWeekButton; - private Label currentWeekLabel; - private Label biggestLoserLabel; - private Label biggestLoserPriceLabel; - private Label biggestWinnerLabel; - private Label biggestWinnerPriceLabel; - private Subscription currentWeekSubscription; - private Subscription biggestLoserSubscription; - private Subscription biggestWinnerSubscription; - - private Exchange exchange; - private final LanguageManager lm = LanguageManager.getInstance(); - - private final ComboBox languageBox; - - /** - * Constructs the layout of the view and its components. - *

      Constructs a {@link ComboBox} to let the user switch language on the fly.

      - *

      Sets the text of the UI elements dynamically.

      - */ - public FooterBar() { - this.setSpacing(10); - this.setPadding(new Insets(15, 12, 15, 12)); - this.setStyle("-fx-background-color: #00bcf0;" + - "-fx-border-color: #303539 transparent transparent transparent;" + - "-fx-border-width: 5 0 0 0;"); - - languageBox = new ComboBox<>(); - languageBox.getItems().addAll(LanguageRegistry.getLanguages()); - - Locale activeLocale = lm.getLocale(); - LanguageOption activeOption = LanguageRegistry.getLanguages().stream() + private final Button advanceWeekButton; + private final Label currentWeekLabel; + private final Label biggestLoserLabel; + private final Label biggestLoserPriceLabel; + private final Label biggestWinnerLabel; + private final Label biggestWinnerPriceLabel; + private Subscription biggestLoserSubscription; + private Subscription biggestWinnerSubscription; + + private final LanguageManager lm = LanguageManager.getInstance(); + + private final ComboBox languageBox; + + /** + * Constructs the layout of the view and its components. + * + *

      Constructs a {@link ComboBox} to let the user switch language on the fly.

      + * + *

      Sets the text of the UI elements dynamically.

      + */ + public FooterBar() { + this.setSpacing(10); + this.setPadding(new Insets(15, 12, 15, 12)); + this.setStyle("-fx-background-color: #00bcf0;" + + "-fx-border-color: #303539 transparent transparent transparent;" + + "-fx-border-width: 5 0 0 0;"); + + languageBox = new ComboBox<>(); + languageBox.getItems().addAll(LanguageRegistry.getLanguages()); + + Locale activeLocale = lm.getLocale(); + LanguageOption activeOption = LanguageRegistry.getLanguages().stream() .filter(lang -> lang.locale().equals(activeLocale)) .findFirst() .orElse(LanguageRegistry.getDefaultLanguage()); - languageBox.setValue(activeOption); + languageBox.setValue(activeOption); - // Listens for changes in language from other sources (e.g. startView) - lm.getBundleProperty().subscribe(bundle -> { - java.util.Locale currentLocale = lm.getLocale(); + // Listens for changes in language from other sources (e.g. startView) + lm.getBundleProperty().subscribe(bundle -> { + java.util.Locale currentLocale = lm.getLocale(); - LanguageOption matchingOption = LanguageRegistry.getLanguages().stream() + LanguageOption matchingOption = LanguageRegistry.getLanguages().stream() .filter(lang -> lang.locale().equals(currentLocale)) .findFirst() .orElse(null); - if (matchingOption != null && !matchingOption.equals(languageBox.getValue())) { - languageBox.setValue(matchingOption); + if (matchingOption != null && !matchingOption.equals(languageBox.getValue())) { + languageBox.setValue(matchingOption); + } + }); + + languageBox.setCellFactory(list -> new ListCell<>() { + private Subscription cellSub; + @Override + protected void updateItem(LanguageOption item, boolean empty) { + super.updateItem(item, empty); + if (cellSub != null) { + cellSub.unsubscribe(); } - }); - - languageBox.setCellFactory(list -> new ListCell<>() { - private Subscription cellSub; - @Override - protected void updateItem(LanguageOption item, boolean empty) { - super.updateItem(item, empty); - if (cellSub != null) cellSub.unsubscribe(); - - if (empty || item == null) { - setText(null); - } else { - cellSub = lm.getBundleProperty().subscribe(bundle -> setText(item.name())); - } - } - }); - - languageBox.setButtonCell(languageBox.getCellFactory().call(null)); - - HBox loserBox = new HBox(); - - Label biggestLoserLabelText = new Label(); - biggestLoserLabelText.textProperty().bind(lm.bindString("biggestLoserLabelText")); - biggestLoserLabel = new Label(); - biggestLoserPriceLabel = new Label(); - biggestLoserPriceLabel.setStyle("-fx-text-fill: red;"); - - loserBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - loserBox.setPadding(new Insets(8, 14, 8, 14)); - loserBox.setAlignment(Pos.CENTER); - - loserBox.getChildren().addAll(biggestLoserLabelText, biggestLoserLabel, biggestLoserPriceLabel); - - HBox winnerBox = new HBox(); - - Label biggestWinnerLabelText = new Label(); - biggestWinnerLabelText.textProperty().bind(lm.bindString("biggestWinnerLabelText")); - biggestWinnerLabel = new Label(); - biggestWinnerPriceLabel = new Label(); - biggestWinnerPriceLabel.setStyle("-fx-text-fill: green;"); - winnerBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - winnerBox.setPadding(new Insets(8, 14, 8, 14)); - winnerBox.setAlignment(Pos.CENTER); - - winnerBox.getChildren().addAll(biggestWinnerLabelText, biggestWinnerLabel, biggestWinnerPriceLabel); - - - Region spacer = new Region(); - HBox.setHgrow(spacer, Priority.ALWAYS); - - HBox currentWeekBox = new HBox(); - currentWeekBox.setSpacing(10); - - currentWeekBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - currentWeekBox.setPadding(new Insets(8, 14, 8, 14)); - currentWeekBox.setAlignment(Pos.CENTER); - - Label currentWeekLabelText = new Label(); - currentWeekLabelText.textProperty().bind(lm.bindString("currentWeekLabelText")); - currentWeekLabel = new Label(); - currentWeekBox.getChildren().addAll(currentWeekLabelText, currentWeekLabel); - - advanceWeekButton = new Button(); - advanceWeekButton.textProperty().bind(lm.bindString("advanceWeekButton")); - advanceWeekButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - - this.getChildren().addAll(languageBox, loserBox, winnerBox, spacer, currentWeekBox, advanceWeekButton); - } - - /** - * Sets the exchange and subscribes to week updates. - * - * @param exchange the {@link Exchange} used in the game - */ - public void setExchange(Exchange exchange) { - this.exchange = exchange; - currentWeekSubscription = exchange.getCurrentWeekProperty().subscribe(newVal -> { - if (exchange != null) { - currentWeekLabel.setText(newVal.toString()); + if (empty || item == null) { + setText(null); } else { - currentWeekLabel.setText("None"); + cellSub = lm.getBundleProperty().subscribe(bundle -> setText(item.name())); } - }); - - if (biggestLoserSubscription != null) { - biggestLoserSubscription.unsubscribe(); - } - - biggestLoserSubscription = exchange.getCurrentWeekProperty().subscribe(newWeek -> { - Stock loser = exchange.getLosers(1).getFirst(); - biggestLoserLabel.setText(loser.getSymbol() + " - " + loser.getCompany()); - biggestLoserPriceLabel.setText(" $" + loser.getLatestPriceChange()); - }); - - if (biggestWinnerSubscription != null) { - biggestWinnerSubscription.unsubscribe(); - } - - biggestWinnerSubscription = exchange.getCurrentWeekProperty().subscribe(newWeek -> { - Stock winner = exchange.getGainers(1).getFirst(); - biggestWinnerLabel.setText(winner.getSymbol() + " - " + winner.getCompany()); - biggestWinnerPriceLabel.setText(" $" + winner.getLatestPriceChange()); - }); + } + }); + + languageBox.setButtonCell(languageBox.getCellFactory().call(null)); + + Label biggestLoserLabelText = new Label(); + biggestLoserLabelText.textProperty().bind(lm.bindString("biggestLoserLabelText")); + biggestLoserLabel = new Label(); + biggestLoserPriceLabel = new Label(); + biggestLoserPriceLabel.setStyle("-fx-text-fill: red;"); + + HBox loserBox = new HBox(); + loserBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + loserBox.setPadding(new Insets(8, 14, 8, 14)); + loserBox.setAlignment(Pos.CENTER); + + loserBox.getChildren().addAll(biggestLoserLabelText, biggestLoserLabel, biggestLoserPriceLabel); + + Label biggestWinnerLabelText = new Label(); + biggestWinnerLabelText.textProperty().bind(lm.bindString("biggestWinnerLabelText")); + biggestWinnerLabel = new Label(); + biggestWinnerPriceLabel = new Label(); + biggestWinnerPriceLabel.setStyle("-fx-text-fill: green;"); + + HBox winnerBox = new HBox(); + winnerBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + winnerBox.setPadding(new Insets(8, 14, 8, 14)); + winnerBox.setAlignment(Pos.CENTER); + + winnerBox.getChildren().addAll( + biggestWinnerLabelText, biggestWinnerLabel, biggestWinnerPriceLabel); + + + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + + HBox currentWeekBox = new HBox(); + currentWeekBox.setSpacing(10); + + currentWeekBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + currentWeekBox.setPadding(new Insets(8, 14, 8, 14)); + currentWeekBox.setAlignment(Pos.CENTER); + + Label currentWeekLabelText = new Label(); + currentWeekLabelText.textProperty().bind(lm.bindString("currentWeekLabelText")); + currentWeekLabel = new Label(); + currentWeekBox.getChildren().addAll(currentWeekLabelText, currentWeekLabel); + + advanceWeekButton = new Button(); + advanceWeekButton.textProperty().bind(lm.bindString("advanceWeekButton")); + advanceWeekButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + + this.getChildren().addAll( + languageBox, loserBox, winnerBox, spacer, currentWeekBox, advanceWeekButton); + } + + /** + * Sets the exchange and subscribes to week updates. + * + * @param exchange the {@link Exchange} used in the game + */ + public void setExchange(Exchange exchange) { + Subscription currentWeekSubscription = exchange.getCurrentWeekProperty().subscribe(newVal -> { + if (exchange != null) { + currentWeekLabel.setText(newVal.toString()); + } else { + currentWeekLabel.setText("None"); + } + }); + + if (biggestLoserSubscription != null) { + biggestLoserSubscription.unsubscribe(); } - /** - * Exposes the advanceWeekButton to {@link GameController} so it can - * advance the week of the {@link Exchange}. - * - * @param action the action to be performed when clicking the button - */ - public void onAdvanceButtonClick(Runnable action) { - advanceWeekButton.setOnAction(e -> action.run()); - } + biggestLoserSubscription = exchange.getCurrentWeekProperty().subscribe(newWeek -> { + Stock loser = exchange.getLosers(1).getFirst(); + biggestLoserLabel.setText(loser.getSymbol() + " - " + loser.getCompany()); + biggestLoserPriceLabel.setText(" $" + loser.getLatestPriceChange()); + }); - - /** - * Exoposes the LanguageOption ComboButton to the controller. - * - * @param action the action to be performed when selecting the language - */ - public void onLanguageChange(Consumer action) { - languageBox.setOnAction(e -> { - LanguageOption selected = languageBox.getValue(); - if (selected != null) { - action.accept(selected); - } - }); + if (biggestWinnerSubscription != null) { + biggestWinnerSubscription.unsubscribe(); } + + biggestWinnerSubscription = exchange.getCurrentWeekProperty().subscribe(newWeek -> { + Stock winner = exchange.getGainers(1).getFirst(); + biggestWinnerLabel.setText(winner.getSymbol() + " - " + winner.getCompany()); + biggestWinnerPriceLabel.setText(" $" + winner.getLatestPriceChange()); + }); + } + + /** + * Exposes the advanceWeekButton to {@link GameController} so it can + * advance the week of the {@link Exchange}. + * + * @param action the action to be performed when clicking the button + */ + public void onAdvanceButtonClick(Runnable action) { + advanceWeekButton.setOnAction(e -> action.run()); + } + + /** + * Exposes the LanguageOption ComboButton to the controller. + * + * @param action the action to be performed when selecting the language + */ + public void onLanguageChange(Consumer action) { + languageBox.setOnAction(e -> { + LanguageOption selected = languageBox.getValue(); + if (selected != null) { + action.accept(selected); + } + }); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java index 9dc83fe..ce37a5d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java @@ -2,17 +2,29 @@ import java.math.RoundingMode; import java.util.function.Consumer; - import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.SortedList; import javafx.geometry.Insets; import javafx.geometry.Pos; -import javafx.scene.control.*; -import javafx.scene.chart.XYChart; -import javafx.scene.layout.*; import javafx.scene.chart.LineChart; +import javafx.scene.chart.XYChart; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.Spinner; +import javafx.scene.control.TextField; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import javafx.util.Subscription; -import no.ntnu.gruppe53.model.*; +import no.ntnu.gruppe53.model.Exchange; +import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.model.PurchaseCalculator; +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; import no.ntnu.gruppe53.service.FormatBigDecimal; import no.ntnu.gruppe53.service.LanguageManager; @@ -22,413 +34,420 @@ Black Color, Hex: #303539 RGB(48, 53, 57) */ - /** * Represents a view of the market of an {@link Exchange}, allowing the {@link Player} to see * price history statistics in the form of a {@link LineChart} through the * {@link StockLineChartView} class, as well as buying {@link Stock}s. + * *

      Extends borderpane to allow easy organizing of the internal layout.

      + * *

      Uses a {@link LanguageManager} to set text to the targer language dynamically.

      */ public class MarketView extends BorderPane { - private final LanguageManager lm = LanguageManager.getInstance(); - - private Button purchaseButton; - private Label selectedStock; - - //Dynamic Labels/Text - public TextField gross; - public TextField commission; - public TextField total; - private TextField searchField; - - //StockListView - private ListView stocksListView; - private FilteredList filteredStocksList; - - //Input - private Spinner quantitySpinner; - private Exchange exchange; - private PurchaseCalculator purchaseCalculator; - - private final StockLineChartView stockLineChartView; - private Subscription priceSubscription; - private Subscription grossSubscription; - private Subscription commissionSubscription; - private Subscription totalSubscription; - - /** - * Initializes the StockLineChartView graph and runs the view - * construction method and places it in the center of the - * BorderPane. - */ - public MarketView() { - stockLineChartView = new StockLineChartView(); - this.setCenter(centerPane()); - } + private final LanguageManager lm = LanguageManager.getInstance(); + + private Button purchaseButton; + private Label selectedStock; + + // Dynamic Labels/Text + public TextField gross; + public TextField commission; + public TextField total; + private TextField searchField; + + // StockListView + private ListView stocksListView; + private FilteredList filteredStocksList; + + // Input + private Spinner quantitySpinner; + + private final StockLineChartView stockLineChartView; + private Subscription priceSubscription; + + /** + * Initializes the StockLineChartView graph and runs the view + * construction method and places it in the center of the + * BorderPane. + */ + public MarketView() { + stockLineChartView = new StockLineChartView(); + this.setCenter(centerPane()); + } + + /** + * Constructs the view and its components. + * + *

      Subscribes to a stock's sales price to update the value immediately.

      + * + *

      Subscribes to the current language to change text immediately.

      + * + *

      Uses a cell factory to format the string found in the stocksListView.

      + * + * @return the constructed MarketView + */ + private VBox centerPane() { + VBox vbox = new VBox(); + + vbox.setPadding(new Insets(15, 12, 15, 12)); + vbox.setSpacing(10); + + // Blue Background color + vbox.setStyle("-fx-background-color: #00bcf0;"); + + Label titleLabel = new Label(); + titleLabel.textProperty().bind(lm.bindString("marketTitle")); + titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; "); + + VBox titleBox = new VBox(titleLabel); + titleBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + titleBox.setAlignment(Pos.CENTER); + titleBox.setPadding(new Insets(4)); + titleBox.setMaxWidth(200); + + // Stock Graph + stockLineChartView.getChart().setMinHeight(0); + stockLineChartView.getChart().setMaxHeight(Double.MAX_VALUE); + stockLineChartView.getChart().setMinWidth(0); + stockLineChartView.getChart().setMaxWidth(Double.MAX_VALUE); + stockLineChartView.getChart().getXAxis().labelProperty().bind(lm.bindString("xAxisLabel")); + stockLineChartView.getChart().getYAxis().labelProperty().bind(lm.bindString("yAxisLabel")); + + // List of stocks + stocksListView = new ListView<>(); + stocksListView.setMinHeight(0); + stocksListView.setMaxHeight(Double.MAX_VALUE); + stocksListView.setMinWidth(0); + stocksListView.setMaxWidth(Double.MAX_VALUE); + stocksListView.setStyle("-fx-background-color: transparent;"); + + stocksListView.setCellFactory(list -> new ListCell<>() { + private Subscription cellSubscription; + private Subscription langSubscription; + + @Override + protected void updateItem(Stock stock, boolean empty) { + super.updateItem(stock, empty); + + if (cellSubscription != null) { + cellSubscription.unsubscribe(); + cellSubscription = null; + } + + if (langSubscription != null) { + langSubscription.unsubscribe(); + langSubscription = null; + } - /** - * Constructs the view and its components. - *

      Subscribes to a stock's sales price to update the value immediately.

      - *

      Subscribes to the current language to change text immediately.

      - *

      Uses a cell factory to format the string found in the stocksListView.

      - * - * @return the constructed MarketView - */ - private VBox centerPane() { - VBox vBox = new VBox(); - - vBox.setPadding(new Insets(15, 12, 15, 12)); - vBox.setSpacing(10); - //Blue Background color - vBox.setStyle("-fx-background-color: #00bcf0;"); - - Label titleLabel = new Label(); - titleLabel.textProperty().bind(lm.bindString("marketTitle")); - titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; "); - - VBox titleBox = new VBox(titleLabel); - titleBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - titleBox.setAlignment(Pos.CENTER); - titleBox.setPadding(new Insets(4)); - titleBox.setMaxWidth(200); - - // Stock Graph - stockLineChartView.getChart().setMinHeight(0); - stockLineChartView.getChart().setMaxHeight(Double.MAX_VALUE); - stockLineChartView.getChart().setMinWidth(0); - stockLineChartView.getChart().setMaxWidth(Double.MAX_VALUE); - stockLineChartView.getChart().getXAxis().labelProperty().bind(lm.bindString("xAxisLabel")); - stockLineChartView.getChart().getYAxis().labelProperty().bind(lm.bindString("yAxisLabel")); - - // List of stocks - stocksListView = new ListView<>(); - stocksListView.setMinHeight(0); - stocksListView.setMaxHeight(Double.MAX_VALUE); - stocksListView.setMinWidth(0); - stocksListView.setMaxWidth(Double.MAX_VALUE); - stocksListView.setStyle("-fx-background-color: transparent;"); - - stocksListView.setCellFactory(list -> new ListCell<>() { - private Subscription cellSubscription; - private Subscription langSubscription; - - @Override - protected void updateItem(Stock stock, boolean empty) { - super.updateItem(stock, empty); - - if (cellSubscription != null) { - cellSubscription.unsubscribe(); - cellSubscription = null; - } - if (langSubscription != null) { - langSubscription.unsubscribe(); - langSubscription = null; - } - - if (empty || stock == null) { - setText(null); - } else { - Runnable updateTextAction = () -> { - String template = lm.getString("stockCellFormat"); - setText(String.format(template, + if (empty || stock == null) { + setText(null); + } else { + Runnable updateTextAction = () -> { + String template = lm.getString("stockCellFormat"); + setText(String.format(template, stock.getSymbol(), stock.getCompany(), FormatBigDecimal.formatNumber(stock.salesPriceProperty().get()), FormatBigDecimal.formatNumber(stock.getLatestPriceChange()), FormatBigDecimal.formatNumber(stock.getHighestPrice()), FormatBigDecimal.formatNumber(stock.getLowestPrice()) - )); - }; - - cellSubscription = stock.salesPriceProperty().subscribe(newPrice -> updateTextAction.run()); - langSubscription = lm.getBundleProperty().subscribe(bundle -> updateTextAction.run()); - - updateTextAction.run(); - } - } - }); - - HBox searchBox = new HBox(); - searchField = new TextField(); - searchField.promptTextProperty().bind(lm.bindString("search")); - searchBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - searchBox.setPadding(new Insets(8, 14, 8, 14)); - searchBox.setAlignment(Pos.CENTER_LEFT); - searchBox.setMaxWidth(Region.USE_PREF_SIZE); - searchBox.getChildren().addAll(searchField); - - - // Container for list of stocks - HBox stockListBox = new HBox(stocksListView); - stockListBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - // Container for graph of stocks - HBox stockChartBox = new HBox(stockLineChartView.getChart()); - stockChartBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - HBox stockBox = new HBox(stockListBox, stockChartBox); - stockBox.setSpacing(10); - - stockListBox.setPadding(new Insets(4)); - stockListBox.setMinHeight(0); - stockListBox.setMaxHeight(Double.MAX_VALUE); - stockListBox.setMinWidth(0); - stockListBox.setMaxWidth(Double.MAX_VALUE); - - VBox.setVgrow(stocksListView, Priority.ALWAYS); - HBox.setHgrow(stocksListView, Priority.ALWAYS); - VBox.setVgrow(stockLineChartView.getChart(), Priority.ALWAYS); - HBox.setHgrow(stockLineChartView.getChart(), Priority.ALWAYS); - VBox.setVgrow(stockListBox, Priority.ALWAYS); - HBox.setHgrow(stockListBox, Priority.ALWAYS); - VBox.setVgrow(stockChartBox, Priority.ALWAYS); - HBox.setHgrow(stockChartBox, Priority.ALWAYS); - VBox.setVgrow(stockBox, Priority.ALWAYS); - HBox.setHgrow(stockBox, Priority.ALWAYS); - - HBox selectedStockBox = new HBox(); - - Label selectedStockLabel = new Label(); - selectedStockLabel.textProperty().bind(lm.bindString("selectedStock")); - selectedStock = new Label(""); - - selectedStockBox.getChildren().addAll(selectedStockLabel, selectedStock); - - HBox quantityBox = new HBox(); + )); + }; - Label quantityLabel = new Label(); - quantityLabel.textProperty().bind(lm.bindString("purchaseQuantity")); - quantitySpinner = new Spinner<>(1, 1_000_000, 1); - quantitySpinner.setEditable(true); + cellSubscription = stock.salesPriceProperty() + .subscribe(newPrice -> updateTextAction.run()); + langSubscription = lm.getBundleProperty() + .subscribe(bundle -> updateTextAction.run()); - quantityBox.getChildren().addAll(quantityLabel, quantitySpinner); - - HBox calculationBox = new HBox(); - - Label grossLabel = new Label(); - grossLabel.textProperty().bind(lm.bindString("purchaseGross")); - gross = new TextField(); - gross.setEditable(false); - gross.setMaxWidth(200); - Label commissionLabel = new Label(); - commissionLabel.textProperty().bind(lm.bindString("purchaseCommission")); - commission = new TextField(); - commission.setEditable(false); - commission.setMaxWidth(200); - - calculationBox.getChildren().addAll(grossLabel, gross, commissionLabel, commission); - - HBox totalBox = new HBox(); - - Label totalLabel = new Label(); - totalLabel.textProperty().bind(lm.bindString("purchaseTotal")); - total = new TextField(""); - total.setEditable(false); - total.setMaxWidth(200); - - totalBox.getChildren().addAll(totalLabel, total); - - purchaseButton = new Button(); - purchaseButton.textProperty().bind(lm.bindString("purchaseButton")); - - VBox purchaseBox = new VBox(5); - - purchaseBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - purchaseBox.setPadding(new Insets(8, 14, 8, 14)); - purchaseBox.setAlignment(Pos.CENTER_LEFT); - - purchaseBox.getChildren().addAll(selectedStockBox, quantityBox, calculationBox, totalBox, purchaseButton); - - - - vBox.getChildren().addAll(titleBox, searchBox, + updateTextAction.run(); + } + } + }); + + HBox searchBox = new HBox(); + searchField = new TextField(); + searchField.promptTextProperty().bind(lm.bindString("search")); + searchBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + searchBox.setPadding(new Insets(8, 14, 8, 14)); + searchBox.setAlignment(Pos.CENTER_LEFT); + searchBox.setMaxWidth(Region.USE_PREF_SIZE); + searchBox.getChildren().addAll(searchField); + + + // Container for list of stocks + HBox stockListBox = new HBox(stocksListView); + stockListBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + // Container for graph of stocks + HBox stockChartBox = new HBox(stockLineChartView.getChart()); + stockChartBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + HBox stockBox = new HBox(stockListBox, stockChartBox); + stockBox.setSpacing(10); + + stockListBox.setPadding(new Insets(4)); + stockListBox.setMinHeight(0); + stockListBox.setMaxHeight(Double.MAX_VALUE); + stockListBox.setMinWidth(0); + stockListBox.setMaxWidth(Double.MAX_VALUE); + + VBox.setVgrow(stocksListView, Priority.ALWAYS); + HBox.setHgrow(stocksListView, Priority.ALWAYS); + VBox.setVgrow(stockLineChartView.getChart(), Priority.ALWAYS); + HBox.setHgrow(stockLineChartView.getChart(), Priority.ALWAYS); + VBox.setVgrow(stockListBox, Priority.ALWAYS); + HBox.setHgrow(stockListBox, Priority.ALWAYS); + VBox.setVgrow(stockChartBox, Priority.ALWAYS); + HBox.setHgrow(stockChartBox, Priority.ALWAYS); + VBox.setVgrow(stockBox, Priority.ALWAYS); + HBox.setHgrow(stockBox, Priority.ALWAYS); + + HBox selectedStockBox = new HBox(); + + Label selectedStockLabel = new Label(); + selectedStockLabel.textProperty().bind(lm.bindString("selectedStock")); + selectedStock = new Label(""); + + selectedStockBox.getChildren().addAll(selectedStockLabel, selectedStock); + + Label quantityLabel = new Label(); + quantityLabel.textProperty().bind(lm.bindString("purchaseQuantity")); + quantitySpinner = new Spinner<>(1, 1_000_000, 1); + quantitySpinner.setEditable(true); + + HBox quantityBox = new HBox(); + quantityBox.getChildren().addAll(quantityLabel, quantitySpinner); + + Label grossLabel = new Label(); + grossLabel.textProperty().bind(lm.bindString("purchaseGross")); + gross = new TextField(); + gross.setEditable(false); + gross.setMaxWidth(200); + Label commissionLabel = new Label(); + commissionLabel.textProperty().bind(lm.bindString("purchaseCommission")); + commission = new TextField(); + commission.setEditable(false); + commission.setMaxWidth(200); + + HBox calculationBox = new HBox(); + calculationBox.getChildren().addAll(grossLabel, gross, commissionLabel, commission); + + Label totalLabel = new Label(); + totalLabel.textProperty().bind(lm.bindString("purchaseTotal")); + total = new TextField(""); + total.setEditable(false); + total.setMaxWidth(200); + + HBox totalBox = new HBox(); + totalBox.getChildren().addAll(totalLabel, total); + + purchaseButton = new Button(); + purchaseButton.textProperty().bind(lm.bindString("purchaseButton")); + + VBox purchaseBox = new VBox(5); + + purchaseBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + purchaseBox.setPadding(new Insets(8, 14, 8, 14)); + purchaseBox.setAlignment(Pos.CENTER_LEFT); + + purchaseBox.getChildren().addAll( + selectedStockBox, quantityBox, calculationBox, totalBox, purchaseButton); + + + + vbox.getChildren().addAll(titleBox, searchBox, stockBox, purchaseBox); - return vBox; - } - - /** - * Sets and updates subscription to the selected stock's sales price. - * @param stock the selected stock - * @param priceChangeAction the action to run on price change (updates the sales price) - */ - public void setupPriceSubscription(Stock stock, Consumer priceChangeAction) { - if (priceSubscription != null) { - priceSubscription.unsubscribe(); - priceSubscription = null; - } - - if (stock != null) { - priceSubscription = stock.salesPriceProperty().subscribe(newPrice -> { - priceChangeAction.accept(stock); - }); - } - } - - /** - * Updates chart data in the StockLineChartView. - * @param series the {@link javafx.scene.chart.XYChart.Series} data for the chart - * @param companyName the name of the company of the selected stock - */ - public void updateChart(XYChart.Series series, String companyName) { - stockLineChartView.setSeries(series, companyName); - } - - /** - * Unsubscribes from the previous stocks sales price and clears the LineChart. - */ - public void clearChart() { - if (priceSubscription != null) { - priceSubscription.unsubscribe(); - priceSubscription = null; - } - stockLineChartView.getChart().getData().clear(); + return vbox; + } + + /** + * Sets and updates subscription to the selected stock's sales price. + * + * @param stock the selected stock + * @param priceChangeAction the action to run on price change (updates the sales price) + */ + public void setupPriceSubscription(Stock stock, Consumer priceChangeAction) { + if (priceSubscription != null) { + priceSubscription.unsubscribe(); + priceSubscription = null; } - /** - * Sets the action to be run on selecting a stock in stockListView. - * @param action the action to run when selecting a stock - */ - public void onStockSelection(Consumer action) { - stocksListView.getSelectionModel().selectedItemProperty().subscribe(action); + if (stock != null) { + priceSubscription = stock.salesPriceProperty().subscribe(newPrice -> { + priceChangeAction.accept(stock); + }); } - - /** - * Sets the action to be run when clicking the purchase button - * - * @param action the action to run when clicking the purchase button - */ - public void onPurchaseButton(Runnable action) { - purchaseButton.setOnAction(e -> action.run()); + } + + /** + * Updates chart data in the StockLineChartView. + * + * @param series the {@link javafx.scene.chart.XYChart.Series} data for the chart + * @param companyName the name of the company of the selected stock + */ + public void updateChart(XYChart.Series series, String companyName) { + stockLineChartView.setSeries(series, companyName); + } + + /** + * Unsubscribes from the previous stocks sales price and clears the LineChart. + */ + public void clearChart() { + if (priceSubscription != null) { + priceSubscription.unsubscribe(); + priceSubscription = null; } - - public void onQuantitySelect(Runnable action) { - quantitySpinner.valueProperty().addListener((obs, oldValue, newValue) -> { - action.run(); - }); - } - - - - /** - * Sets the exchange to use for the view. - *

      Uses first a {@link FilteredList} to account for what the user has searched for.

      - *

      Then utilises a {@link SortedList} to sort the stocks in the exchange alphabetically - * on their symbol/ticker.

      - * - * @param exchange the exchange to be used for the view - */ - public void setExchange(Exchange exchange) { - this.exchange = exchange; - - - if (exchange != null) { - filteredStocksList = new FilteredList<>(exchange.getObservableStocks()); - - SortedList sortedStocks = new SortedList<>(filteredStocksList); - sortedStocks.setComparator((s1, s2) -> s1.getSymbol().compareToIgnoreCase(s2.getSymbol())); - - stocksListView.setItems(sortedStocks); - - searchField.textProperty().addListener((observable, oldText, newText) -> { - String search = newText == null ? "" : newText.toLowerCase(); - - filteredStocksList.setPredicate(stock -> search.isBlank() + stockLineChartView.getChart().getData().clear(); + } + + /** + * Sets the action to be run on selecting a stock in stockListView. + * + * @param action the action to run when selecting a stock + */ + public void onStockSelection(Consumer action) { + stocksListView.getSelectionModel().selectedItemProperty().subscribe(action); + } + + /** + * Sets the action to be run when clicking the purchase button. + * + * @param action the action to run when clicking the purchase button + */ + public void onPurchaseButton(Runnable action) { + purchaseButton.setOnAction(e -> action.run()); + } + + /** + * Sets the action to be run when changing the value of the quantitySpinner. + * + * @param action the action to run when using the quantitySpinner + */ + public void onQuantitySelect(Runnable action) { + quantitySpinner.valueProperty().addListener((obs, oldValue, newValue) -> { + action.run(); + }); + } + + /** + * Sets the exchange to use for the view. + * + *

      Uses first a {@link FilteredList} to account for what the user has searched for.

      + * + *

      Then utilises a {@link SortedList} to sort the stocks in the exchange alphabetically + * on their symbol/ticker.

      + * + * @param exchange the exchange to be used for the view + */ + public void setExchange(Exchange exchange) { + if (exchange != null) { + filteredStocksList = new FilteredList<>(exchange.getObservableStocks()); + + SortedList sortedStocks = new SortedList<>(filteredStocksList); + sortedStocks.setComparator((s1, s2) -> s1.getSymbol().compareToIgnoreCase(s2.getSymbol())); + + stocksListView.setItems(sortedStocks); + + searchField.textProperty().addListener((observable, oldText, newText) -> { + String search = newText == null ? "" : newText.toLowerCase(); + + filteredStocksList.setPredicate(stock -> search.isBlank() || stock.getSymbol().toLowerCase().contains(search) || stock.getCompany().toLowerCase().contains(search) - ); - }); - } - + ); + }); } - - /** - * Sets the PurchaseCalculator to use for the view. - *

      Uses a {@link Subscription} to observe the calculations made by the current - * Stock and Quantity selected.

      - * - * @param purchaseCalculator the purchaseCalculator to be used for the view - */ - public void setPurchaseCalculator(PurchaseCalculator purchaseCalculator) { - this.purchaseCalculator = purchaseCalculator; - grossSubscription = purchaseCalculator.getGrossProperty().subscribe(newVal -> { - if (newVal != null) { - gross.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { - gross.setText(""); - } - }); - commissionSubscription = purchaseCalculator.getCommissionProperty().subscribe(newVal -> { - if (newVal != null) { + } + + /** + * Sets the PurchaseCalculator to use for the view. + * + *

      Uses a {@link Subscription} to observe the calculations made by the current + * Stock and Quantity selected.

      + * + * @param purchaseCalculator the purchaseCalculator to be used for the view + */ + public void setPurchaseCalculator(PurchaseCalculator purchaseCalculator) { + Subscription grossSubscription = purchaseCalculator.getGrossProperty().subscribe(newVal -> { + if (newVal != null) { + gross.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + gross.setText(""); + } + }); + + Subscription commissionSubscription = + purchaseCalculator.getCommissionProperty().subscribe(newVal -> { + if (newVal != null) { commission.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { + } else { commission.setText(""); - } - }); - totalSubscription = purchaseCalculator.getTotalProperty().subscribe(newVal -> { - if (newVal != null) { - total.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { - total.setText(""); - } - }); - } - - /** - * Returns the selected stock in the stocksListView. - * - * @return the selected stock in the stocksListView - */ - public Stock getSelectedStock() { - return stocksListView.getSelectionModel().getSelectedItem(); - } - - /** - * Returns the numeric value of the quantitySpinner for purchasing {@link Share}s in a stock. - * - * @return an integer of the amount of shares to buy - */ - public int getQuantityPurchase() { - return quantitySpinner.getValue(); - } - - /** - * Sets the company name of the selected stock in the stocksListView. - * - * @param selectedStockString the company name of the selected stock - */ - public void setSelectedStockLabel(String selectedStockString) { - selectedStock.setText(selectedStockString); - } - + } + }); + Subscription totalSubscription = purchaseCalculator.getTotalProperty().subscribe(newVal -> { + if (newVal != null) { + total.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + total.setText(""); + } + }); + } + + /** + * Returns the selected stock in the stocksListView. + * + * @return the selected stock in the stocksListView + */ + public Stock getSelectedStock() { + return stocksListView.getSelectionModel().getSelectedItem(); + } + + /** + * Returns the numeric value of the quantitySpinner for purchasing {@link Share}s in a stock. + * + * @return an integer of the amount of shares to buy + */ + public int getQuantityPurchase() { + return quantitySpinner.getValue(); + } + + /** + * Sets the company name of the selected stock in the stocksListView. + * + * @param selectedStockString the company name of the selected stock + */ + public void setSelectedStockLabel(String selectedStockString) { + selectedStock.setText(selectedStockString); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java index 2360452..838e521 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53.view; +import java.math.RoundingMode; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Button; @@ -12,235 +13,228 @@ import no.ntnu.gruppe53.model.Player; import no.ntnu.gruppe53.service.LanguageManager; -import java.math.RoundingMode; - - /* Yellow Color, Hex: #ffe556 RGB(255, 229, 86) Blue Color, Hex: #00bcf0 RGB(0, 188, 240) Black Color, Hex: #303539 RGB(48, 53, 57) */ - /** * Represents the top bar of the views. + * *

      Provides the user with the ability to switch between views on the fly by * clicking on the buttons representing the desired view.

      + * *

      The {@link no.ntnu.gruppe53.controller.GameController} handles * the actions for the buttons.

      + * *

      Uses a {@link LanguageManager} to set text of UI elements dynamically.

      */ public class NavigationBar extends HBox { - private final LanguageManager lm = LanguageManager.getInstance(); - - private final Button marketButton; - private final Button portfolioButton; - private final Button historyButton; - private final Button endGameButton; - private Subscription netWorthSubscription; - private Subscription moneySubscription; - private Subscription currentWeekSubscription; - - - - private Player player; - private Exchange exchange; - - //Navigation Buttons - - - //Dynamic Labels - private Label playerLabel; - private Label statusLabel; - private Label netWorthLabel; - private Label moneyLabel; - - /** - * Constructs the UI elements of the navigationBar. - *

      Binds the language manager to the textProperty of the UI elements to dynamically change the text.

      - */ - public NavigationBar() { - - this.setPadding(new Insets(15, 12, 15, 12)); - this.setSpacing(10); - //Blue Background color - this.setStyle("-fx-background-color: #00bcf0;" + - "-fx-border-color: transparent transparent #303539 transparent;" + - "-fx-border-width: 0 0 5 0;"); - - - marketButton = new Button(); - marketButton.textProperty().bind(lm.bindString("marketButton")); - marketButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - portfolioButton = new Button(); - portfolioButton.textProperty().bind(lm.bindString("portfolioButton")); - portfolioButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - historyButton = new Button(); - historyButton.textProperty().bind(lm.bindString("historyButton")); - historyButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - - Region spacer = new Region(); - HBox.setHgrow(spacer, Priority.ALWAYS); - - - - HBox playerBox = new HBox(); - - Label playerLabelText = new Label(); - playerLabelText.textProperty().bind(lm.bindString("navPlayerLabelText")); - playerLabel = new Label("Player"); - - playerBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - playerBox.setPadding(new Insets(8, 14, 8, 14)); - playerBox.setAlignment(Pos.CENTER); - - playerBox.getChildren().addAll(playerLabelText, playerLabel); - - HBox statusBox = new HBox(); - - Label statusLabelText = new Label(); - statusLabelText.textProperty().bind(lm.bindString("statusLabelText")); - statusLabel = new Label(); - - statusBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - statusBox.setPadding(new Insets(8, 14, 8, 14)); - statusBox.setAlignment(Pos.CENTER); - - statusBox.getChildren().addAll(statusLabelText, statusLabel); - - HBox moneyBox = new HBox(); - - Label moneyLabelText = new Label(); - moneyLabelText.textProperty().bind(lm.bindString("navMoneyLabelText")); - moneyLabel = new Label(); - - moneyBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - moneyBox.setPadding(new Insets(8, 14, 8, 14)); - moneyBox.setAlignment(Pos.CENTER); - moneyBox.getChildren().addAll(moneyLabelText, moneyLabel); - - HBox networthBox = new HBox(); - - Label networthLabelText = new Label(); - networthLabelText.textProperty().bind(lm.bindString("navNetWorthLabelText")); - netWorthLabel = new Label(); - - networthBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - networthBox.setPadding(new Insets(8, 14, 8, 14)); - networthBox.setAlignment(Pos.CENTER); - networthBox.getChildren().addAll(networthLabelText, netWorthLabel); - - endGameButton = new Button(); - endGameButton.textProperty().bind(lm.bindString("endGameButton")); - endGameButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - - this.getChildren().addAll(marketButton, portfolioButton, historyButton, spacer, playerBox, statusBox, moneyBox, networthBox, endGameButton); - } - /** - * Exposes the marketButton to the GameController. - * - * @param action the action to be run when clicking the button - */ - public void onMarketButtonClick(Runnable action) { - marketButton.setOnAction(e -> action.run()); + private final Button marketButton; + private final Button portfolioButton; + private final Button historyButton; + private final Button endGameButton; + private Subscription netWorthSubscription; + + private Player player; + + // Dynamic Labels + private final Label playerLabel; + private final Label statusLabel; + private final Label netWorthLabel; + private final Label moneyLabel; + + /** + * Constructs the UI elements of the navigationBar. + * + *

      Binds the language manager to the textProperty of the + * UI elements to dynamically change the text.

      + */ + public NavigationBar() { + this.setPadding(new Insets(15, 12, 15, 12)); + this.setSpacing(10); + // Blue Background color + this.setStyle("-fx-background-color: #00bcf0;" + + "-fx-border-color: transparent transparent #303539 transparent;" + + "-fx-border-width: 0 0 5 0;"); + + marketButton = new Button(); + LanguageManager lm = LanguageManager.getInstance(); + marketButton.textProperty().bind(lm.bindString("marketButton")); + marketButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + portfolioButton = new Button(); + portfolioButton.textProperty().bind(lm.bindString("portfolioButton")); + portfolioButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + historyButton = new Button(); + historyButton.textProperty().bind(lm.bindString("historyButton")); + historyButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + + HBox playerBox = new HBox(); + + Label playerLabelText = new Label(); + playerLabelText.textProperty().bind(lm.bindString("navPlayerLabelText")); + playerLabel = new Label("Player"); + + playerBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + playerBox.setPadding(new Insets(8, 14, 8, 14)); + playerBox.setAlignment(Pos.CENTER); + + playerBox.getChildren().addAll(playerLabelText, playerLabel); + + HBox statusBox = new HBox(); + + Label statusLabelText = new Label(); + statusLabelText.textProperty().bind(lm.bindString("statusLabelText")); + statusLabel = new Label(); + + statusBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + statusBox.setPadding(new Insets(8, 14, 8, 14)); + statusBox.setAlignment(Pos.CENTER); + + statusBox.getChildren().addAll(statusLabelText, statusLabel); + + HBox moneyBox = new HBox(); + + Label moneyLabelText = new Label(); + moneyLabelText.textProperty().bind(lm.bindString("navMoneyLabelText")); + moneyLabel = new Label(); + + moneyBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + moneyBox.setPadding(new Insets(8, 14, 8, 14)); + moneyBox.setAlignment(Pos.CENTER); + moneyBox.getChildren().addAll(moneyLabelText, moneyLabel); + + HBox networthBox = new HBox(); + + Label networthLabelText = new Label(); + networthLabelText.textProperty().bind(lm.bindString("navNetWorthLabelText")); + netWorthLabel = new Label(); + + networthBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + networthBox.setPadding(new Insets(8, 14, 8, 14)); + networthBox.setAlignment(Pos.CENTER); + networthBox.getChildren().addAll(networthLabelText, netWorthLabel); + + endGameButton = new Button(); + endGameButton.textProperty().bind(lm.bindString("endGameButton")); + endGameButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + + this.getChildren().addAll( + marketButton, portfolioButton, historyButton, + spacer, playerBox, statusBox, moneyBox, networthBox, endGameButton); + } + + /** + * Exposes the marketButton to the GameController. + * + * @param action the action to be run when clicking the button + */ + public void onMarketButtonClick(Runnable action) { + marketButton.setOnAction(e -> action.run()); + } + + /** + * Exposes the portfolioButton to the GameController. + * + * @param action the action to be run when clicking the button + */ + public void onPortfolioButtonClick(Runnable action) { + portfolioButton.setOnAction(e -> action.run()); + } + + /** + * Exposes the historyButton to the GameController. + * + * @param action the action to be run when clicking the button + */ + public void onHistoryButtonClick(Runnable action) { + historyButton.setOnAction(e -> action.run()); + } + + /** + * Exposes the endGameButton to the GameController. + * + * @param action the action to be run when clicking the button + */ + public void onEndGameButtonClick(Runnable action) { + endGameButton.setOnAction(e -> action.run()); + } + + /** + * Binds a subscription/observer to a {@link Player}. + * + *

      It specifically listens to the players money, net worth and name.

      + * + * @param player the player object to be observed + */ + public void bindPlayer(Player player) { + this.player = player; + + if (netWorthSubscription != null) { + netWorthSubscription.unsubscribe(); } - /** - * Exposes the portfolioButton to the GameController. - * - * @param action the action to be run when clicking the button - */ - public void onPortfolioButtonClick(Runnable action) { - portfolioButton.setOnAction(e -> action.run()); - } + if (player != null) { + playerLabel.setText(player.getName()); + netWorthSubscription = player.getNetWorthProperty().subscribe(newVal -> { + + if (newVal != null) { + netWorthLabel.setText("$" + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + netWorthLabel.setText("$0.00"); + } + }); + + Subscription moneySubscription = player.getMoneyProperty().subscribe(newVal -> { + if (newVal != null) { + moneyLabel.setText("$" + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + moneyLabel.setText("$0.00"); + } + }); - /** - * Exposes the historyButton to the GameController. - * - * @param action the action to be run when clicking the button - */ - public void onHistoryButtonClick(Runnable action) { - historyButton.setOnAction(e -> action.run()); } - - /** - * Exposes the endGameButton to the GameController. - * - * @param action the action to be run when clicking the button - */ - public void onEndGameButtonClick(Runnable action) { - endGameButton.setOnAction(e -> action.run()); - } - - - /** - * Binds a subscription/observer to a {@link Player}. - *

      It specifically listens to the players money, net worth and name.

      - * - * @param player the player object to be observed - */ - public void bindPlayer(Player player) { - this.player = player; - - if (netWorthSubscription != null) { - netWorthSubscription.unsubscribe(); - } - - if (player != null) { - playerLabel.setText(player.getName()); - netWorthSubscription = player.getNetWorthProperty().subscribe(newVal -> { - if (newVal != null) { - netWorthLabel.setText("$" + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { - netWorthLabel.setText("$0.00"); - } - }); - moneySubscription = player.getMoneyProperty().subscribe(newVal -> { - if (newVal != null) { - moneyLabel.setText("$" + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { - moneyLabel.setText("$0.00"); - } - }); - - } - } - - /** - * Binds a subscription/observer to a {@link Exchange}. - *

      It specifically listens to week changes so that it can update the player status.

      - * - * @param exchange the exchange object to be observed - */ - public void bindExchange(Exchange exchange) { - this.exchange = exchange; - - currentWeekSubscription = exchange.getCurrentWeekProperty().subscribe(newVal -> { - statusLabel.setText(player.getStatus(exchange)); - }); - } - - + } + + /** + * Binds a subscription/observer to a {@link Exchange}. + * + *

      It specifically listens to week changes so that it can update the player status.

      + * + * @param exchange the exchange object to be observed + */ + public void bindExchange(Exchange exchange) { + Subscription currentWeekSubscription = exchange.getCurrentWeekProperty().subscribe(newVal -> { + statusLabel.setText(player.getStatus(exchange)); + }); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java index 0711ae2..046fcb1 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java @@ -1,15 +1,19 @@ package no.ntnu.gruppe53.view; -import no.ntnu.gruppe53.model.Player; - +import java.util.Optional; import javafx.geometry.Insets; -import javafx.scene.control.*; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.control.TextFormatter; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; +import no.ntnu.gruppe53.model.Player; import no.ntnu.gruppe53.service.LanguageManager; -import java.util.Optional; - /* Yellow Color, Hex: #ffe556 RGB(255, 229, 86) Blue Color, Hex: #00bcf0 RGB(0, 188, 240) @@ -17,93 +21,94 @@ */ /** - * Represents a prompt for the {@link Player}'s name + * Represents a prompt for the {@link Player}'s name. */ public class PlayerNameChooserView { - private final LanguageManager lm = LanguageManager.getInstance(); - Dialog dialog; - - /** - * Returns the dialog where the player can input a name of maximum 50 characters. - *

      A {@link TextFormatter} is used to set the maximum amount - * of characters for the player name.

      - *

      Uses a {@link LanguageManager} to determine text language.

      - * - * @return the dialog for player name input - */ - public Optional getDialog() { - dialog = new Dialog<>(); - - // Sets dialog title and header text - dialog.setTitle(null); - dialog.setHeaderText(null); - - // Sets the dimensions for the dialog pane - dialog.setResizable(true); - - dialog.getDialogPane().setPrefWidth(320); - dialog.getDialogPane().setPrefHeight(50); - - ButtonType confirmButton = + private final LanguageManager lm = LanguageManager.getInstance(); + Dialog dialog; + + /** + * Returns the dialog where the player can input a name of maximum 50 characters. + * + *

      A {@link TextFormatter} is used to set the maximum amount + * of characters for the player name.

      + * + *

      Uses a {@link LanguageManager} to determine text language.

      + * + * @return the dialog for player name input + */ + public Optional getDialog() { + dialog = new Dialog<>(); + + // Sets dialog title and header text + dialog.setTitle(null); + dialog.setHeaderText(null); + + // Sets the dimensions for the dialog pane + dialog.setResizable(true); + + dialog.getDialogPane().setPrefWidth(320); + dialog.getDialogPane().setPrefHeight(50); + + ButtonType confirmButton = new ButtonType("Confirm", ButtonBar.ButtonData.OK_DONE); - dialog.getDialogPane().getButtonTypes().add(confirmButton); - Button actualConfirmButton = (Button) dialog.getDialogPane().lookupButton(confirmButton); - - actualConfirmButton.textProperty().bind(lm.bindString("playerNameConfirmButton")); + dialog.getDialogPane().getButtonTypes().add(confirmButton); + Button actualConfirmButton = (Button) dialog.getDialogPane().lookupButton(confirmButton); - TextField nameField = new TextField(); + actualConfirmButton.textProperty().bind(lm.bindString("playerNameConfirmButton")); - nameField.setEditable(true); + TextField nameField = new TextField(); - int maxLength = 50; + nameField.setEditable(true); - // Prevents player name over 50 characters + int maxLength = 50; - TextFormatter formatter = new TextFormatter<>(change -> { + // Prevents player name over 50 characters - String newText = change.getControlNewText(); + TextFormatter formatter = new TextFormatter<>(change -> { - if (newText.length() <= maxLength) { - return change; - } + String newText = change.getControlNewText(); - return null; + if (newText.length() <= maxLength) { + return change; + } - }); + return null; - nameField.setTextFormatter(formatter); + }); - // Adds a counter for max characters - Label counterLabel = new Label("0 / 50"); + nameField.setTextFormatter(formatter); - nameField.textProperty().addListener((obs, oldVal, newVal) -> { - counterLabel.setText(newVal.length() + " / 50"); - }); + // Adds a counter for max characters + Label counterLabel = new Label("0 / 50"); - Label infoLabel = new Label(); - infoLabel.textProperty().bind(lm.bindString("playerNameInfo")); + nameField.textProperty().addListener((obs, oldVal, newVal) -> { + counterLabel.setText(newVal.length() + " / 50"); + }); - VBox layout = new VBox(15); + Label infoLabel = new Label(); + infoLabel.textProperty().bind(lm.bindString("playerNameInfo")); - HBox hBox = new HBox(15); + VBox layout = new VBox(15); - layout.setPadding(new Insets(10)); + HBox hbox = new HBox(15); - hBox.getChildren().addAll(nameField, counterLabel); + layout.setPadding(new Insets(10)); - layout.getChildren().addAll(infoLabel, hBox); + hbox.getChildren().addAll(nameField, counterLabel); - dialog.getDialogPane().setContent(layout); + layout.getChildren().addAll(infoLabel, hbox); - dialog.setResultConverter(button -> { - if (button == confirmButton) { + dialog.getDialogPane().setContent(layout); - return nameField.getText(); - } + dialog.setResultConverter(button -> { + if (button == confirmButton) { + return nameField.getText(); + } - return null; - }); + return null; + }); - return dialog.showAndWait(); - } + return dialog.showAndWait(); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java index 7dd6d65..7962367 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java @@ -1,110 +1,116 @@ package no.ntnu.gruppe53.view; -import no.ntnu.gruppe53.model.Player; - +import java.math.BigDecimal; +import java.util.Optional; import javafx.geometry.Insets; -import javafx.scene.control.*; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.Label; +import javafx.scene.control.Spinner; +import javafx.scene.control.SpinnerValueFactory; +import javafx.scene.control.TextFormatter; import javafx.scene.layout.VBox; +import no.ntnu.gruppe53.model.Player; import no.ntnu.gruppe53.service.LanguageManager; -import java.math.BigDecimal; -import java.util.Optional; - /** * Represents the prompt for the {@link Player}'s starting money. + * *

      Uses a {@link LanguageManager} to set text to target language.

      */ public class PlayerStartingMoneyChooserView { - Dialog dialog = new Dialog<>(); - private final LanguageManager lm = LanguageManager.getInstance(); - - /** - * Returns the dialog for the player's starting money. - *

      Uses a {@link TextFormatter} and listener for value integrity.

      - *

      Uses a {@link SpinnerValueFactory} to set min, max, default, - * and step size values for the spinner.

      - * @return the dialog for player's starting money - */ - public Optional getDialog() { - - // Sets dialog title and header text - dialog.setTitle(null); - dialog.setHeaderText(null); - - // Sets the dimensions for the dialog pane - dialog.setResizable(true); - - dialog.getDialogPane().setPrefWidth(320); - dialog.getDialogPane().setPrefHeight(50); - - ButtonType confirmButton = - new ButtonType("Confirm", ButtonBar.ButtonData.OK_DONE); - dialog.getDialogPane().getButtonTypes().add(confirmButton); - - Button actualConfirmButton = (Button) dialog.getDialogPane().lookupButton(confirmButton); - - actualConfirmButton.textProperty().bind(lm.bindString("playerStartingMoneyConfirmButton")); - - Spinner spinner = new Spinner<>(); - - // Sets allowed values and step size for the spinner - SpinnerValueFactory.DoubleSpinnerValueFactory valueFactory = - new SpinnerValueFactory.DoubleSpinnerValueFactory( + Dialog dialog = new Dialog<>(); + private final LanguageManager lm = LanguageManager.getInstance(); + + /** + * Returns the dialog for the player's starting money. + * + *

      Uses a {@link TextFormatter} and listener for value integrity.

      + * + *

      Uses a {@link SpinnerValueFactory} to set min, max, default, + * and step size values for the spinner.

      + * + * @return the dialog for player's starting money + */ + public Optional getDialog() { + // Sets dialog title and header text + dialog.setTitle(null); + dialog.setHeaderText(null); + + // Sets the dimensions for the dialog pane + dialog.setResizable(true); + + dialog.getDialogPane().setPrefWidth(320); + dialog.getDialogPane().setPrefHeight(50); + + ButtonType confirmButton = + new ButtonType("Confirm", ButtonBar.ButtonData.OK_DONE); + dialog.getDialogPane().getButtonTypes().add(confirmButton); + + Button actualConfirmButton = (Button) dialog.getDialogPane().lookupButton(confirmButton); + + actualConfirmButton.textProperty().bind(lm.bindString("playerStartingMoneyConfirmButton")); + + Spinner spinner = new Spinner<>(); + + // Sets allowed values and step size for the spinner + SpinnerValueFactory.DoubleSpinnerValueFactory valueFactory = + new SpinnerValueFactory.DoubleSpinnerValueFactory( 1000.0, // min 1000000.0, // max 5000.0, // initial value 100.0 // step-size - ); - - spinner.setValueFactory(valueFactory); - - spinner.setEditable(true); + ); - // Formats the text to only allow numbers, and doubles preceded by a number - TextFormatter formatter = new TextFormatter<>(change -> { + spinner.setValueFactory(valueFactory); - String newText = change.getControlNewText(); + spinner.setEditable(true); - if (newText.matches("\\d+(\\.\\d*)?")) { + // Formats the text to only allow numbers, and doubles preceded by a number + TextFormatter formatter = new TextFormatter<>(change -> { - return change; - } + String newText = change.getControlNewText(); - return null; - }); + if (newText.matches("\\d+(\\.\\d*)?")) { + return change; + } - spinner.focusedProperty().addListener((obs, oldVal, newVal) -> { + return null; + }); - if (!newVal) { - spinner.increment(0); - } - }); + spinner.focusedProperty().addListener((obs, oldVal, newVal) -> { - spinner.getEditor().setTextFormatter(formatter); + if (!newVal) { + spinner.increment(0); + } + }); - spinner.setPrefWidth(200); + spinner.getEditor().setTextFormatter(formatter); - Label infoLabel = new Label(); - infoLabel.textProperty().bind(lm.bindString("playerStartingMoneyInfoLabel")); + spinner.setPrefWidth(200); - VBox layout = new VBox(15); + Label infoLabel = new Label(); + infoLabel.textProperty().bind(lm.bindString("playerStartingMoneyInfoLabel")); - layout.setPadding(new Insets(10)); + VBox layout = new VBox(15); - layout.getChildren().addAll(infoLabel, spinner); + layout.setPadding(new Insets(10)); - dialog.getDialogPane().setContent(layout); + layout.getChildren().addAll(infoLabel, spinner); - dialog.setResultConverter(button -> { + dialog.getDialogPane().setContent(layout); - if (button == confirmButton) { + dialog.setResultConverter(button -> { - return BigDecimal.valueOf(spinner.getValue()); - } + if (button == confirmButton) { + return BigDecimal.valueOf(spinner.getValue()); + } - return null; - }); + return null; + }); - return dialog.showAndWait(); - } + return dialog.showAndWait(); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java index 4c55fe5..416d5c0 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java @@ -3,17 +3,19 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.function.Consumer; - -import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; -import javafx.geometry.Pos; -import javafx.scene.control.*; -import javafx.scene.layout.*; +import javafx.scene.control.TextField; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import javafx.scene.text.Text; import javafx.scene.text.TextFlow; import javafx.util.Subscription; @@ -29,354 +31,351 @@ Black Color, Hex: #303539 RGB(48, 53, 57) */ - /** * Represent a view of the {@link Player}s {@link no.ntnu.gruppe53.model.Portfolio}. + * *

      Lets the player view their current {@link Share} assets, see their * current sale value, whether the value has gone up or down since they purchased, * and the ability to sell the share for money.

      + * *

      Extends the {@link BorderPane}

      for easy layout setup. + * *

      Events are handled by the {@link no.ntnu.gruppe53.controller.GameController}.

      + * *

      Uses a {@link LanguageManager} to dynamically set text based on target language.

      */ public class PortfolioView extends BorderPane { - private final LanguageManager lm = LanguageManager.getInstance(); - - private Button sellButton; - - //Static Labels - private Label titleLabel; - - //Dynamic Labels - private Label selectedShare; - private TextField gross; - private TextField commission; - private TextField tax; - private TextField total; - private TextField profit; - private TextField searchField; - - //PortfolioListView - private ListView portfolioListView; - private FilteredList filteredPortfolioList; - - private Player player; - private SaleCalculator saleCalculator; - - private Subscription grossSubscription; - private Subscription commissionSubscription; - private Subscription taxSubscription; - private Subscription totalSubscription; - private Subscription profitSubscription; - - /** - * Builds the view components and places them in the center pane. - */ - public PortfolioView() { - this.setCenter(centerPane()); - } - - /** - * Constructs the view of the portfolio. - *

      Subscribes to shares for sales price updates.

      - *

      Uses a cell factory to format the displayed string representing each share. - * The sell factory has the text dynamically set by the language manager.

      - * - * @return a VBox of the portfolio view - */ - private VBox centerPane() { - VBox vBox = new VBox(); - vBox.setPadding(new Insets(15, 12, 15, 12)); - vBox.setSpacing(10); - //Blue Background color - vBox.setStyle("-fx-background-color: #00bcf0; "); - - titleLabel = new Label(); - titleLabel.textProperty().bind(lm.bindString("portfolioTitle")); - titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: #303539;"); - - VBox titleBox = new VBox(titleLabel); - titleBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - titleBox.setAlignment(Pos.CENTER); - titleBox.setPadding(new Insets(4)); - titleBox.setMaxWidth(200); - - portfolioListView = new ListView<>(); - portfolioListView.setMinHeight(0); - portfolioListView.setMaxHeight(Double.MAX_VALUE); - - portfolioListView.setCellFactory(list -> new ListCell<>() { - private Subscription cellSubscription; - private Subscription langSubscription; - - @Override - protected void updateItem(Share item, boolean empty) { - super.updateItem(item, empty); - - if (cellSubscription != null) { - cellSubscription.unsubscribe(); - cellSubscription = null; - } - if (langSubscription != null) { - langSubscription.unsubscribe(); - langSubscription = null; - } - - if (empty || item == null || item.getStock() == null) { - setText(null); - setGraphic(null); - } else { - setText(null); - - - Runnable updateGraphicAction = () -> { - BigDecimal currentPrice = item.getStock().salesPriceProperty().get(); - setGraphic(createColoredShareText(item, currentPrice)); - }; - - cellSubscription = item.getStock().salesPriceProperty().subscribe(currentPrice -> { - updateGraphicAction.run(); - }); - - langSubscription = lm.getBundleProperty().subscribe(bundle -> { - updateGraphicAction.run(); - }); - - updateGraphicAction.run(); - } + private final LanguageManager lm = LanguageManager.getInstance(); + + private Button sellButton; + + // Dynamic Labels + private Label selectedShare; + private TextField gross; + private TextField commission; + private TextField tax; + private TextField total; + private TextField profit; + private TextField searchField; + + // PortfolioListView + private ListView portfolioListView; + private FilteredList filteredPortfolioList; + + /** + * Builds the view components and places them in the center pane. + */ + public PortfolioView() { + this.setCenter(centerPane()); + } + + /** + * Constructs the view of the portfolio. + * + *

      Subscribes to shares for sales price updates.

      + * + *

      Uses a cell factory to format the displayed string representing each share. + * The sell factory has the text dynamically set by the language manager.

      + * + * @return a VBox of the portfolio view + */ + private VBox centerPane() { + VBox vbox = new VBox(); + vbox.setPadding(new Insets(15, 12, 15, 12)); + vbox.setSpacing(10); + // Blue Background color + vbox.setStyle("-fx-background-color: #00bcf0; "); + + Label titleLabel = new Label(); + titleLabel.textProperty().bind(lm.bindString("portfolioTitle")); + titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: #303539;"); + + VBox titleBox = new VBox(titleLabel); + titleBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + titleBox.setAlignment(Pos.CENTER); + titleBox.setPadding(new Insets(4)); + titleBox.setMaxWidth(200); + + portfolioListView = new ListView<>(); + portfolioListView.setMinHeight(0); + portfolioListView.setMaxHeight(Double.MAX_VALUE); + + portfolioListView.setCellFactory(list -> new ListCell<>() { + private Subscription cellSubscription; + private Subscription langSubscription; + + @Override + protected void updateItem(Share item, boolean empty) { + super.updateItem(item, empty); + + if (cellSubscription != null) { + cellSubscription.unsubscribe(); + cellSubscription = null; } - }); - - HBox searchBox = new HBox(); - searchField = new TextField(); - searchField.promptTextProperty().bind(lm.bindString("search")); - searchBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - searchBox.setPadding(new Insets(8, 14, 8, 14)); - searchBox.setAlignment(Pos.CENTER_LEFT); - searchBox.setMaxWidth(Region.USE_PREF_SIZE); - searchBox.getChildren().addAll(searchField); - - VBox portfolioListBox = new VBox(portfolioListView); - portfolioListBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - portfolioListBox.setPadding(new Insets(4)); - portfolioListBox.setMinHeight(0); - portfolioListBox.setMaxHeight(Double.MAX_VALUE); - - // Ensures the portfoliolist grows to fit the available screenspace - VBox.setVgrow(portfolioListView, Priority.ALWAYS); - VBox.setVgrow(portfolioListBox, Priority.ALWAYS); - - - HBox selectedShareBox = new HBox(); - - Label selectedShareLabel = new Label(); - selectedShareLabel.textProperty().bind(lm.bindString("selectedShare")); - selectedShare = new Label(); - - selectedShareBox.getChildren().addAll(selectedShareLabel, selectedShare); - - HBox calculationBox = new HBox(); - Label grossLabel = new Label(); - grossLabel.textProperty().bind(lm.bindString("portfolioGross")); - gross = new TextField(); - gross.setEditable(false); - gross.setMaxWidth(200); - Label commissionLabel = new Label(); - commissionLabel.textProperty().bind(lm.bindString("portfolioCommission")); - commission = new TextField(); - commission.setEditable(false); - commission.setMaxWidth(200); - Label taxLabel = new Label(); - taxLabel.textProperty().bind(lm.bindString("portfolioTax")); - tax = new TextField(); - tax.setEditable(false); - tax.setMaxWidth(200); - - calculationBox.getChildren().addAll(grossLabel, gross, commissionLabel, commission, taxLabel, tax); - - - - HBox totalBox = new HBox(); - - Label totalLabel = new Label(); - totalLabel.textProperty().bind(lm.bindString("portfolioTotal")); - total = new TextField(); - total.setEditable(false); - total.setMaxWidth(200); - - Label profitLabel = new Label(); - profitLabel.textProperty().bind(lm.bindString("portfolioProfit")); - profit = new TextField(); - profit.setEditable(false); - profit.setMaxWidth(200); - - totalBox.getChildren().addAll(totalLabel, total, profitLabel, profit); - - sellButton = new Button(); - sellButton.textProperty().bind(lm.bindString("sellButton")); + if (langSubscription != null) { + langSubscription.unsubscribe(); + langSubscription = null; + } - VBox saleBox = new VBox(5); + if (empty || item == null || item.getStock() == null) { + setText(null); + setGraphic(null); + } else { + setText(null); - saleBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - saleBox.setPadding(new Insets(8, 14, 8, 14)); - saleBox.setAlignment(Pos.CENTER_LEFT); + Runnable updateGraphicAction = () -> { + BigDecimal currentPrice = item.getStock().salesPriceProperty().get(); + setGraphic(createColoredShareText(item, currentPrice)); + }; - saleBox.getChildren().addAll(selectedShareBox, calculationBox, totalBox, sellButton); + cellSubscription = item.getStock().salesPriceProperty().subscribe(currentPrice -> { + updateGraphicAction.run(); + }); + langSubscription = lm.getBundleProperty().subscribe(bundle -> { + updateGraphicAction.run(); + }); - vBox.getChildren().addAll(titleBox, searchBox, + updateGraphicAction.run(); + } + } + }); + + HBox searchBox = new HBox(); + searchField = new TextField(); + searchField.promptTextProperty().bind(lm.bindString("search")); + searchBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + searchBox.setPadding(new Insets(8, 14, 8, 14)); + searchBox.setAlignment(Pos.CENTER_LEFT); + searchBox.setMaxWidth(Region.USE_PREF_SIZE); + searchBox.getChildren().addAll(searchField); + + VBox portfolioListBox = new VBox(portfolioListView); + portfolioListBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + portfolioListBox.setPadding(new Insets(4)); + portfolioListBox.setMinHeight(0); + portfolioListBox.setMaxHeight(Double.MAX_VALUE); + + // Ensures the portfoliolist grows to fit the available screenspace + VBox.setVgrow(portfolioListView, Priority.ALWAYS); + VBox.setVgrow(portfolioListBox, Priority.ALWAYS); + + HBox selectedShareBox = new HBox(); + + Label selectedShareLabel = new Label(); + selectedShareLabel.textProperty().bind(lm.bindString("selectedShare")); + selectedShare = new Label(); + + selectedShareBox.getChildren().addAll(selectedShareLabel, selectedShare); + + Label grossLabel = new Label(); + grossLabel.textProperty().bind(lm.bindString("portfolioGross")); + gross = new TextField(); + gross.setEditable(false); + gross.setMaxWidth(200); + Label commissionLabel = new Label(); + commissionLabel.textProperty().bind(lm.bindString("portfolioCommission")); + commission = new TextField(); + commission.setEditable(false); + commission.setMaxWidth(200); + Label taxLabel = new Label(); + taxLabel.textProperty().bind(lm.bindString("portfolioTax")); + tax = new TextField(); + tax.setEditable(false); + tax.setMaxWidth(200); + + HBox calculationBox = new HBox(); + calculationBox.getChildren().addAll( + grossLabel, gross, commissionLabel, commission, taxLabel, tax); + + Label totalLabel = new Label(); + totalLabel.textProperty().bind(lm.bindString("portfolioTotal")); + total = new TextField(); + total.setEditable(false); + total.setMaxWidth(200); + + Label profitLabel = new Label(); + profitLabel.textProperty().bind(lm.bindString("portfolioProfit")); + profit = new TextField(); + profit.setEditable(false); + profit.setMaxWidth(200); + + HBox totalBox = new HBox(); + totalBox.getChildren().addAll(totalLabel, total, profitLabel, profit); + + sellButton = new Button(); + sellButton.textProperty().bind(lm.bindString("sellButton")); + + VBox saleBox = new VBox(5); + + saleBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + saleBox.setPadding(new Insets(8, 14, 8, 14)); + saleBox.setAlignment(Pos.CENTER_LEFT); + + saleBox.getChildren().addAll(selectedShareBox, calculationBox, totalBox, sellButton); + + + vbox.getChildren().addAll(titleBox, searchBox, portfolioListBox, saleBox); - return vBox; - } - - /** - * Exposes the sell button to the GameController. - * @param action the action to be run when pressing the sell button - */ - public void onSellButton(Runnable action) { - sellButton.setOnAction(e -> action.run()); - } - - /** - * Sets the action to be run on selecting a share in portfolioListView. - * @param action the action to run when selecting a share - */ - public void onShareSelection(Consumer action) { - portfolioListView.getSelectionModel().selectedItemProperty().subscribe(action); - } - - /** - * Gets the selected share in the portfolioListView. - * - * @return the currently selected share - */ - public Share getSelectedShare() { - return portfolioListView.getSelectionModel().getSelectedItem(); - } - - /** - * Sets the player to be observed. - *

      The values observed are the players portfolio and name.

      - *

      Uses a {@link FilteredList} to get and filter the shares and - * binds it to the portfolioListView.

      - * - * @param player the player to be observed - */ - public void setPlayer(Player player) { - this.player = player; - - if (player != null && player.getPortfolio() != null) { - - filteredPortfolioList = new FilteredList<>(player.getPortfolio().getShares()); - - portfolioListView.setItems(filteredPortfolioList); - - searchField.textProperty().addListener((observable, oldText, newText) -> { - String search = newText == null ? "" : newText.toLowerCase(); - - filteredPortfolioList.setPredicate(share -> search.isBlank() + return vbox; + } + + /** + * Exposes the sell button to the GameController. + * + * @param action the action to be run when pressing the sell button + */ + public void onSellButton(Runnable action) { + sellButton.setOnAction(e -> action.run()); + } + + /** + * Sets the action to be run on selecting a share in portfolioListView. + * + * @param action the action to run when selecting a share + */ + public void onShareSelection(Consumer action) { + portfolioListView.getSelectionModel().selectedItemProperty().subscribe(action); + } + + /** + * Gets the selected share in the portfolioListView. + * + * @return the currently selected share + */ + public Share getSelectedShare() { + return portfolioListView.getSelectionModel().getSelectedItem(); + } + + /** + * Sets the player to be observed. + * + *

      The values observed are the players portfolio and name.

      + * + *

      Uses a {@link FilteredList} to get and filter the shares and + * binds it to the portfolioListView.

      + * + * @param player the player to be observed + */ + public void setPlayer(Player player) { + if (player != null && player.getPortfolio() != null) { + filteredPortfolioList = new FilteredList<>(player.getPortfolio().getShares()); + + portfolioListView.setItems(filteredPortfolioList); + + searchField.textProperty().addListener((observable, oldText, newText) -> { + String search = newText == null ? "" : newText.toLowerCase(); + + filteredPortfolioList.setPredicate(share -> search.isBlank() || share.getStock().getSymbol().toLowerCase().contains(search) || share.getStock().getCompany().toLowerCase().contains(search) - ); - }); - - } else { + ); + }); - portfolioListView.setItems(null); - } - } + } else { - /** - * Sets the share name of the selected share in the portfolioListView. - * - * @param selectedShareString the share name of the selected share - */ - public void setSelectedShareLabel(String selectedShareString) { - selectedShare.setText(selectedShareString); + portfolioListView.setItems(null); } - - /** - * Sets the SaleCalculator to use for the view. - *

      Uses a {@link Subscription} to observe the calculations made by the current - * Share selected.

      - * - * @param saleCalculator the SaleCalculator to be used for the view - */ - public void setSaleCalculator(SaleCalculator saleCalculator) { - this.saleCalculator = saleCalculator; - grossSubscription = saleCalculator.getGrossProperty().subscribe(newVal -> { - if (newVal != null) { + } + + /** + * Sets the share name of the selected share in the portfolioListView. + * + * @param selectedShareString the share name of the selected share + */ + public void setSelectedShareLabel(String selectedShareString) { + selectedShare.setText(selectedShareString); + } + + /** + * Sets the SaleCalculator to use for the view. + * + *

      Uses a {@link Subscription} to observe the calculations made by the current + * Share selected.

      + * + * @param saleCalculator the SaleCalculator to be used for the view + */ + public void setSaleCalculator(SaleCalculator saleCalculator) { + Subscription grossSubscription = + saleCalculator.getGrossProperty().subscribe(newVal -> { + if (newVal != null) { gross.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { + } else { gross.setText(""); - } - }); - commissionSubscription = saleCalculator.getCommissionProperty().subscribe(newVal -> { - if (newVal != null) { + } + }); + + Subscription commissionSubscription = + saleCalculator.getCommissionProperty().subscribe(newVal -> { + if (newVal != null) { commission.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { + } else { commission.setText(""); - } - }); - taxSubscription = saleCalculator.getTaxProperty().subscribe(newVal -> { - if (newVal != null) { - tax.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN).abs()); - } else { - tax.setText(""); - } - }); - totalSubscription = saleCalculator.getTotalProperty().subscribe(newVal -> { - if (newVal != null) { - total.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { - total.setText(""); - } - }); - profitSubscription = saleCalculator.getProfitProperty().subscribe(newVal -> { - if (newVal != null) { - profit.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { - profit.setText(""); - } - }); - } - + } + }); - /** - * Formats the text representation of the share in the portfolioListView - * and applies a color to it based on its current sales price compared - * to the buy price of the share. - * - * @param share the share to be formatted - * @param currentPrice the cusort the stocks in the exchange alphabetically - * on their symbol/tickerrrent price of the share - * @return a {@link TextFlow} representation of the share - */ - private TextFlow createColoredShareText(Share share, BigDecimal currentPrice) { - String text = share.getStock().getSymbol() + Subscription taxSubscription = saleCalculator.getTaxProperty().subscribe(newVal -> { + if (newVal != null) { + tax.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN).abs()); + } else { + tax.setText(""); + } + }); + + Subscription totalSubscription = saleCalculator.getTotalProperty().subscribe(newVal -> { + if (newVal != null) { + total.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + total.setText(""); + } + }); + + Subscription profitSubscription = saleCalculator.getProfitProperty().subscribe(newVal -> { + if (newVal != null) { + profit.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + profit.setText(""); + } + }); + } + + /** + * Formats the text representation of the share in the portfolioListView + * and applies a color to it based on its current sales price compared + * to the buy price of the share. + * + * @param share the share to be formatted + * @param currentPrice current price of the stock the share belongs to + * @return a {@link TextFlow} representation of the share + */ + private TextFlow createColoredShareText(Share share, BigDecimal currentPrice) { + String text = share.getStock().getSymbol() + " - " + share.getStock().getCompany() + " | " @@ -390,17 +389,22 @@ private TextFlow createColoredShareText(Share share, BigDecimal currentPrice) { + lm.getString("portfolioCurrentPrice") + "$"; - Text t1 = new Text(text); - Text tValue = new Text(FormatBigDecimal.formatNumber(currentPrice)); + Text t1 = new Text(text); + Text tvalue = new Text(FormatBigDecimal.formatNumber(currentPrice)); - BigDecimal buyPrice = share.getPurchasePrice(); - if (buyPrice != null && currentPrice != null) { - int compare = currentPrice.compareTo(buyPrice); - if (compare > 0) tValue.setStyle("-fx-fill: #2ecc71; -fx-font-weight: bold;"); - else if (compare < 0) tValue.setStyle("-fx-fill: #e74c3c; -fx-font-weight: bold;"); - else tValue.setStyle("-fx-fill: #000000;"); - } + BigDecimal buyPrice = share.getPurchasePrice(); + if (buyPrice != null && currentPrice != null) { + int compare = currentPrice.compareTo(buyPrice); - return new TextFlow(t1, tValue); + if (compare > 0) { + tvalue.setStyle("-fx-fill: #2ecc71; -fx-font-weight: bold;"); + } else if (compare < 0) { + tvalue.setStyle("-fx-fill: #e74c3c; -fx-font-weight: bold;"); + } else { + tvalue.setStyle("-fx-fill: #000000;"); + } } + + return new TextFlow(t1, tvalue); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java b/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java index a49d52b..264fa74 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java @@ -5,7 +5,6 @@ import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.VBox; - import javafx.util.Subscription; import no.ntnu.gruppe53.controller.StartViewController; import no.ntnu.gruppe53.controller.ViewController; @@ -13,77 +12,77 @@ /** * Represents the first page seen by the user of the application. + * *

      Uses a {@link LanguageManager} for dynamically setting button and label text. * Subscribes to changes in language set by the manager.

      */ public class StartView extends VBox { - private final Button newGameButton; - private final Button languageButton; - private final Button quitButton; - private final Image logo; - private Subscription languageSubscription; - - /** - * Constructs the view and sets parameters to be exposed for the {@link StartViewController}. - * - * @param vm the {@link ViewController} for the view - */ - public StartView(ViewController vm) { - LanguageManager lm = LanguageManager.getInstance(); + private final Button newGameButton; + private final Button languageButton; + private final Button quitButton; - this.setAlignment(Pos.CENTER); - this.setSpacing(20); + /** + * Constructs the view and sets parameters to be exposed for the {@link StartViewController}. + * + * @param vm the {@link ViewController} for the view + */ + public StartView(ViewController vm) { + this.setAlignment(Pos.CENTER); + this.setSpacing(20); - this.setStyle("-fx-background-color: #00bcf0;"); + this.setStyle("-fx-background-color: #00bcf0;"); - logo = new Image(getClass().getClassLoader().getResourceAsStream("Millions-logo.png")); - ImageView logoView = new ImageView(logo); - logoView.setFitWidth(300); - logoView.setPreserveRatio(true); + Image logo = new Image(getClass().getClassLoader().getResourceAsStream("Millions-logo.png")); + ImageView logoView = new ImageView(logo); + logoView.setFitWidth(300); + logoView.setPreserveRatio(true); - newGameButton = new Button(); - newGameButton.textProperty().bind(lm.bindString("newGame")); + LanguageManager lm = LanguageManager.getInstance(); + newGameButton = new Button(); + newGameButton.textProperty().bind(lm.bindString("newGame")); - languageButton = new Button(); - languageButton.setPrefWidth(200); - languageButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - languageSubscription = lm.getBundleProperty().subscribe(bundle -> { - languageButton.setText(lm.getString("languageButtonLabel")); - }); + languageButton = new Button(); + languageButton.setPrefWidth(200); + languageButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + Subscription languageSubscription = lm.getBundleProperty().subscribe(bundle -> { + languageButton.setText(lm.getString("languageButtonLabel")); + }); - quitButton = new Button(); - quitButton.textProperty().bind(lm.bindString("quitGame")); + quitButton = new Button(); + quitButton.textProperty().bind(lm.bindString("quitGame")); - newGameButton.setPrefWidth(200); - newGameButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - quitButton.setPrefWidth(200); - quitButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + newGameButton.setPrefWidth(200); + newGameButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + quitButton.setPrefWidth(200); + quitButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - this.getChildren().addAll(logoView, newGameButton, languageButton, quitButton); - } + this.getChildren().addAll(logoView, newGameButton, languageButton, quitButton); + } - /** - * Sets the method to be run when the new game button is pressed - * - * @param action the method to execute on button press - */ - public void setOnNewGame(Runnable action) { - newGameButton.setOnAction(e -> action.run()); - } + /** + * Sets the method to be run when the new game button is pressed. + * + * @param action the method to execute on button press + */ + public void setOnNewGame(Runnable action) { + newGameButton.setOnAction(e -> action.run()); + } - /** - * Sets the method to be run when the languageButton is pressed - * - * @param action the method to execute on button press - */ - public void setOnLanguage(Runnable action) {languageButton.setOnAction(e -> action.run());} + /** + * Sets the method to be run when the languageButton is pressed. + * + * @param action the method to execute on button press + */ + public void setOnLanguage(Runnable action) { + languageButton.setOnAction(e -> action.run()); + } - /** - * Sets the method to be run when the quit game button is pressed - * - * @param action the method to execute on button press - */ - public void setOnQuit(Runnable action) { - quitButton.setOnAction(e -> action.run()); - } + /** + * Sets the method to be run when the quit game button is pressed. + * + * @param action the method to execute on button press + */ + public void setOnQuit(Runnable action) { + quitButton.setOnAction(e -> action.run()); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/StockLineChartView.java b/millions/src/main/java/no/ntnu/gruppe53/view/StockLineChartView.java index adb5dc5..048d5b9 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/StockLineChartView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/StockLineChartView.java @@ -1,126 +1,128 @@ package no.ntnu.gruppe53.view; + +import java.util.List; import javafx.geometry.Side; import javafx.scene.chart.LineChart; import javafx.scene.chart.NumberAxis; import javafx.scene.chart.XYChart; import no.ntnu.gruppe53.model.Stock; -import java.util.List; - /** * Represents a {@link LineChart} view of a {@link Stock}. + * *

      Y-axis: Price as a {@code double} value. Represented by a {@link NumberAxis}

      + * *

      X-axis: Week as an {@code int} value. Represented by a {@code NumberAxis}

      */ public class StockLineChartView { - private final NumberAxis xAxis = new NumberAxis(); - private final NumberAxis yAxis = new NumberAxis(); - - private final LineChart chart; - - /** - * Sets the settings for the {@code LineChart}. - *

      Sets autoscale, stepsize and manual values for the range x- and y-axis, as well - * as the labels.

      - *

      Initializes the LineChart.

      - */ - - public StockLineChartView() { - xAxis.setAutoRanging(false); - yAxis.setAutoRanging(true); - yAxis.setForceZeroInRange(false); - - xAxis.setLowerBound(1); - - xAxis.setLabel("Week"); - yAxis.setLabel("Price"); - - chart = new LineChart<>(xAxis, yAxis); - } - - /** - * Returns the constructed LineChart. - * - * @return the current LineChart - */ - public LineChart getChart() { - return chart; - } - - /** - * Calculates the max x-axis value for a {@link XYChart.Series}. - * - * @param series the XYChart series to determine max x-axis value from - * @return the maximum x-axis value in the XYChart.Series - */ - - private double calculateXAxisMax(XYChart.Series series) { - return series.getData().stream() + private final NumberAxis xaxis = new NumberAxis(); + + private final LineChart chart; + + /** + * Sets the settings for the {@code LineChart}. + * + *

      Sets autoscale, stepsize and manual values for the range x- and y-axis, as well + * as the labels.

      + * + *

      Initializes the LineChart.

      + */ + public StockLineChartView() { + xaxis.setAutoRanging(false); + NumberAxis yaxis = new NumberAxis(); + yaxis.setAutoRanging(true); + yaxis.setForceZeroInRange(false); + + xaxis.setLowerBound(1); + + xaxis.setLabel("Week"); + yaxis.setLabel("Price"); + + chart = new LineChart<>(xaxis, yaxis); + } + + /** + * Returns the constructed LineChart. + * + * @return the current LineChart + */ + public LineChart getChart() { + return chart; + } + + /** + * Calculates the max x-axis value for a {@link XYChart.Series}. + * + * @param series the XYChart series to determine max x-axis value from + * @return the maximum x-axis value in the XYChart.Series + */ + private double calculatexAxisMax(XYChart.Series series) { + return series.getData().stream() .mapToDouble(data -> data.getXValue() .doubleValue()) - .max(). - orElse(0); + .max() + .orElse(0); + } + + /** + * Calculates the max x-axis value for a {@code List}. + * + * @param seriesList the list of XYChart.Series to determine max x-axis value from + * @return the maximum x-axis value in the List of XYChart.Series + */ + private double calculatexAxisMax(List> seriesList) { + double xmax = 0; + + for (XYChart.Series s : seriesList) { + double tempMax = calculatexAxisMax(s); + + if (tempMax > xmax) { + xmax = tempMax; + } } - /** - * Calculates the max x-axis value for a {@code List}. - * - * @param seriesList the list of XYChart.Series to determine max x-axis value from - * @return the maximum x-axis value in the List of XYChart.Series - */ + return xmax; + } - private double calculateXAxisMax(List> seriesList) { - double xMax = 0; + /** + * Sets the {@code XYChart.Series} containing the x- and y-values for the {@code LineChart}. + * + * @param series the XYChart.Series containing the x- and y-values for the LineChart + * @param title the name of the company the stock represents + */ - for (XYChart.Series s : seriesList) { - double tempMax = calculateXAxisMax(s); - if (tempMax > xMax) { - xMax = tempMax; - } - } + public void setSeries(XYChart.Series series, String title) { + chart.getData().clear(); - return xMax; - } - - /** - * Sets the {@code XYChart.Series} containing the x- and y-values for the {@code LineChart}. - * - * @param series the XYChart.Series containing the x- and y-values for the LineChart - * @param title the name of the company the stock represents - */ + chart.setTitle(title); + chart.setLegendSide(Side.RIGHT); - public void setSeries(XYChart.Series series, String title) { - chart.getData().clear(); + double xmax = calculatexAxisMax(series); + xaxis.setUpperBound(xmax); - chart.setTitle(title); - chart.setLegendSide(Side.RIGHT); + chart.getData().add(series); + } - double xMax = calculateXAxisMax(series); - xAxis.setUpperBound(xMax); + /** + * Sets the {@code XYChart.Series} containing the x- and y-values for the {@code LineChart}. + * + *

      Here the series values are given as a list of {@code XYChart.Series} objects.

      + * + * @param seriesList the list of XYChart.Series containing the x- and y-values for each stock + */ + public void setSeries(List> seriesList) { + chart.getData().clear(); - chart.getData().add(series); - } + chart.setTitle("Price History"); + chart.setLegendSide(Side.RIGHT); - /** - * Sets the {@code XYChart.Series} containing the x- and y-values for the {@code LineChart}. - *

      Here the series values are given as a list of {@code XYChart.Series} objects.

      - * - * @param seriesList the list of XYChart.Series containing the x- and y-values for each stock - */ + double xax = calculatexAxisMax(seriesList); - public void setSeries(List> seriesList) { - chart.getData().clear(); - - chart.setTitle("Price History"); - chart.setLegendSide(Side.RIGHT); - - double xMax = calculateXAxisMax(seriesList); - - if (xMax != 0) { - xAxis.setUpperBound(xMax); - } - - chart.getData().addAll(seriesList); + if (xax != 0) { + xaxis.setUpperBound(xax); } + + chart.getData().addAll(seriesList); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java index 4c949a5..75ea202 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53.view; +import java.math.BigDecimal; import javafx.collections.transformation.FilteredList; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -7,13 +8,18 @@ import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.control.TextField; -import javafx.scene.layout.*; - -import java.math.BigDecimal; -import java.math.RoundingMode; - +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import javafx.util.Subscription; -import no.ntnu.gruppe53.model.*; +import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.model.Purchase; +import no.ntnu.gruppe53.model.Sale; +import no.ntnu.gruppe53.model.SaleCalculator; +import no.ntnu.gruppe53.model.Transaction; +import no.ntnu.gruppe53.model.TransactionArchive; import no.ntnu.gruppe53.service.FormatBigDecimal; import no.ntnu.gruppe53.service.LanguageManager; @@ -26,105 +32,112 @@ /** * Represents a view of all the {@link Purchase} and {@link Sale} * {@link Transaction}s a {@link Player} has committed in a game. + * *

      Extends the {@link BorderPane} for easy layout setup.

      */ public class TransactionArchiveView extends BorderPane { - private final LanguageManager lm = LanguageManager.getInstance(); + private final LanguageManager lm = LanguageManager.getInstance(); + + private ListView historyListView; + private FilteredList filteredTransactionList; + + private TextField searchField; + + /** + * Builds the view components and places them in the center pane. + */ + public TransactionArchiveView() { + this.setCenter(centerPane()); + } + + /** + * Constructs the view of the transaction archive. + * + *

      Builds the necessary components and places them in a {@link VBox}.

      + * + *

      Uses a cell factory to format the string shown

      + * + *

      Assigns red text to purchases, and green text to sales.

      + * + *

      Uses the formatBigDecimal method to convert the BigDecimal to a + * string with 2 decimals.

      + * + *

      Uses a {@link LanguageManager} to dynamically set text based on target language.

      + * + * @return a VBox of the constructed view of the transaction archive + */ + private VBox centerPane() { + VBox vbox = new VBox(10); + vbox.setPadding(new Insets(15, 12, 15, 12)); + vbox.setStyle("-fx-background-color: #00bcf0; "); + + Label titleLabel = new Label(); + titleLabel.textProperty().bind(lm.bindString("historyTitle")); + titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: #303539;"); + + VBox titleBox = new VBox(titleLabel); + titleBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + titleBox.setAlignment(Pos.CENTER); + titleBox.setPadding(new Insets(4)); + titleBox.setMaxWidth(300); + + historyListView = new ListView<>(); + historyListView.setMinHeight(0); + historyListView.setMaxHeight(Double.MAX_VALUE); + + historyListView.setCellFactory(list -> new ListCell<>() { + private Subscription langSubscription; + + @Override + protected void updateItem(Transaction item, boolean empty) { + super.updateItem(item, empty); + + if (langSubscription != null) { + langSubscription.unsubscribe(); + langSubscription = null; + } - private ListView historyListView; - private FilteredList filteredTransactionList; + if (empty || item == null) { + setText(null); + setStyle(""); + } else { + Runnable updateTextAction = () -> { + String typeStr; + BigDecimal price = BigDecimal.ZERO; + BigDecimal profit = BigDecimal.ZERO; + + // Changed to allow for more transaction types in the future + if (item instanceof Purchase) { + typeStr = lm.getString("historyTypePurchase"); + price = item.getShare().getPurchasePrice(); + } else if (item instanceof Sale sale) { + typeStr = lm.getString("historyTypeSale"); + price = item.getShare().getStock().getSalesPrice(); + + if (sale.getCalculator() instanceof SaleCalculator saleCalc) { + profit = saleCalc.getProfitProperty().getValue(); + } + } else { + typeStr = "ERROR"; + } - private Label titleLabel; + BigDecimal quantity = item.getShare().getQuantity(); + BigDecimal gross = item.getCalculator().calculateGross(); + BigDecimal commission = item.getCalculator().calculateCommission(); + BigDecimal tax = item.getCalculator().calculateTax(); + BigDecimal total = item.getCalculator().calculateTotal(); - private TextField searchField; + String symbol = item.getShare().getStock().getSymbol(); - /** - * Builds the view components and places them in the center pane. - */ - public TransactionArchiveView() { - this.setCenter(centerPane()); - } + String template = lm.getString("historyCellFormat"); - /** - * Constructs the view of the transaction archive. - *

      Builds the necessary components and places them in a {@link VBox}.

      - *

      Uses a cell factory to format the string shown

      - *

      Assigns red text to purchases, and green text to sales.

      - *

      Uses the formatBigDecimal method to convert the BigDecimal to a - * string with 2 decimals.

      - *

      Uses a {@link LanguageManager} to dynamically set text based on target language.

      - * - * @return a VBox of the constructed view of the transaction archive - */ - private VBox centerPane() { - VBox vBox = new VBox(10); - vBox.setPadding(new Insets(15, 12, 15, 12)); - vBox.setStyle("-fx-background-color: #00bcf0; "); - - titleLabel = new Label(); - titleLabel.textProperty().bind(lm.bindString("historyTitle")); - titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: #303539;"); - - VBox titleBox = new VBox(titleLabel); - titleBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - titleBox.setAlignment(Pos.CENTER); - titleBox.setPadding(new Insets(4)); - titleBox.setMaxWidth(300); - - historyListView = new ListView<>(); - historyListView.setMinHeight(0); - historyListView.setMaxHeight(Double.MAX_VALUE); - - historyListView.setCellFactory(list -> new ListCell<>() { - private Subscription langSubscription; - - @Override - protected void updateItem(Transaction item, boolean empty) { - super.updateItem(item, empty); - - if (langSubscription != null) { - langSubscription.unsubscribe(); - langSubscription = null; - } - - if (empty || item == null) { - setText(null); - setStyle(""); - } else { - Runnable updateTextAction = () -> { - String typeStr; - BigDecimal price = BigDecimal.ZERO; - BigDecimal profit = BigDecimal.ZERO; - // Changed to allow for more transaction types in the future - if (item instanceof Purchase) { - typeStr = lm.getString("historyTypePurchase"); - price = item.getShare().getPurchasePrice(); - } else if (item instanceof Sale sale) { - typeStr = lm.getString("historyTypeSale"); - price = item.getShare().getStock().getSalesPrice(); - if (sale.getCalculator() instanceof SaleCalculator saleCalc) { - profit = saleCalc.getProfitProperty().getValue(); - } - } else { - typeStr = "ERROR"; - } - - BigDecimal quantity = item.getShare().getQuantity(); - BigDecimal gross = item.getCalculator().calculateGross(); - BigDecimal commission = item.getCalculator().calculateCommission(); - BigDecimal tax = item.getCalculator().calculateTax(); - BigDecimal total = item.getCalculator().calculateTotal(); - - String symbol = item.getShare().getStock().getSymbol(); - - String template = lm.getString("historyCellFormat"); - - setText(String.format(template, + setText(String.format(template, item.getWeek(), typeStr, symbol, @@ -135,81 +148,88 @@ protected void updateItem(Transaction item, boolean empty) { FormatBigDecimal.formatNumber(tax), FormatBigDecimal.formatNumber(total), FormatBigDecimal.formatNumber(profit) - )); - }; - - langSubscription = lm.getBundleProperty().subscribe(bundle -> updateTextAction.run()); + )); + }; - updateTextAction.run(); + langSubscription = lm.getBundleProperty() + .subscribe(bundle -> updateTextAction.run()); - if (item instanceof Purchase) { - setStyle("-fx-text-fill: #c0392b; -fx-font-weight: bold;"); - } else if (item instanceof Sale) { - setStyle("-fx-text-fill: #27ae60; -fx-font-weight: bold;"); - } - } - } - }); - - HBox searchBox = new HBox(); - searchField = new TextField(); - searchField.promptTextProperty().bind(lm.bindString("search")); - searchBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); + updateTextAction.run(); - searchBox.setPadding(new Insets(8, 14, 8, 14)); - searchBox.setAlignment(Pos.CENTER_LEFT); - searchBox.setMaxWidth(Region.USE_PREF_SIZE); - searchBox.getChildren().addAll(searchField); - - VBox historyListBox = new VBox(historyListView); - historyListBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" + if (item instanceof Purchase) { + setStyle("-fx-text-fill: #c0392b; -fx-font-weight: bold;"); + } else if (item instanceof Sale) { + setStyle("-fx-text-fill: #27ae60; -fx-font-weight: bold;"); + } + } + } + }); + + HBox searchBox = new HBox(); + searchField = new TextField(); + searchField.promptTextProperty().bind(lm.bindString("search")); + searchBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + searchBox.setPadding(new Insets(8, 14, 8, 14)); + searchBox.setAlignment(Pos.CENTER_LEFT); + searchBox.setMaxWidth(Region.USE_PREF_SIZE); + searchBox.getChildren().addAll(searchField); + + VBox historyListBox = new VBox(historyListView); + historyListBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + historyListBox.setPadding(new Insets(4)); + historyListBox.setMinHeight(0); + historyListBox.setMaxHeight(Double.MAX_VALUE); + + VBox.setVgrow(historyListView, Priority.ALWAYS); + VBox.setVgrow(historyListBox, Priority.ALWAYS); + + vbox.getChildren().addAll(titleBox, searchBox, historyListBox); + return vbox; + } + + /** + * Sets the player to be observed. + * + *

      And puts the {@link TransactionArchive} into a {@link FilteredList} for searching.

      + * + * @param player the player to be observed + */ + public void setPlayer(Player player) { + if (player != null && player.getTransactionArchive().getObservableTransactions() != null) { + + filteredTransactionList = new FilteredList<>( + player.getTransactionArchive().getObservableTransactions()); + + historyListView.setItems(filteredTransactionList); + + searchField.textProperty().addListener(( + observable, oldText, newText) -> { + String search = newText == null ? "" : newText.toLowerCase(); + + filteredTransactionList.setPredicate(transaction -> search.isBlank() + || + transaction.getShare().getStock() + .getSymbol().toLowerCase().contains(search) + || + transaction.getShare().getStock() + .getCompany().toLowerCase().contains(search) ); + }); - historyListBox.setPadding(new Insets(4)); - historyListBox.setMinHeight(0); - historyListBox.setMaxHeight(Double.MAX_VALUE); - - VBox.setVgrow(historyListView, Priority.ALWAYS); - VBox.setVgrow(historyListBox, Priority.ALWAYS); - - vBox.getChildren().addAll(titleBox, searchBox, historyListBox); - return vBox; - } - - /** - * Sets the player to be observed. - *

      And puts the {@link TransactionArchive} into a {@link FilteredList} for searching.

      - * - * @param player the player to be observed - */ - public void setPlayer(Player player) { - if (player != null && player.getTransactionArchive().getObservableTransactions() != null) { - - - filteredTransactionList = new FilteredList<>(player.getTransactionArchive().getObservableTransactions()); - - historyListView.setItems(filteredTransactionList); - - searchField.textProperty().addListener((observable, oldText, newText) -> { - String search = newText == null ? "" : newText.toLowerCase(); - - filteredTransactionList.setPredicate( transaction -> search.isBlank() - || transaction.getShare().getStock().getSymbol().toLowerCase().contains(search) - || transaction.getShare().getStock().getCompany().toLowerCase().contains(search) - ); - }); - - } else { - historyListView.setItems(null); - } + } else { + historyListView.setItems(null); } + } }