Skip to content

implimented events with stocks and mail app #125

Merged
merged 1 commit into from
May 18, 2026
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.ntnu.idi.idatt2003.gruppe42.Controller.AppControllers;

import edu.ntnu.idi.idatt2003.gruppe42.Controller.TimeAndWeatherController;
import edu.ntnu.idi.idatt2003.gruppe42.Model.Day;
import edu.ntnu.idi.idatt2003.gruppe42.Model.Message;
import javafx.beans.binding.Bindings;
Expand All @@ -14,22 +15,27 @@
public class MailController implements AppController {
private final ObservableList<Message> messages = FXCollections.observableArrayList();
private final IntegerBinding unreadCount;
private final TimeAndWeatherController timeAndWeatherController;
private int week;
private String day;
private int time;

/**
* Constructs the MailController and sets up the unread count binding.
*/
public MailController() {
public MailController(TimeAndWeatherController timeAndWeatherController) {
this.timeAndWeatherController = timeAndWeatherController;
unreadCount = Bindings.createIntegerBinding(() ->
(int) messages.stream().filter(m -> !m.isSeen()).count(),
messages
);

// Ensure unreadCount updates when a message's seen property changes
messages.addListener((ListChangeListener<Message>) c -> {
while (c.next()) {
if (c.wasAdded()) {
for (Message m : c.getAddedSubList()) {
m.seenProperty().addListener((obs, oldVal, newVal) -> unreadCount.invalidate());
messages.addListener((ListChangeListener<Message>) change -> {
while (change.next()) {
if (change.wasAdded()) {
for (Message message : change.getAddedSubList()) {
message.seenProperty().addListener((obs, oldVal, newVal) -> unreadCount.invalidate());
}
}
}
Expand All @@ -51,22 +57,25 @@ public IntegerBinding unreadCountProperty() {
return unreadCount;
}


public void createMessage(String author, String title, String message) {
updateDateTime();
messages.add(0, new Message(author, title, message, week, day, time));
}

/**
* Creates and adds a new message to the inbox.
*
* @param author the sender
* @param title the subject
* @param message the content
* @param week the week
* @param day the day
* @param time the time
* Clears all messages from the inbox.
*/
public void createMessage(String author, String title, String message, int week, Day day, int time) {
messages.add(new Message(author, title, message, week, day, time));
public void clearMessages() {
messages.clear();
}
public void updateDateTime() {
week = timeAndWeatherController.weekIndexProperty().get();
day = timeAndWeatherController.getDayOfWeekString();
time = timeAndWeatherController.hourProperty().get();
}

@Override
public void nextTick() {
// No specific tick logic currently needed for mail
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,39 @@ protected void updateItem(final Stock stock, final boolean empty) {
/** Sets weekend status. */
public void setWeekend(final boolean weekend) {
this.isWeekend = weekend;
Platform.runLater(this::updateTradeUI);
}

/** Updates the Trade button and spinner state. */
private void updateTradeUI() {
Button tradeButton = stockApp.getConfirmButton();
if (weekend) {
var spinner = stockApp.getQuantitySpinner();

if (isWeekend) {
tradeButton.setDisable(true);
tradeButton.getStyleClass().add("trade-button-weekend");
tradeButton.setOpacity(0.45);
stockApp.getQuantitySpinner().setDisable(true);
spinner.setDisable(true);
return;
}

spinner.setDisable(false);
tradeButton.getStyleClass().remove("trade-button-weekend");

if (currentStock != null) {
boolean isBankrupt = currentStock.getSalesPrice().compareTo(BigDecimal.ZERO) <= 0;
boolean isBuying = spinner.getValue() > 0;

if (isBankrupt && isBuying) {
tradeButton.setDisable(true);
tradeButton.setOpacity(0.5);
} else {
tradeButton.setDisable(false);
tradeButton.setOpacity(1.0);
}
} else {
tradeButton.getStyleClass().remove("trade-button-weekend");
tradeButton.setDisable(false);
tradeButton.setOpacity(1.0);
stockApp.getQuantitySpinner().setDisable(false);
}
}

Expand Down Expand Up @@ -189,6 +213,11 @@ private void handleTransaction() {
if (value == 0) {
return;
}

if (value > 0 && currentStock.getSalesPrice().compareTo(BigDecimal.ZERO) <= 0) {
return; // Buying bankrupt stock disabled
}

BigDecimal quantity = new BigDecimal(Math.abs(value));
if (value > 0) {
try{
Expand Down Expand Up @@ -237,12 +266,13 @@ private void handleSell(final Stock stock, final BigDecimal quantity) {
/** Updates receipt view. */
private void updateReceipt() {
if (currentStock != null) {
Platform.runLater(() ->
Platform.runLater(() -> {
stockApp.getReceipt().update(
currentStock,
new BigDecimal(stockApp.getQuantitySpinner().getValue())
)
);
);
updateTradeUI();
});
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ public PopupsController(List<Popup> popups) {
private void initPopup(Popup popup) {
makeDraggable(popup);
popup.getCloseButton().setOnAction(event -> hide(popup.getType()));

// Keep in bounds when parent resizes
Pane root = popup.getRoot();
root.parentProperty().addListener((obs, oldP, newP) -> {
if (newP instanceof Pane parent) {
parent.widthProperty().addListener((o, ov, nv) -> keepInBounds(popup));
parent.heightProperty().addListener((o, ov, nv) -> keepInBounds(popup));
}
});
}

/** Shows a popup. */
Expand All @@ -34,7 +43,10 @@ public boolean show(App type) {
popups.stream()
.filter(popup -> popup.getType().equals(type))
.findFirst()
.ifPresent(Popup::show);
.ifPresent(popup -> {
popup.show();
keepInBounds(popup);
});
audioManager.playSFX(Audio.OPEN_APP);
return true;
}
Expand Down Expand Up @@ -62,6 +74,9 @@ private void makeDraggable(Popup popup) {
double[] offset = new double[2];

popup.getHeader().setOnMousePressed(event -> {
root.layoutXProperty().unbind();
root.layoutYProperty().unbind();

if (root.getParent() instanceof Pane parent) {
Point2D local = parent.sceneToLocal(event.getSceneX(), event.getSceneY());
offset[0] = local.getX() - root.getLayoutX();
Expand All @@ -75,15 +90,37 @@ private void makeDraggable(Popup popup) {
Point2D local = parent.sceneToLocal(event.getSceneX(), event.getSceneY());
double newX = local.getX() - offset[0];
double newY = local.getY() - offset[1];
double maxX = parent.getWidth() - root.getWidth();
double maxY = parent.getHeight() - root.getHeight();

double width = root.getWidth() > 0 ? root.getWidth() : root.prefWidth(-1);
double height = root.getHeight() > 0 ? root.getHeight() : root.prefHeight(-1);

double maxX = Math.max(0, parent.getWidth() - width);
double maxY = Math.max(0, parent.getHeight() - height);

root.setLayoutX(Math.max(0, Math.min(newX, maxX)));
root.setLayoutY(Math.max(0, Math.min(newY, maxY)));
}
event.consume();
});
}

private void keepInBounds(Popup popup) {
Pane root = popup.getRoot();
if (root.layoutXProperty().isBound()) {
return;
}
if (root.getParent() instanceof Pane parent) {
double width = root.getWidth() > 0 ? root.getWidth() : root.prefWidth(-1);
double height = root.getHeight() > 0 ? root.getHeight() : root.prefHeight(-1);

double maxX = Math.max(0, parent.getWidth() - width);
double maxY = Math.max(0, parent.getHeight() - height);

root.setLayoutX(Math.max(0, Math.min(root.getLayoutX(), maxX)));
root.setLayoutY(Math.max(0, Math.min(root.getLayoutY(), maxY)));
}
}

/** Adds popups. */
public void addPopups(List<Popup> popups) {
popups.forEach(this::addPopup);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,58 +1,118 @@
package edu.ntnu.idi.idatt2003.gruppe42.Controller;

import edu.ntnu.idi.idatt2003.gruppe42.Controller.AppControllers.MailController;
import edu.ntnu.idi.idatt2003.gruppe42.Model.EventBus;
import edu.ntnu.idi.idatt2003.gruppe42.Model.Stock;

import edu.ntnu.idi.idatt2003.gruppe42.Model.StockEvent;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class StockController {
private Stock stock;
private int timeframe;

private static final int STEEP_THRESHOLD = 46;
private static final int EVENT_MAGNITUDE = 90;
private static final double NOISE_RANGE = 8.0;
private static final int MIN_TREND_FRAMES = 10;

private final Stock stock;

private int trendTimeframe;
private int trendFramesRemaining;
private int trend;
private int delay;
private int time;
private Random random;
private double timeMultiplier;
private BigDecimal price;
private BigDecimal startPrice;
private BigDecimal offset;
private BigDecimal priceChange;

private BigDecimal pendingShock;

public StockController(Stock stock) {
public StockController(final Stock stock) {
this.stock = stock;
time = 0;
random = new Random();
timeMultiplier = (random.nextDouble() + 0.1) / 20;
startPrice = stock.getSalesPrice();

price = startPrice;
priceChange = new BigDecimal(random.nextInt(startPrice.intValue()/2));
this.time = 0;
this.delay = 0;
this.random = new Random();
this.startPrice = stock.getSalesPrice();
this.price = startPrice;
nextTrend();
}

public BigDecimal updatePrice() {
BigDecimal marketFluctuation = BigDecimal.valueOf(Math.sin(timeMultiplier * time))
.multiply(priceChange)
.add(startPrice);
BigDecimal noise = BigDecimal.valueOf((random.nextDouble() * 2 - 1) * NOISE_RANGE);

if (isDelay()) {
delay--;
if (delay == 0 && pendingShock != null) {
price = price.add(pendingShock);
pendingShock = null;
}

if (price.compareTo(BigDecimal.ZERO) > 0) {
price = price.add(noise);
}

if (price.compareTo(BigDecimal.ZERO) < 0) {
price = BigDecimal.ZERO;
}
return price.setScale(2, RoundingMode.HALF_UP);
}

if (price.compareTo(BigDecimal.ZERO) > 0 || (isTrajectorySteep() && trend > 0)) {
BigDecimal trendStep = BigDecimal.valueOf(trend)
.divide(BigDecimal.valueOf(trendTimeframe), 6, RoundingMode.HALF_UP);

double speculatorFluctuation = random.nextDouble(2) - 1;
offset = BigDecimal.valueOf(speculatorFluctuation*30);
price = price.add(trendStep).add(noise);
}

time += 1;
price = marketFluctuation;
if (price.compareTo(BigDecimal.ZERO) < 0) {
price = BigDecimal.ZERO;
}

return price.add(offset);
trendFramesRemaining--;
time++;

if (trendFramesRemaining <= 0) {
nextTrend();
}

return price.setScale(2, RoundingMode.HALF_UP);
}

public void handleEvent() {
private void nextTrend() {
trend = random.nextInt(101) - 50;
trendTimeframe = random.nextInt(120) + MIN_TREND_FRAMES;
trendFramesRemaining = trendTimeframe;

if (isTrajectorySteep()) {
prepareEvent();
delay = 20;
} else {
delay = 0;
pendingShock = null;
}
}

private void prepareEvent() {
int direction = trend > 0 ? 1 : -1;
double scaledMagnitude = EVENT_MAGNITUDE * ((double) Math.abs(trend) / 50.0);
pendingShock = BigDecimal.valueOf(direction * scaledMagnitude);
EventBus.get()
.publish(new StockEvent(stock.getCompany(), trend));
}

public boolean isTrajectorySteep() {
return true;
private boolean isTrajectorySteep() {
return Math.abs(trend) > STEEP_THRESHOLD;
}

public boolean isDelay() {
private boolean isDelay() {
return delay > 0;
}
}

public BigDecimal getPrice() {
return price;
}

}
Loading