added basic gui functionality for generating loading map and selecting start and end vities

This commit is contained in:
Ksan 2025-07-27 14:46:33 +02:00
parent 46b225430d
commit 38fa1c69e0
13 changed files with 560 additions and 52 deletions

View File

@ -0,0 +1,4 @@
package dev.ksan.travelpathoptimizer;
public class Main {
}

View File

@ -8,17 +8,26 @@ public class TransportDataGenerator {
private static final int SIZE = 10;
private int n;
private int m;
private static final int DEPARTURES_PER_STATION = 1;
private static final int DEPARTURES_DEFAULT = 3;
private static int DEPARTURES_PER_STATION = DEPARTURES_DEFAULT;
private static final Random random = new Random();
public static void main(String[] args) {
TransportDataGenerator generator = new TransportDataGenerator(2, 2);
public static void generateNewMap(int n, int m) {
DEPARTURES_PER_STATION= DEPARTURES_DEFAULT;
TransportDataGenerator generator = new TransportDataGenerator(n, m);
TransportData data = generator.generateData();
generator.saveToJson(data, "transport_data.json");
System.out.println("Podaci su generisani i sacuvani kao transport_data.json");
}
public static void generateNewMap(int n, int m, int departures) {
TransportDataGenerator generator = new TransportDataGenerator(n, m);
DEPARTURES_PER_STATION = departures;
TransportData data = generator.generateData();
generator.saveToJson(data, "transport_data.json");
System.out.println("Podaci su generisani i sacuvani kao transport_data.json");
}
TransportDataGenerator() {
this.n = SIZE;
this.m = SIZE;

View File

@ -1,23 +1,34 @@
package dev.ksan.travelpathoptimizer.app;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.io.IOException;
public class TravelPathOptimizerApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(TravelPathOptimizerApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader =
new FXMLLoader(TravelPathOptimizerApplication.class.getResource("main.fxml"));
String css = this.getClass().getResource("application.css").toExternalForm();
Scene scene = new Scene(fxmlLoader.load());
Image image = new Image(getClass().getResourceAsStream("/images/img1.jpg"));
scene.getStylesheets().add(css);
stage.getIcons().add(image);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
public static void main(String[] args) {
launch();
}
}

View File

@ -0,0 +1,278 @@
package dev.ksan.travelpathoptimizer.controller;
import dev.ksan.travelpathoptimizer.app.TransportDataGenerator;
import dev.ksan.travelpathoptimizer.model.City;
import dev.ksan.travelpathoptimizer.model.Departure;
import dev.ksan.travelpathoptimizer.model.Location;
import dev.ksan.travelpathoptimizer.service.CityManager;
import dev.ksan.travelpathoptimizer.util.JsonParser;
import java.io.File;
import java.util.List;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
public class MainController {
@FXML private GridPane map;
@FXML private Label welcomeText;
@FXML private Text selectedFileText;
private City startCity = null;
private City endCity = null;
private boolean selectingStart = false;
private boolean selectingEnd = false;
@FXML private TextField startCityText;
@FXML private TextField endCityText;
@FXML private Button startCityButton;
@FXML private Button endCityButton;
City[][] cities;
private CityManager cityManager;
private File selectedFile;
@FXML private Button openFileButton;
@FXML private TextField nTextField;
@FXML private TextField mTextField;
@FXML private TextField departureTextField;
@FXML private VBox randomSideBar;
@FXML private VBox mapSideBar;
@FXML
protected void onHelloButtonClick() {
welcomeText.setText("Welcome to JavaFX Application!");
}
@FXML
void showRandomSideBar() {
boolean visible = randomSideBar.isVisible();
randomSideBar.setVisible(!visible);
randomSideBar.setManaged(!visible);
}
@FXML
void showMapSideBar() {
boolean visible = mapSideBar.isVisible();
mapSideBar.setVisible(!visible);
mapSideBar.setManaged(!visible);
}
private void updateMap() {
map.getChildren().clear();
map.getRowConstraints().clear();
map.getColumnConstraints().clear();
int rows = cities.length;
int cols = cities[0].length;
// Make each row/column take equal space and grow
for (int i = 0; i < rows; i++) {
RowConstraints rc = new RowConstraints();
rc.setPercentHeight(100.0 / rows);
rc.setVgrow(Priority.ALWAYS);
map.getRowConstraints().add(rc);
}
for (int j = 0; j < cols; j++) {
ColumnConstraints cc = new ColumnConstraints();
cc.setPercentWidth(100.0 / cols);
cc.setHgrow(Priority.ALWAYS);
map.getColumnConstraints().add(cc);
}
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
final int currentRow = row;
final int currentCol = col;
StackPane cell = new StackPane();
cell.setPrefSize(60, 60);
cell.setStyle("-fx-border-color: black; -fx-background-color: white;");
City city = cities[currentRow][currentCol];
Label label = new Label(city.getName());
label.setStyle("-fx-font-size: 10px;");
label.setWrapText(true);
final StackPane thisCell = cell;
thisCell.getChildren().add(label);
cell.setOnMouseClicked(
e -> {
if (selectingStart) {
startCity = cities[currentRow][currentCol];
startCityText.setText(startCity.getName());
selectingStart = false;
updateMap(); // redraw grid with updated startCity style
} else if (selectingEnd) {
endCity = cities[currentRow][currentCol];
endCityText.setText(endCity.getName());
selectingEnd = false;
updateMap(); // redraw grid with updated endCity style
}
});
thisCell.setStyle("-fx-border-color: black; -fx-background-color: white;");
Location loc = cities[currentRow][currentCol].getLocation();
if (startCity != null) {
Location startLoc = startCity.getLocation();
if (loc.getX() == startLoc.getX() && loc.getY() == startLoc.getY()) {
thisCell.setStyle(
"-fx-border-color: green; -fx-border-width: 3; -fx-background-color: white;");
}
}
if (endCity != null) {
Location endLoc = endCity.getLocation();
if (loc.getX() == endLoc.getX() && loc.getY() == endLoc.getY()) {
thisCell.setStyle(
"-fx-border-color: red; -fx-border-width: 3; -fx-background-color: white;");
}
}
map.add(cell, col, row);
}
}
}
@FXML
private void selectStart() {
this.selectingEnd = false;
this.selectingStart = true;
updateMap();
}
@FXML
private void selectEnd() {
this.selectingStart = false;
this.selectingEnd = true;
updateMap();
}
private boolean cityExists(String name) {
for (int row = 0; row < cities.length; row++) {
for (int col = 0; col < cities[0].length; col++) {
City city = cities[row][col];
if (city != null && city.getName().equalsIgnoreCase(name.trim())) {
return true;
}
}
}
return false;
}
@FXML
public void initialize() {
endCityText
.textProperty()
.addListener(
(obs, oldText, newText) -> {
if (newText.isBlank()) {
endCity = null;
endCityText.setStyle("-fx-text-fill: black;");
} else {
City match = cityManager.getCityByName(newText.trim());
if (match != null) {
endCity = match;
endCityText.setStyle("-fx-text-fill: green;");
} else {
endCity = null;
endCityText.setStyle("-fx-text-fill: red;");
}
}
updateMap(); // Re-draw with highlight if matched
});
startCityText
.textProperty()
.addListener(
(obs, oldText, newText) -> {
if (newText.isBlank()) {
startCity = null;
startCityText.setStyle("-fx-text-fill: black;");
} else {
City match = cityManager.getCityByName(newText.trim());
if (match != null) {
startCity = match;
startCityText.setStyle("-fx-text-fill: green;");
} else {
startCity = null;
startCityText.setStyle("-fx-text-fill: red;");
}
}
updateMap(); // Re-draw with highlight if matched
});
}
@FXML
void generateNewMap() {
if (!nTextField.getText().isEmpty() && !mTextField.getText().isEmpty()) {
System.out.println(nTextField.getText());
System.out.println(mTextField.getText());
System.out.println(departureTextField.getText());
if (!departureTextField.getText().isEmpty()) {
TransportDataGenerator.generateNewMap(
Integer.parseInt(nTextField.getText()),
Integer.parseInt(mTextField.getText()),
Integer.parseInt(departureTextField.getText()));
} else {
TransportDataGenerator.generateNewMap(
Integer.parseInt(nTextField.getText()), Integer.parseInt(mTextField.getText()));
}
}
}
private void getData() {
List<City> cities = JsonParser.parseCities(selectedFile.toString(), "stations");
List<Departure> departures = JsonParser.getDeparturesList("transport_data.json", "departures");
cities = JsonParser.loadDepartures(cities, departures);
City[][] map = JsonParser.loadMap("transport_data.json", "countryMap", cities);
cities = JsonParser.loadDepartures(cities, departures);
for (City city : cities) {
System.out.println(city);
CityManager.addCity(city);
}
this.cities = map;
}
@FXML
void loadMapFromFile() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open File");
fileChooser
.getExtensionFilters()
.addAll(
new FileChooser.ExtensionFilter("Json Files", "*.json"),
new FileChooser.ExtensionFilter("All Files", "*.*"));
Stage stage = (Stage) openFileButton.getScene().getWindow();
selectedFile = fileChooser.showOpenDialog(stage);
if (selectedFile != null) {
selectedFileText.setText(selectedFile.getAbsoluteFile().getName().toString());
}
getData();
updateMap();
}
/*
@FXML
void openLoadedFileDesktop(){
selectedFileText.setOnMouseClicked(event -> {
if(selectedFile != null && selectedFile.exists()){
try{
Desktop.getDesktop().open(selectedFile);
}
catch(Exception e){
e.printStackTrace();
}
}
});
}*/
}

View File

@ -1,14 +0,0 @@
package dev.ksan.travelpathoptimizer.controller;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class TransportPathOptimizerController {
@FXML
private Label welcomeText;
@FXML
protected void onHelloButtonClick() {
welcomeText.setText("Welcome to JavaFX Application!");
}
}

View File

@ -14,7 +14,7 @@ import java.util.PriorityQueue;
public class Graph {
private City[][] matrix;
/*
public static void main(String[] args) {
List<City> cities = JsonParser.parseCities("transport_data.json", "stations");
List<Departure> departures = JsonParser.getDeparturesList("transport_data.json", "departures");
@ -32,6 +32,7 @@ public class Graph {
System.out.println(
cities.getLast().getName() + " = " + result.get(cities.get(3).getLocation()));
}
*/
public Map<Location, Double> calculateShortestPath(City startCity, City endCity, String type) {
int n = matrix.length;

View File

@ -9,14 +9,14 @@ public class City {
private Station busStation;
private Location location;
City(String name, String bus, String train, int row, int col) {
public City(String name, String bus, String train, int row, int col) {
this.name = name;
this.trainStation = new Station(TransportType.TRAIN, train);
this.busStation = new Station(TransportType.BUS, bus);
this.location = new Location(row, col);
}
City(String name, Station bus, Station train, int row, int col) {
public City(String name, Station bus, Station train, int row, int col) {
this.name = name;
this.trainStation = train;
this.busStation = bus;

View File

@ -1,6 +1,7 @@
module dev.ksan.travelpathoptimizer {
requires javafx.controls;
requires javafx.fxml;
requires java.desktop;
opens dev.ksan.travelpathoptimizer to javafx.fxml;

View File

@ -0,0 +1,41 @@
#sideMenu{
-fx-background-color: rgba(118, 140, 239, 0.53);
}
#header{
-fx-background-color: rgb(132, 64, 234);
-fx-border-color: black;
}
#randomSideBar{
-fx-background-color: red;
}
#headerBar{
-fx-alignment: center;
-fx-end-margin: 40;
}
#headerBar Button {
-fx-background-color: linear-gradient(to bottom, #6a1b9a, #8e24aa);
-fx-text-fill: white;
-fx-font-weight: bold;
-fx-font-size: 14px;
-fx-background-radius: 8px;
-fx-border-radius: 8px;
-fx-padding: 8 16;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.3), 4, 0, 0, 2);
}
#mapSideBar{
-fx-background-color: #ba9be3;
}
#headerBar Button:hover {
-fx-background-color: linear-gradient(to bottom, #8e24aa, #9c27b0);
}
#headerBar Button:pressed {
-fx-background-color: linear-gradient(to bottom, #4a148c, #6a1b9a);
-fx-effect: innershadow(gaussian, rgba(0,0,0,0.5), 3, 0, 0, 1);
}
#sectionText{
-fx-font-size: 18;
-fx-underline: true;
}

View File

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Text?>
<VBox fx:id="root" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dev.ksan.travelpathoptimizer.controller.MainController">
<children>
<HBox fx:id="header" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minWidth="-Infinity" prefHeight="12.0" prefWidth="1000.0">
<children>
<HBox maxHeight="1.7976931348623157E308" prefHeight="50.0" prefWidth="180.0" HBox.hgrow="SOMETIMES">
<children>
<ImageView fitHeight="50.0" fitWidth="129.0" pickOnBounds="true" preserveRatio="true" HBox.hgrow="ALWAYS">
<image>
<Image url="@../../../../images/img1.jpg" />
</image>
<HBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</HBox.margin>
</ImageView>
<Text boundsType="LOGICAL_VERTICAL_CENTER" strokeType="OUTSIDE" strokeWidth="0.0" text="Text" textAlignment="CENTER" textOrigin="CENTER" wrappingWidth="80.13000106811523" HBox.hgrow="ALWAYS">
<HBox.margin>
<Insets top="25.0" />
</HBox.margin>
</Text>
</children>
</HBox>
<HBox fx:id="headerBar" minWidth="400.0" prefHeight="121.0" prefWidth="805.0">
<children>
<Button fx:id="headerButton1" onAction="#showMapSideBar" text="Map Options" />
<Label style="-fx-font-size: 20px; -fx-text-fill: gray;" text="|" />
<Button fx:id="headerButton2" text="Button" />
<Label style="-fx-font-size: 20px; -fx-text-fill: gray;" text="|" />
<Button fx:id="headerButton3" text="Button" />
</children>
</HBox>
</children></HBox>
<HBox fx:id="menuContainer" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="578.0" prefWidth="1000.0" VBox.vgrow="ALWAYS">
<children>
<VBox fx:id="mapSideBar" managed="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="354.0" prefWidth="180.0" visible="false">
<children>
<Text fx:id="sectionText" strokeType="OUTSIDE" strokeWidth="0.0" text="Map Generator" textAlignment="CENTER" wrappingWidth="179.2100067138672" />
<Button mnemonicParsing="false" onAction="#generateNewMap" prefHeight="26.0" prefWidth="194.0" text="Generate Map">
<VBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</VBox.margin>
</Button>
<HBox prefHeight="39.0" prefWidth="180.0">
<children>
<TextField fx:id="nTextField" alignment="CENTER" promptText="n">
<HBox.margin>
<Insets left="10.0" right="5.0" />
</HBox.margin>
</TextField>
<TextField fx:id="mTextField" alignment="CENTER" promptText="m">
<HBox.margin>
<Insets left="5.0" right="10.0" />
</HBox.margin>
</TextField>
</children>
</HBox>
<HBox prefHeight="39.0" prefWidth="180.0">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Departures:">
<HBox.margin>
<Insets left="10.0" top="3.0" />
</HBox.margin>
</Text>
<TextField fx:id="departureTextField" alignment="CENTER" promptText="3">
<HBox.margin>
<Insets left="15.0" right="10.0" />
</HBox.margin>
</TextField>
</children>
</HBox>
<VBox prefHeight="80.0" prefWidth="180.0">
<children>
<Button fx:id="openFileButton" maxWidth="1.7976931348623157E308" minHeight="0.0" minWidth="0.0" mnemonicParsing="false" onAction="#loadMapFromFile" text="Load Map" VBox.vgrow="ALWAYS">
<VBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</VBox.margin>
</Button>
<Text fx:id="selectedFileText" strokeType="OUTSIDE" strokeWidth="0.0" text="no file" textAlignment="CENTER" textOrigin="CENTER" wrappingWidth="147.13000106811523" VBox.vgrow="ALWAYS">
<VBox.margin>
<Insets left="15.0" right="15.0" />
</VBox.margin>
</Text>
</children>
</VBox>
</children>
</VBox>
<VBox fx:id="calculatorSideBar" managed="true" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="354.0" prefWidth="180.0" visible="true">
<children>
<Text fx:id="sectionText1" strokeType="OUTSIDE" strokeWidth="0.0" text="Path Calculator" textAlignment="CENTER" wrappingWidth="179.2100067138672" />
<HBox prefHeight="39.0" prefWidth="180.0">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Start:">
<HBox.margin>
<Insets left="10.0" top="3.0" />
</HBox.margin>
</Text>
<TextField fx:id="startCityText" alignment="CENTER" promptText="G_0_0">
<HBox.margin>
<Insets left="15.0" right="10.0" />
</HBox.margin>
</TextField>
</children>
</HBox>
<Button fx:id="startCityButton" mnemonicParsing="false" onAction="#selectStart" prefHeight="26.0" prefWidth="194.0" text="Click Start City">
<VBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="-5.0" />
</VBox.margin>
</Button>
<HBox prefHeight="39.0" prefWidth="180.0">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="End:">
<HBox.margin>
<Insets left="10.0" right="6.0" top="3.0" />
</HBox.margin>
</Text>
<TextField fx:id="endCityText" alignment="CENTER" promptText="G_1_1">
<HBox.margin>
<Insets left="15.0" right="10.0" />
</HBox.margin>
</TextField>
</children>
</HBox>
<Button fx:id="endCityButton" mnemonicParsing="false" onAction="#selectEnd" prefHeight="26.0" prefWidth="194.0" text="Click End City">
<VBox.margin>
<Insets left="10.0" right="10.0" top="-5.0" />
</VBox.margin>
</Button>
<VBox prefHeight="80.0" prefWidth="180.0">
<children>
<Button fx:id="openFileButton1" maxWidth="1.7976931348623157E308" minHeight="0.0" minWidth="0.0" mnemonicParsing="false" text="Load Map" VBox.vgrow="ALWAYS">
<VBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</VBox.margin>
</Button>
<Text fx:id="selectedFileText1" strokeType="OUTSIDE" strokeWidth="0.0" text="no file" textAlignment="CENTER" textOrigin="CENTER" wrappingWidth="147.13000106811523" VBox.vgrow="ALWAYS">
<VBox.margin>
<Insets left="15.0" right="15.0" />
</VBox.margin>
</Text>
</children>
</VBox>
</children>
</VBox>
<GridPane fx:id="map" alignment="CENTER" gridLinesVisible="true" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" HBox.hgrow="ALWAYS">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</GridPane>
<VBox fx:id="randomSideBar" managed="false" maxHeight="1.7976931348623157E308" prefHeight="354.0" prefWidth="150.0" visible="false" />
</children>
<VBox.margin>
<Insets />
</VBox.margin>
</HBox>
<HBox prefHeight="100.0" prefWidth="200.0" />
</children>
</VBox>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="dev.ksan.travelpathoptimizer.app.Test"
prefHeight="400.0" prefWidth="600.0">
</AnchorPane>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml"
fx:controller="dev.ksan.travelpathoptimizer.controller.TransportPathOptimizerController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
</padding>
<Label fx:id="welcomeText"/>
<Button text="Hello!" onAction="#onHelloButtonClick"/>
</VBox>

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB