Compare commits
21 Commits
25f13105f2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
f6d205cd29
|
|||
|
573498ddb4
|
|||
|
857802227d
|
|||
|
34a71d5ef7
|
|||
|
1be8ca6bf4
|
|||
|
8dd6533d47
|
|||
|
19141831c8
|
|||
|
f098922149
|
|||
|
c10ba4b2fc
|
|||
|
d4705dc11e
|
|||
|
0fb5fd0654
|
|||
|
b12cf9f6d2
|
|||
|
528254c9bc
|
|||
|
63864e4e25
|
|||
|
f8b38bae8f
|
|||
|
31302dd0d5
|
|||
|
81abdd064d
|
|||
|
593840bd51
|
|||
|
a80fe72611
|
|||
|
1e55218f47
|
|||
|
ff2c9489bb
|
29
hw2/.gitignore
vendored
Normal file
29
hw2/.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
### IntelliJ IDEA ###
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
8
hw2/.idea/.gitignore
generated
vendored
Normal file
8
hw2/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
6
hw2/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
hw2/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
6
hw2/.idea/misc.xml
generated
Normal file
6
hw2/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-22" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
8
hw2/.idea/modules.xml
generated
Normal file
8
hw2/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/hw2.iml" filepath="$PROJECT_DIR$/hw2.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
hw2/.idea/vcs.xml
generated
Normal file
6
hw2/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
2
hw2/Users
Normal file
2
hw2/Users
Normal file
@@ -0,0 +1,2 @@
|
||||
hi,PUBLIC,R3JlZXRlcg==,aGVsbG8=
|
||||
admin,ADMIN,YWRtaW4=,YWRtaW4=
|
||||
1
hw2/googledoc.txt
Normal file
1
hw2/googledoc.txt
Normal file
@@ -0,0 +1 @@
|
||||
https://docs.google.com/document/d/1JvaiUiMjBGCoZ_znD07hcGHm9pu1VZYskcvsJmrsNv4/edit?usp=sharing
|
||||
11
hw2/hw2.iml
Normal file
11
hw2/hw2.iml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
417
hw2/src/ReservationSystem.java
Normal file
417
hw2/src/ReservationSystem.java
Normal file
@@ -0,0 +1,417 @@
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Main entrypoint for the reservation system.
|
||||
* <p>
|
||||
* The reservation system allows users to make, cancel, and view reservations.
|
||||
* The system also allows administrators to view a manifest list of reservations.
|
||||
* <p>
|
||||
* The system loads reservations and users from files specified as command line arguments.
|
||||
* If the files do not exist, the system creates them and creates an admin user with the username "admin" and password "admin".
|
||||
* <p>
|
||||
* The system saves reservations and users to the files when the admin exits the system.
|
||||
* <p>
|
||||
*/
|
||||
public class ReservationSystem {
|
||||
private static final Scanner stdin = new Scanner(System.in);
|
||||
private static final String ADMIN_USERNAME = "admin";
|
||||
private static final String ADMIN_PASSWORD = "admin";
|
||||
private static Map<String, User> users = new HashMap<>();
|
||||
private static ArrayList<String> seatReservations = new ArrayList<>(Stream.generate(() -> (String) null).limit(Seat.TOTAL_SEATS).toList());
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 2) {
|
||||
System.out.println("Usage: java ReservationSystem <reservationFilename> <userFilename>");
|
||||
return;
|
||||
}
|
||||
loadFiles(args[0], args[1]);
|
||||
System.out.println(users.get(ADMIN_USERNAME).toCsvString());
|
||||
userSelectionMenu();
|
||||
saveFiles(args[0], args[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* User selection menu: [A]dministrator, [P]ublic User
|
||||
*/
|
||||
private static void userSelectionMenu() {
|
||||
String input = "";
|
||||
while (!input.equals("A")) {
|
||||
System.out.println(String.join(";\t ",
|
||||
"User:\t [A]dministrator",
|
||||
"[P]ublic User"
|
||||
));
|
||||
input = stdin.nextLine().toUpperCase();
|
||||
switch (input) {
|
||||
case "A" -> adminMenu(authenticateAdmin());
|
||||
case "P" -> publicUserMenu(authenticatePublicUser());
|
||||
default -> System.out.println("Invalid input");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate admin user with username and password.
|
||||
*
|
||||
* @return authenticated user
|
||||
*/
|
||||
private static User authenticateAdmin() {
|
||||
System.out.print("Enter admin user id: ");
|
||||
String username = stdin.nextLine();
|
||||
System.out.print("Enter admin password: ");
|
||||
String password = stdin.nextLine();
|
||||
|
||||
final User user = users.get(username);
|
||||
if (user == null || !user.isAdmin() || !user.checkPassword(password)) {
|
||||
System.out.println("Invalid user id or password");
|
||||
return authenticateAdmin();
|
||||
}
|
||||
return users.get(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate or create public user with id, name, and password.
|
||||
*
|
||||
* @return authenticated or created user
|
||||
*/
|
||||
private static User authenticatePublicUser() {
|
||||
System.out.println(String.join(";\t ",
|
||||
"[S]ign up",
|
||||
"[L]og in"
|
||||
));
|
||||
String input = stdin.nextLine().toUpperCase();
|
||||
switch (input) {
|
||||
case "S" -> {
|
||||
System.out.print("Enter user id: ");
|
||||
// can't have commas in a csv because I'm too lazy to do escaping
|
||||
String id = stdin.nextLine().replace(",", "");
|
||||
System.out.print("Enter name: ");
|
||||
String name = stdin.nextLine();
|
||||
System.out.print("Enter password: ");
|
||||
String password = stdin.nextLine();
|
||||
User user = new User(id, User.Type.PUBLIC, name, password);
|
||||
users.put(id, user);
|
||||
return user;
|
||||
}
|
||||
case "L" -> {
|
||||
System.out.print("Enter user id: ");
|
||||
String id = stdin.nextLine();
|
||||
System.out.print("Enter user password: ");
|
||||
String password = stdin.nextLine();
|
||||
|
||||
final User user = users.get(id);
|
||||
if (user == null || user.isAdmin() || !user.checkPassword(password)) {
|
||||
System.out.println("Invalid user id or password");
|
||||
return authenticatePublicUser();
|
||||
}
|
||||
return users.get(id);
|
||||
}
|
||||
default -> {
|
||||
System.out.println("Invalid input");
|
||||
return authenticatePublicUser();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin menu: [M]anifest list, E[X]it
|
||||
*
|
||||
* @param user authenticated admin user
|
||||
*/
|
||||
private static void adminMenu(User user) {
|
||||
String input = "";
|
||||
while (true) {
|
||||
System.out.println(String.join(";\t ",
|
||||
"Show [M]anifest list",
|
||||
"E[X]it"
|
||||
));
|
||||
input = stdin.nextLine().toUpperCase();
|
||||
switch (input) {
|
||||
case "M" -> showManifestList();
|
||||
case "X" -> {
|
||||
return;
|
||||
}
|
||||
default -> System.out.println("Invalid input");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public user menu: [A]vailability, [R]eservation, [C]ancel Reservation, [V]iew Reservations, [D]one
|
||||
*
|
||||
* @param user authenticated public user
|
||||
*/
|
||||
private static void publicUserMenu(User user) {
|
||||
String input = "";
|
||||
while (true) {
|
||||
System.out.println(String.join(";\t ",
|
||||
"Check [A]vailability",
|
||||
"Make [R]eservation",
|
||||
"[C]ancel Reservation",
|
||||
"[V]iew Reservations",
|
||||
"[D]one"
|
||||
));
|
||||
input = stdin.nextLine().toUpperCase();
|
||||
switch (input) {
|
||||
case "A" -> checkAvailability();
|
||||
case "R" -> makeReservation(user);
|
||||
case "C" -> cancelReservation(user);
|
||||
case "V" -> viewReservations(user);
|
||||
case "D" -> {
|
||||
return;
|
||||
}
|
||||
default -> System.out.println("Invalid input");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show [M]anifest list: A manifest lists the reserved seats (only) and the corresponding passenger name.
|
||||
*/
|
||||
private static void showManifestList() {
|
||||
System.out.println("First");
|
||||
for (int seat = 0; seat < Seat.FIRST_CLASS_SEATS; seat++) {
|
||||
if (seatReservations.get(seat) != null) printManifestReservation(seat);
|
||||
}
|
||||
System.out.println("\nEconomy Plus");
|
||||
for (int seat = Seat.FIRST_CLASS_SEATS; seat < Seat.FIRST_CLASS_SEATS + Seat.ECONOMY_PLUS_SEATS; seat++) {
|
||||
if (seatReservations.get(seat) != null) printManifestReservation(seat);
|
||||
}
|
||||
System.out.println("\nEconomy");
|
||||
for (int seat = Seat.FIRST_CLASS_SEATS + Seat.ECONOMY_PLUS_SEATS; seat < Seat.TOTAL_SEATS; seat++) {
|
||||
if (seatReservations.get(seat) != null) printManifestReservation(seat);
|
||||
}
|
||||
}
|
||||
|
||||
private static void printManifestReservation(Integer seat) {
|
||||
User user = users.get(seatReservations.get(seat));
|
||||
System.out.printf("%d%c: %s%n", Seat.getSeatRow(seat), Seat.getSeatColumn(seat), user.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check [A]vailability: This option shows a seat availability list which shows the available seats of each class along with the seat price.
|
||||
*/
|
||||
private static void checkAvailability() {
|
||||
System.out.println("Seat Availability\n");
|
||||
System.out.print("First (price: $1000/seat)");
|
||||
for (int seat = 0; seat < Seat.FIRST_CLASS_SEATS; seat++) {
|
||||
if (seat % Seat.ROW_SIZE == 0) {
|
||||
System.out.println();
|
||||
System.out.print(Seat.getSeatRow(seat) + ": ");
|
||||
}
|
||||
if (seatReservations.get(seat) == null) {
|
||||
System.out.print(Seat.getSeatColumn(seat) + ", ");
|
||||
}
|
||||
}
|
||||
System.out.print("\n\nEconomy Plus (price: $500/seat)");
|
||||
for (int seat = Seat.FIRST_CLASS_SEATS; seat < Seat.FIRST_CLASS_SEATS + Seat.ECONOMY_PLUS_SEATS; seat++) {
|
||||
if (seat % Seat.ROW_SIZE == 0) {
|
||||
System.out.println();
|
||||
System.out.print(Seat.getSeatRow(seat) + ": ");
|
||||
}
|
||||
if (seatReservations.get(seat) == null) {
|
||||
System.out.print(Seat.getSeatColumn(seat) + ", ");
|
||||
}
|
||||
}
|
||||
System.out.print("\n\nEconomy (price: $250/seat)");
|
||||
for (int seat = Seat.FIRST_CLASS_SEATS + Seat.ECONOMY_PLUS_SEATS; seat < Seat.TOTAL_SEATS; seat++) {
|
||||
if (seat % Seat.ROW_SIZE == 0) {
|
||||
System.out.println();
|
||||
System.out.print(Seat.getSeatRow(seat) + ": ");
|
||||
}
|
||||
if (seatReservations.get(seat) == null) {
|
||||
System.out.print(Seat.getSeatColumn(seat) + ", ");
|
||||
}
|
||||
}
|
||||
System.out.print("\n\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Make [R]eservation: With this option, the user can make multiple reservations but one seat at a time. The user enters the seat number consisting of a number and a letter (e.g., 1K for a first-class seat). If the seat number is not valid (specifically, not an existing seat number in the plane), the system asks for another one until the user enters a valid one. If the seat is not available, the system prompts an error and asks for another seat. If the seat is available, the system prompts the seat number, the type of service corresponding to this seat, and the price, and asks for the user's confirmation. If the user confirms the reservation, the seat will be reserved. If not, the reservation request will be void. The system asks if the user wants to make another one or not and proceeds with the user's request.
|
||||
*
|
||||
* @param user
|
||||
*/
|
||||
private static void makeReservation(User user) {
|
||||
System.out.print("Enter seat number: ");
|
||||
String seat = stdin.nextLine().toUpperCase();
|
||||
try {
|
||||
int seatNumber = Seat.getSeatNumber(seat);
|
||||
if (seatReservations.get(seatNumber) != null) {
|
||||
System.out.println("Seat is already reserved");
|
||||
makeReservation(user);
|
||||
return;
|
||||
}
|
||||
System.out.printf("Seat: %s\n", seat);
|
||||
System.out.printf("Service: %s, $%d\n", Seat.getServiceClass(seatNumber), Seat.getSeatPrice(seatNumber));
|
||||
System.out.println("Confirm reservation? [Y/N]");
|
||||
String confirm = stdin.nextLine().toUpperCase();
|
||||
while (!confirm.equals("Y") && !confirm.equals("N")) {
|
||||
System.out.println("Invalid input");
|
||||
System.out.println("Confirm reservation? [Y/N]");
|
||||
confirm = stdin.nextLine().toUpperCase();
|
||||
}
|
||||
switch (confirm) {
|
||||
case "Y" -> {
|
||||
seatReservations.set(seatNumber, user.getId());
|
||||
System.out.println("Reservation confirmed");
|
||||
}
|
||||
case "N" -> System.out.println("Reservation cancelled");
|
||||
}
|
||||
System.out.println("Make another reservation? [Y/N]");
|
||||
String another = stdin.nextLine().toUpperCase();
|
||||
while (!another.equals("Y") && !another.equals("N")) {
|
||||
System.out.println("Invalid input");
|
||||
System.out.println("Make another reservation? [Y/N]");
|
||||
another = stdin.nextLine().toUpperCase();
|
||||
}
|
||||
if (another.equals("Y")) {
|
||||
makeReservation(user);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
System.out.println("Invalid seat number");
|
||||
makeReservation(user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [C]ancel Reservation: With this option, the system first shows all the seats the user reserved. The user can cancel multiple seat reservations but one seat at a time. The user is supposed to enter a seat number from the list. If the user enters an unlisted number, the system asks for another one until the user enters one of the listed numbers.
|
||||
*
|
||||
* @param user
|
||||
*/
|
||||
private static void cancelReservation(User user) {
|
||||
System.out.println("Your reservations:");
|
||||
for (int seat = 0; seat < seatReservations.size(); seat++) {
|
||||
if (seatReservations.get(seat) != null && seatReservations.get(seat).equals(user.getId())) {
|
||||
System.out.printf("%d%c, %s, $%d\n", Seat.getSeatRow(seat), Seat.getSeatColumn(seat), Seat.getServiceClass(seat), Seat.getSeatPrice(seat));
|
||||
}
|
||||
}
|
||||
System.out.print("Enter seat number to cancel: ");
|
||||
String seat = stdin.nextLine().toUpperCase();
|
||||
try {
|
||||
int seatNumber = Seat.getSeatNumber(seat);
|
||||
if (seatReservations.get(seatNumber) == null || !seatReservations.get(seatNumber).equals(user.getId())) {
|
||||
System.out.println("Seat is not reserved by you");
|
||||
cancelReservation(user);
|
||||
return;
|
||||
}
|
||||
seatReservations.set(seatNumber, null);
|
||||
System.out.println("Reservation cancelled");
|
||||
System.out.println("Cancel another reservation? [Y/N]");
|
||||
String another = stdin.nextLine().toUpperCase();
|
||||
while (!another.equals("Y") && !another.equals("N")) {
|
||||
System.out.println("Invalid input");
|
||||
System.out.println("Cancel another reservation? [Y/N]");
|
||||
another = stdin.nextLine().toUpperCase();
|
||||
}
|
||||
if (another.equals("Y")) {
|
||||
cancelReservation(user);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
System.out.println("Invalid seat number");
|
||||
cancelReservation(user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [V]iew Reservations: It shows all reservations made by the current transaction. It doesn't show any cancelled reservation. The following format is suggested:
|
||||
* <p>
|
||||
* Name: Bill Smith
|
||||
* Seats: 2I $1000, 17J $500, 42k $250
|
||||
* Total Balance Due: $1750
|
||||
*
|
||||
* @param user
|
||||
*/
|
||||
private static void viewReservations(User user) {
|
||||
System.out.printf("Name: %s\n Seats: ", user.getName());
|
||||
int totalBalanceDue = 0;
|
||||
for (int seat = 0; seat < seatReservations.size(); seat++) {
|
||||
if (seatReservations.get(seat) != null && seatReservations.get(seat).equals(user.getId())) {
|
||||
System.out.printf("%d%c $%d, ", Seat.getSeatRow(seat), Seat.getSeatColumn(seat), Seat.getSeatPrice(seat));
|
||||
totalBalanceDue += Seat.getSeatPrice(seat);
|
||||
}
|
||||
}
|
||||
System.out.printf("\nTotal Balance Due: $%d\n", totalBalanceDue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load reservations and users from files.
|
||||
* If the files do not exist, create them and create an admin user with the username "admin" and password "admin".
|
||||
*
|
||||
* @param reservationFilename reservation file name
|
||||
* @param userFilename user file name
|
||||
*/
|
||||
private static void loadFiles(String reservationFilename, String userFilename) {
|
||||
// check if files exist
|
||||
File reservationFile = new File(reservationFilename);
|
||||
File userFile = new File(userFilename);
|
||||
if (!reservationFile.isFile() || !userFile.isFile()) {
|
||||
users.put(ADMIN_USERNAME, new User(ADMIN_USERNAME, User.Type.ADMIN, ADMIN_USERNAME, ADMIN_PASSWORD));
|
||||
try {
|
||||
reservationFile.createNewFile();
|
||||
userFile.createNewFile();
|
||||
try (PrintWriter writer = new PrintWriter(userFile)) {
|
||||
users.values().forEach(user -> writer.println(user.toCsvString()));
|
||||
}
|
||||
System.out.println(reservationFilename + " and " + userFilename + " are now created.");
|
||||
System.out.println("Admin user created with username: \"" + ADMIN_USERNAME + "\" and password: \"" + ADMIN_PASSWORD + "\"");
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error creating files");
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// load reservations from reservationFile
|
||||
try (Scanner scanner = new Scanner(new File(reservationFilename))) {
|
||||
while (scanner.hasNextLine()) {
|
||||
String line = scanner.nextLine();
|
||||
String[] parts = line.split(",");
|
||||
int seatNumber = Integer.parseInt(parts[0]);
|
||||
String passengerId = parts[1];
|
||||
seatReservations.set(seatNumber, passengerId);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
System.out.println(reservationFilename + " not found");
|
||||
}
|
||||
|
||||
// load users from userFile
|
||||
try (Scanner scanner = new Scanner(new File(userFilename))) {
|
||||
while (scanner.hasNextLine()) {
|
||||
String line = scanner.nextLine();
|
||||
User user = User.fromCsvString(line);
|
||||
users.put(user.getId(), user);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
System.out.println(userFilename + " not found");
|
||||
}
|
||||
System.out.println("Existing Reservations and Users are loaded.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Save reservations and users to files.
|
||||
*
|
||||
* @param reservationFilename reservation file name
|
||||
* @param userFilename user file name
|
||||
*/
|
||||
private static void saveFiles(String reservationFilename, String userFilename) {
|
||||
try (PrintWriter reservationWriter = new PrintWriter(reservationFilename)) {
|
||||
for (int seatNumber = 0; seatNumber < seatReservations.size(); seatNumber++) {
|
||||
if (seatReservations.get(seatNumber) != null) {
|
||||
reservationWriter.println(Seat.toCsvString(seatNumber, seatReservations.get(seatNumber)));
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
System.out.println("Error saving reservations");
|
||||
}
|
||||
try (PrintWriter userWriter = new PrintWriter(userFilename)) {
|
||||
users.values().forEach(user -> userWriter.println(user.toCsvString()));
|
||||
} catch (FileNotFoundException e) {
|
||||
System.out.println("Error saving users");
|
||||
}
|
||||
System.out.printf("Saved reservations to %s and users to %s%n", reservationFilename, userFilename);
|
||||
}
|
||||
}
|
||||
124
hw2/src/Seat.java
Normal file
124
hw2/src/Seat.java
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Seat class - static methods for seat number conversion, seat class and price calculation.
|
||||
* row numbers are 1 to 50, column letters are 'A' to 'J'. This is represented by numbers 0 to 499.
|
||||
* <p>
|
||||
* Defines constants for total seats (500), row size (10), row count (50), first class seats (40),
|
||||
* economy plus seats (110), economy seats (350), first class price ($1000), economy plus price ($500),
|
||||
* economy price ($250).
|
||||
*
|
||||
* @author Yuri Tatishchev
|
||||
* @version 0.1 2025-02-24
|
||||
*/
|
||||
public class Seat {
|
||||
public static final int TOTAL_SEATS = 500;
|
||||
public static final int ROW_SIZE = 10;
|
||||
public static final int ROW_COUNT = TOTAL_SEATS / ROW_SIZE;
|
||||
public static final int FIRST_CLASS_SEATS = 40;
|
||||
public static final int ECONOMY_PLUS_SEATS = 110;
|
||||
public static final int ECONOMY_SEATS = TOTAL_SEATS - FIRST_CLASS_SEATS - ECONOMY_PLUS_SEATS;
|
||||
|
||||
public static final int FIRST_CLASS_PRICE = 1000;
|
||||
public static final int ECONOMY_PLUS_PRICE = 500;
|
||||
public static final int ECONOMY_PRICE = 250;
|
||||
|
||||
public enum ServiceClass {
|
||||
FIRST_CLASS,
|
||||
ECONOMY_PLUS,
|
||||
ECONOMY,
|
||||
}
|
||||
|
||||
/**
|
||||
* Get seat number from row and column.
|
||||
*
|
||||
* @param row 1 to 50 row number
|
||||
* @param column 'A' to 'J' column letter
|
||||
* @return seat number from 0 to 499
|
||||
* @throws IllegalArgumentException if row or column is out of range
|
||||
*/
|
||||
public static int getSeatNumber(int row, char column) {
|
||||
if (row < 1 || row > ROW_COUNT || column < 'A' || column >= 'A' + ROW_SIZE) {
|
||||
throw new IllegalArgumentException("Invalid seat number");
|
||||
}
|
||||
return (row - 1) * 10 + (column - 'A');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get seat number from seat string.
|
||||
*
|
||||
* @param seat seat string like "1A"
|
||||
* @return seat number from 0 to 499
|
||||
* @throws IllegalArgumentException if seat string is invalid
|
||||
*/
|
||||
public static int getSeatNumber(String seat) {
|
||||
String seatNumber = seat.substring(0, seat.length() - 1);
|
||||
char seatColumn = seat.charAt(seat.length() - 1);
|
||||
return getSeatNumber(Integer.parseInt(seatNumber), seatColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get row number from seat number.
|
||||
*
|
||||
* @param seatNumber 0 to 499 seat number
|
||||
* @return 1 to 50 row number
|
||||
*/
|
||||
public static int getSeatRow(int seatNumber) {
|
||||
return (seatNumber / 10) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get column letter from seat number.
|
||||
*
|
||||
* @param seatNumber 0 to 499 seat number
|
||||
* @return 'A' to 'J' column letter
|
||||
*/
|
||||
public static char getSeatColumn(int seatNumber) {
|
||||
return (char) ('A' + seatNumber % 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a csv formatted string from seat number and passenger id.
|
||||
*
|
||||
* @param seatNumber 0 to 499 seat number
|
||||
* @param passengerId passenger id
|
||||
* @return csv formatted string like "1A,user123"
|
||||
*/
|
||||
public static String toCsvString(int seatNumber, String passengerId) {
|
||||
return String.format("%d,%s", seatNumber, passengerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the service class for a given seat number.
|
||||
*
|
||||
* @param seatNumber 0 to 499 seat number
|
||||
* @return service class
|
||||
*/
|
||||
public static ServiceClass getServiceClass(int seatNumber) {
|
||||
if (seatNumber < FIRST_CLASS_SEATS) {
|
||||
return ServiceClass.FIRST_CLASS;
|
||||
} else if (seatNumber < FIRST_CLASS_SEATS + ECONOMY_PLUS_SEATS) {
|
||||
return ServiceClass.ECONOMY_PLUS;
|
||||
}
|
||||
return ServiceClass.ECONOMY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the price for a given seat number, based on the service class.
|
||||
*
|
||||
* @param seatNumber 0 to 499 seat number
|
||||
* @return price
|
||||
*/
|
||||
public static int getSeatPrice(int seatNumber) {
|
||||
switch (getServiceClass(seatNumber)) {
|
||||
case FIRST_CLASS -> {
|
||||
return FIRST_CLASS_PRICE;
|
||||
}
|
||||
case ECONOMY_PLUS -> {
|
||||
return ECONOMY_PLUS_PRICE;
|
||||
}
|
||||
case ECONOMY -> {
|
||||
return ECONOMY_PRICE;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
97
hw2/src/User.java
Normal file
97
hw2/src/User.java
Normal file
@@ -0,0 +1,97 @@
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* A class representing a user in the system.
|
||||
* <p>
|
||||
* A user has an id, a type, a name, and a password.
|
||||
* The id is a unique identifier (string) for the user.
|
||||
* The type can be either ADMIN or PUBLIC.
|
||||
* The name and password are stored as base64-encoded strings.
|
||||
*
|
||||
* @author Yuri Tatishchev
|
||||
* @version 0.1 2025-02-24
|
||||
*/
|
||||
public class User {
|
||||
public enum Type {
|
||||
ADMIN,
|
||||
PUBLIC,
|
||||
}
|
||||
|
||||
private String id;
|
||||
private Type type;
|
||||
private String name;
|
||||
private String password;
|
||||
|
||||
public User(String id, Type type, String name, String password) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a User object from a CSV string.
|
||||
*
|
||||
* @param line The CSV string, should be in the format:
|
||||
* <code>id,type,base64(name),base64(password)</code>.
|
||||
* with escaped backslashes and commas.
|
||||
* @return a User object
|
||||
*/
|
||||
public static User fromCsvString(String line) {
|
||||
String[] parts = line.split(",");
|
||||
String id = parts[0];
|
||||
Type type = Type.valueOf(parts[1]);
|
||||
String name = new String(Base64.getDecoder().decode(parts[2]));
|
||||
String password = new String(Base64.getDecoder().decode(parts[3]));
|
||||
return new User(id, type, name, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a CSV formatted string from the user object.
|
||||
*
|
||||
* @return a CSV formatted string in the format:
|
||||
* <code>id,type,base64(name),base64(password)</code>.
|
||||
*/
|
||||
public String toCsvString() {
|
||||
// don't forget to escape commas in name and password
|
||||
String csvName = Base64.getEncoder().encodeToString(name.getBytes());
|
||||
String csvPassword = Base64.getEncoder().encodeToString(password.getBytes());
|
||||
return String.join(",", id, type.toString(), csvName, csvPassword);
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given password matches the user's password.
|
||||
*
|
||||
* @param password the inputted password to check
|
||||
* @return true if the password matches, false otherwise
|
||||
*/
|
||||
public boolean checkPassword(String password) {
|
||||
return this.password.equals(password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user is an admin.
|
||||
*
|
||||
* @return true if the user is an admin, false otherwise
|
||||
*/
|
||||
public boolean isAdmin() {
|
||||
return type == Type.ADMIN;
|
||||
}
|
||||
|
||||
}
|
||||
29
hw3/.gitignore
vendored
Normal file
29
hw3/.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
### IntelliJ IDEA ###
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
8
hw3/.idea/.gitignore
generated
vendored
Normal file
8
hw3/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
6
hw3/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
hw3/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
6
hw3/.idea/misc.xml
generated
Normal file
6
hw3/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-22" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
8
hw3/.idea/modules.xml
generated
Normal file
8
hw3/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/hw3.iml" filepath="$PROJECT_DIR$/hw3.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
hw3/.idea/vcs.xml
generated
Normal file
6
hw3/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
79
hw3/README.md
Normal file
79
hw3/README.md
Normal file
@@ -0,0 +1,79 @@
|
||||
## Objectives
|
||||
|
||||
Design and implement a desktop-based photo album manager using Java Swing. The application should demonstrate the use of the Iterator pattern, the MVC pattern, and the Strategy pattern to allow flexible behavior for sorting and displaying images.
|
||||
|
||||
## Problem Statement
|
||||
|
||||
Create a photo album manager application that allows users to:
|
||||
|
||||
- Add, view, and delete photos in the album.
|
||||
- Navigate through the album using next/previous buttons.
|
||||
- Display the photos in three different sorting orders by name, date added, and file size.
|
||||
- The sorting should be implemented using the Strategy pattern, allowing different sorting strategies to be selected.
|
||||
- The album should be iterated through using the Iterator pattern.
|
||||
- The application should follow the MVC (Model-View-Controller) pattern to separate business logic, user interface, and control flow.
|
||||
|
||||
## Functional Requirements
|
||||
|
||||
- Photo Management:
|
||||
- The program manages real images.
|
||||
- Users should be able to add new photos (with a name, file path, and date).
|
||||
- Users can delete photos by name from the album.
|
||||
- Display a list of photos (name and thumbnail preview).
|
||||
- Display the current photo.
|
||||
- Navigation:
|
||||
- The "Next" and "Previous" buttons change the current photo to the next photo and the previous photo, respectively. The next and previous photo are determined by the current sorting order of the list of photos.
|
||||
- The application must implement an Iterator to traverse the photo collection.
|
||||
- Sorting Photos:
|
||||
- Users can sort the list of photos by name, date added, or file size.
|
||||
- The sorting behavior should be dynamic and implemented using the Strategy pattern.
|
||||
- The system should handle the following edge cases: empty photo albums (e.g. can't delete from an empty photo album) and non-existent file paths.
|
||||
|
||||
## Use of Patterns
|
||||
|
||||
### MVC
|
||||
|
||||
- Model (`PhotoAlbumModel`) Represents the photo collection and the current state of the album. Notifies the view when there is a change in the photo collection (by adding or deleting). Photo class contains attributes like name, file path, and date added.
|
||||
- View (`PhotoAlbumView`) A Swing-based UI that displays the up-to-date list of photos in the order of date by default and the current photo.
|
||||
- Controller (`PhotoAlbumController`) Provides controls (buttons for adding, deleting, sorting, navigating). Has buttons like "Add Photo", "Delete Photo", "Next", "Previous", and "Sort By" (Name/Date/Size). Responds to user actions and modifies the model accordingly. Manages the connection between user input (e.g., button clicks) and the photo album (model).
|
||||
|
||||
### Iterator Pattern
|
||||
|
||||
Implement an AlbumIterator class (class that implements the AlbumIterator interface) to iterate over the photos in the album.
|
||||
|
||||
### Strategy Pattern
|
||||
|
||||
Create three different sorting strategies, specifically SortByName, SortByDate, SortBySize for sorting the photos. The user can select different strategies dynamically to change the sorting order of the photos.
|
||||
|
||||
## Rough Layout of the User Interface
|
||||
|
||||
You should adhere to the layout provided below as closely as possible, but some reasonable flexibility is allowed.
|
||||

|
||||
|
||||
## Submission
|
||||
|
||||
Submit hw3.zip through the course web site. The file hw3.zip should contain the following:
|
||||
|
||||
- All source files
|
||||
- PhotoAlbumApp.java (with main), Photo.java, PhotoAlbumController.java, PhotoAlbumModel.java, PhotoAlbumView.java, SortByDate.java, SortByName.java, SortBySize.java, SortingStrategy.java.
|
||||
- Put javadoc comments in the source codes.
|
||||
- Remove all package statements from the source files.
|
||||
- All .java files (not .class files)
|
||||
- googledoc.txt: In this text file, specify a link to your google doc, titled as CS151_HW3_YOUR ID. Be sure to make this document available to ANYONE WHO HAS THE LINK (There will be point deductions if this requirement is not met.) The google doc should not be modified after the due date . The followings are required to present in the google document.
|
||||
- Screen 1: Initial screen without any photos.
|
||||
- Screen 2: After adding the first photo, the list and the current photo change.
|
||||
- Screen 3: After adding the second photo, the list updates. Photos are displayed in order of date, with the first photo in the list set as the current photo.
|
||||
- Screen 4: After adding the third photo, the list updates. Photos are displayed in order of date, with the first photo in the list set as the current photo.
|
||||
- Screen 5: After sorting the list by name, both the list and the current photo change.
|
||||
- Screen 6: After sorting the list by size, both the list and the current photo change.
|
||||
- Screen 7: After sorting the list by date, both the list and the current photo change.
|
||||
- Screen 8: After the next button is clicked, the list remains the same but the current photo changes.
|
||||
- Screen 9: After the previous button is clicked, the list remains the same but the current photo changes.
|
||||
- Screen 10: After deleting one of the photos by name. The list changes. The current photo only changes if the deleted photo was the current one. If so, the first photo in the updated list becomes the new current photo.
|
||||
- Show the correct use of patterns (copy and paste the specified code from your Java programs and label them clearly):
|
||||
- MVC Pattern
|
||||
- Controller: The action listener code for the "Add" button, which calls the mutator of the model.
|
||||
- Model: The data structure and the mutator that updates the data structure and notifies the view.
|
||||
- View: The code that calls the model's accessor and triggers a repaint.
|
||||
- Strategy Pattern: A class diagram documenting the use of the Strategy pattern. One interface and three concrete classes are expected.
|
||||
- Iterator Pattern: The Iterator class. (That is, a class that implements the AlbumnIterator class.)
|
||||
1
hw3/googledoc.txt
Normal file
1
hw3/googledoc.txt
Normal file
@@ -0,0 +1 @@
|
||||
https://docs.google.com/document/d/1iuiCyrvFca-Ygdt_7QWl_Ie5XfvbfI1GYd8HVLfGGH0/edit?usp=sharing
|
||||
11
hw3/hw3.iml
Normal file
11
hw3/hw3.iml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
BIN
hw3/photomanager.png
Normal file
BIN
hw3/photomanager.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 149 KiB |
27
hw3/src/PhotoAlbumApp.java
Normal file
27
hw3/src/PhotoAlbumApp.java
Normal file
@@ -0,0 +1,27 @@
|
||||
import controller.PhotoAlbumController;
|
||||
import model.PhotoAlbumModel;
|
||||
import view.PhotoAlbumView;
|
||||
|
||||
|
||||
/**
|
||||
* Photo Album Manager application entry point.
|
||||
* <p>
|
||||
* This class creates the model, view, and controller objects and initializes the view.
|
||||
*
|
||||
* @author Yuri Tatishchev
|
||||
* @version 0.1 2025-03-26
|
||||
*/
|
||||
public class PhotoAlbumApp {
|
||||
public static void main(String[] args) {
|
||||
// Create MVC components
|
||||
PhotoAlbumModel model = new PhotoAlbumModel();
|
||||
PhotoAlbumView view = new PhotoAlbumView();
|
||||
PhotoAlbumController controller = new PhotoAlbumController(model, view);
|
||||
|
||||
// Initialize view with the controller
|
||||
view.setController(controller);
|
||||
|
||||
// Display the main window
|
||||
javax.swing.SwingUtilities.invokeLater(() -> view.setVisible(true));
|
||||
}
|
||||
}
|
||||
135
hw3/src/controller/PhotoAlbumController.java
Normal file
135
hw3/src/controller/PhotoAlbumController.java
Normal file
@@ -0,0 +1,135 @@
|
||||
package controller;
|
||||
|
||||
import model.Photo;
|
||||
import model.PhotoAlbumModel;
|
||||
import strategy.SortByDate;
|
||||
import strategy.SortByName;
|
||||
import strategy.SortBySize;
|
||||
import view.PhotoAlbumView;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A controller that manages interactions between the photo album model and view.
|
||||
* <p>
|
||||
* This class handles user actions from the view, such as adding and deleting photos,
|
||||
* navigating through the album, and changing the sorting strategy.
|
||||
* It acts as an intermediary between the {@link PhotoAlbumModel} and {@link PhotoAlbumView},
|
||||
* translating user interface events into model operations.
|
||||
*
|
||||
* @author Yuri Tatishchev
|
||||
* @version 0.1 2025-03-26
|
||||
* @see PhotoAlbumModel
|
||||
* @see PhotoAlbumView
|
||||
* @see Photo
|
||||
*/
|
||||
public class PhotoAlbumController {
|
||||
private final PhotoAlbumModel model;
|
||||
private final PhotoAlbumView view;
|
||||
|
||||
/**
|
||||
* Constructs a new photo album controller.
|
||||
*
|
||||
* @param model the photo album model to control
|
||||
* @param view the view to handle user interactions
|
||||
*/
|
||||
public PhotoAlbumController(PhotoAlbumModel model, PhotoAlbumView view) {
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the photo album model being controlled.
|
||||
*
|
||||
* @return the photo album model
|
||||
*/
|
||||
public PhotoAlbumModel getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the addition of a new photo to the album.
|
||||
* Opens a file chooser dialog for selecting an image file and
|
||||
* prompts for a photo name.
|
||||
*/
|
||||
public void handleAddPhoto() {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setFileFilter(new FileNameExtensionFilter(
|
||||
"Image files",
|
||||
"jpg", "jpeg", "png", "gif"
|
||||
));
|
||||
|
||||
if (fileChooser.showOpenDialog(view) == JFileChooser.APPROVE_OPTION) {
|
||||
File file = fileChooser.getSelectedFile();
|
||||
String name = JOptionPane.showInputDialog(view, "Enter photo name:");
|
||||
|
||||
if (name != null && !name.trim().isEmpty()) {
|
||||
Photo photo = new Photo(
|
||||
name,
|
||||
file.getAbsolutePath(),
|
||||
new Date(),
|
||||
file.length()
|
||||
);
|
||||
model.addPhoto(photo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the deletion of a photo from the album.
|
||||
* Prompts for the name of the photo to delete.
|
||||
*/
|
||||
public void handleDeletePhoto() {
|
||||
String name = JOptionPane.showInputDialog(view, "Enter photo name to delete:");
|
||||
if (name != null && !name.trim().isEmpty()) {
|
||||
model.deletePhoto(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles navigation to the next photo in the album.
|
||||
* Moves to the next photo if one exists.
|
||||
*/
|
||||
public void handleNext() {
|
||||
if (model.hasNext()) {
|
||||
model.next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles navigation to the previous photo in the album.
|
||||
* Moves to the previous photo if one exists.
|
||||
*/
|
||||
public void handlePrevious() {
|
||||
if (model.hasPrevious()) {
|
||||
model.previous();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles changing the sorting strategy for photos in the album.
|
||||
*
|
||||
* @param strategy the sorting strategy to use:
|
||||
* <ul>
|
||||
* <li>0 for name-based sorting,
|
||||
* <li>1 for date-based sorting,
|
||||
* <li>2 for size-based sorting
|
||||
* </ul>
|
||||
*/
|
||||
public void handleSort(int strategy) {
|
||||
switch (strategy) {
|
||||
case 0:
|
||||
model.setSortingStrategy(new SortByName());
|
||||
break;
|
||||
case 1:
|
||||
model.setSortingStrategy(new SortByDate());
|
||||
break;
|
||||
case 2:
|
||||
model.setSortingStrategy(new SortBySize());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
hw3/src/iterator/AlbumIterator.java
Normal file
35
hw3/src/iterator/AlbumIterator.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package iterator;
|
||||
|
||||
import model.Photo;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Interface for album iterators.
|
||||
*/
|
||||
public interface AlbumIterator extends Iterator<Photo> {
|
||||
boolean hasNext(); // to check if there is a next element
|
||||
|
||||
/**
|
||||
* Returns whether there is a previous photo in the album.
|
||||
*
|
||||
* @return {@code true} if there is a previous photo, {@code false} otherwise
|
||||
*/
|
||||
boolean hasPrevious(); // to check if there is a previous element
|
||||
|
||||
/**
|
||||
* Returns the current photo in the album.
|
||||
*
|
||||
* @return the current photo
|
||||
*/
|
||||
Photo current(); // to get the photo at the current position
|
||||
|
||||
Photo next(); // to advance the iterator to the next position
|
||||
|
||||
/**
|
||||
* Moves to and returns the previous photo in the album.
|
||||
*
|
||||
* @return the previous photo
|
||||
*/
|
||||
Photo previous(); // to advance the iterator to the previous position
|
||||
}
|
||||
79
hw3/src/iterator/PhotoIterator.java
Normal file
79
hw3/src/iterator/PhotoIterator.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package iterator;
|
||||
|
||||
import model.Photo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* An iterator implementation for navigating through photos in a photo album.
|
||||
* <p>
|
||||
* This class provides functionality to traverse a list of photos in both
|
||||
* forward and backward directions. It maintains a current position and
|
||||
* provides methods to check for the existence of next and previous photos,
|
||||
* as well as to retrieve the current, next, and previous photos.
|
||||
* <p>
|
||||
* The iterator throws {@link NoSuchElementException} when attempting to
|
||||
* access elements beyond the bounds of the photo list.
|
||||
*
|
||||
* @author Yuri Tatishchev
|
||||
* @version 0.1 2025-03-26
|
||||
* @see Photo
|
||||
* @see AlbumIterator
|
||||
* @see NoSuchElementException
|
||||
*/
|
||||
public class PhotoIterator implements AlbumIterator {
|
||||
private final List<Photo> photos;
|
||||
private int currentPosition;
|
||||
|
||||
/**
|
||||
* Constructs a new photo iterator for the given list of photos.
|
||||
* The iterator starts at position 0.
|
||||
*
|
||||
* @param photos the list of photos to iterate over
|
||||
*/
|
||||
public PhotoIterator(List<Photo> photos) {
|
||||
this.photos = photos;
|
||||
this.currentPosition = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return currentPosition < photos.size() - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrevious() {
|
||||
return currentPosition > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Photo current() {
|
||||
if (photos.isEmpty()) {
|
||||
throw new NoSuchElementException("The photo album is empty.");
|
||||
}
|
||||
return photos.get(currentPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NoSuchElementException if there is no next photo
|
||||
*/
|
||||
@Override
|
||||
public Photo next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException("No next photo in the album.");
|
||||
}
|
||||
return photos.get(++currentPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NoSuchElementException if there is no previous photo
|
||||
*/
|
||||
@Override
|
||||
public Photo previous() {
|
||||
if (!hasPrevious()) {
|
||||
throw new NoSuchElementException("No previous photo in the album.");
|
||||
}
|
||||
return photos.get(--currentPosition);
|
||||
}
|
||||
}
|
||||
22
hw3/src/model/Photo.java
Normal file
22
hw3/src/model/Photo.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A record class that represents a photo.
|
||||
* <p>
|
||||
* A photo has a:
|
||||
* <ul>
|
||||
* <li>name
|
||||
* <li>file path
|
||||
* <li>date added
|
||||
* <li>file size
|
||||
* </ul>
|
||||
* <p>
|
||||
* A photo is immutable.
|
||||
*
|
||||
* @author Yuri Tatishchev
|
||||
* @version 0.1 2022-03-26
|
||||
*/
|
||||
public record Photo(String name, String filePath, Date dateAdded, long fileSize) {
|
||||
}
|
||||
187
hw3/src/model/PhotoAlbumModel.java
Normal file
187
hw3/src/model/PhotoAlbumModel.java
Normal file
@@ -0,0 +1,187 @@
|
||||
package model;
|
||||
|
||||
import strategy.SortingStrategy;
|
||||
import iterator.AlbumIterator;
|
||||
import iterator.PhotoIterator;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A model that represents a photo album.
|
||||
* <p>
|
||||
* This class is responsible for managing the photos in the album, as well as
|
||||
* the sorting strategy used to sort the photos.
|
||||
* The model notifies its listeners when the model changes.
|
||||
* It also provides methods to add and delete photos,
|
||||
* as well as to navigate through the photos.
|
||||
*
|
||||
* @author Yuri Tatishchev
|
||||
* @version 0.1 2025-03-26
|
||||
* @see Photo
|
||||
* @see SortingStrategy
|
||||
* @see AlbumIterator
|
||||
* @see PhotoIterator
|
||||
* @see ModelChangeListener
|
||||
*/
|
||||
public class PhotoAlbumModel {
|
||||
private List<Photo> photos;
|
||||
private SortingStrategy<Photo> sortingStrategy;
|
||||
private final List<ModelChangeListener> listeners;
|
||||
private AlbumIterator iterator;
|
||||
|
||||
/**
|
||||
* A listener interface for model changes.
|
||||
* The method {@link #onModelChanged()} is called when the model changes.
|
||||
*/
|
||||
public interface ModelChangeListener {
|
||||
/**
|
||||
* Called when the model changes.
|
||||
*/
|
||||
void onModelChanged();
|
||||
}
|
||||
|
||||
public PhotoAlbumModel() {
|
||||
photos = new ArrayList<>();
|
||||
listeners = new ArrayList<>();
|
||||
iterator = new PhotoIterator(photos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a photo to the album.
|
||||
* Resets the iterator and notifies the listeners.
|
||||
*
|
||||
* @param photo the photo to add
|
||||
*/
|
||||
public void addPhoto(Photo photo) {
|
||||
photos.add(photo);
|
||||
sortPhotos();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a photo from the album by name.
|
||||
* If the deleted photo is the current photo or the album is empty,
|
||||
* the iterator is reset.
|
||||
*
|
||||
* @param name the name of the photo to delete
|
||||
*/
|
||||
public void deletePhoto(String name) {
|
||||
Photo currentPhoto = iterator.current();
|
||||
photos.removeIf(photo -> photo.name().equals(name));
|
||||
if (photos.isEmpty() || (currentPhoto != null && currentPhoto.name().equals(name))) {
|
||||
iterator = new PhotoIterator(photos);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sorting strategy for the photos.
|
||||
* Sorts the photos using the new strategy and resets the iterator.
|
||||
*
|
||||
* @param strategy the sorting strategy to set
|
||||
*/
|
||||
public void setSortingStrategy(SortingStrategy<Photo> strategy) {
|
||||
this.sortingStrategy = strategy;
|
||||
sortPhotos();
|
||||
iterator = new PhotoIterator(photos);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
private void sortPhotos() {
|
||||
if (sortingStrategy != null) {
|
||||
photos = sortingStrategy.sort(photos);
|
||||
iterator = new PhotoIterator(photos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for model changes.
|
||||
*
|
||||
* @param listener the listener to add
|
||||
*/
|
||||
public void addListener(ModelChangeListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all listeners that the model has changed,
|
||||
* calling the {@link ModelChangeListener#onModelChanged()} method.
|
||||
*/
|
||||
private void notifyListeners() {
|
||||
for (ModelChangeListener listener : listeners) {
|
||||
listener.onModelChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable list of photos in the album.
|
||||
*
|
||||
* @return an unmodifiable list of photos
|
||||
*/
|
||||
public List<Photo> getPhotos() {
|
||||
return Collections.unmodifiableList(photos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the photo in the album that the iterator is currently pointing to.
|
||||
*
|
||||
* @return the current photo
|
||||
*/
|
||||
public Photo getCurrentPhoto() {
|
||||
try {
|
||||
return iterator.current();
|
||||
} catch (NoSuchElementException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether there is a next photo in the album.
|
||||
*
|
||||
* @return {@code true} if there is a next photo, {@code false} otherwise
|
||||
*/
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether there is a previous photo in the album.
|
||||
*
|
||||
* @return {@code true} if there is a previous photo, {@code false} otherwise
|
||||
*/
|
||||
public boolean hasPrevious() {
|
||||
return iterator.hasPrevious();
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances the iterator to the next photo in the album.
|
||||
* Notifies the listeners.
|
||||
*
|
||||
* @return the next photo or {@code null} if there is no next photo
|
||||
*/
|
||||
public Photo next() {
|
||||
try {
|
||||
Photo next = iterator.next();
|
||||
notifyListeners();
|
||||
return next;
|
||||
} catch (NoSuchElementException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances the iterator to the previous photo in the album.
|
||||
* Notifies the listeners.
|
||||
*
|
||||
* @return the previous photo or {@code null} if there is no previous photo
|
||||
*/
|
||||
public Photo previous() {
|
||||
try {
|
||||
Photo prev = iterator.previous();
|
||||
notifyListeners();
|
||||
return prev;
|
||||
} catch (NoSuchElementException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
hw3/src/strategy/SortByDate.java
Normal file
17
hw3/src/strategy/SortByDate.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package strategy;
|
||||
|
||||
import model.Photo;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A sorting strategy that sorts photos by date.
|
||||
*/
|
||||
public class SortByDate implements SortingStrategy<Photo> {
|
||||
@Override
|
||||
public List<Photo> sort(List<Photo> photos) {
|
||||
photos.sort(Comparator.comparing(Photo::dateAdded));
|
||||
return photos;
|
||||
}
|
||||
}
|
||||
17
hw3/src/strategy/SortByName.java
Normal file
17
hw3/src/strategy/SortByName.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package strategy;
|
||||
|
||||
import model.Photo;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A sorting strategy that sorts photos by name.
|
||||
*/
|
||||
public class SortByName implements SortingStrategy<Photo> {
|
||||
@Override
|
||||
public List<Photo> sort(List<Photo> photos) {
|
||||
photos.sort(Comparator.comparing(Photo::name));
|
||||
return photos;
|
||||
}
|
||||
}
|
||||
17
hw3/src/strategy/SortBySize.java
Normal file
17
hw3/src/strategy/SortBySize.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package strategy;
|
||||
|
||||
import model.Photo;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A sorting strategy that sorts photos by size.
|
||||
*/
|
||||
public class SortBySize implements SortingStrategy<Photo> {
|
||||
@Override
|
||||
public List<Photo> sort(List<Photo> photos) {
|
||||
photos.sort(Comparator.comparing(Photo::fileSize));
|
||||
return photos;
|
||||
}
|
||||
}
|
||||
12
hw3/src/strategy/SortingStrategy.java
Normal file
12
hw3/src/strategy/SortingStrategy.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package strategy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface for sorting strategies.
|
||||
*
|
||||
* @param <T> the type of elements to be sorted
|
||||
*/
|
||||
public interface SortingStrategy<T> {
|
||||
List<T> sort(List<T> photos);
|
||||
}
|
||||
318
hw3/src/view/PhotoAlbumView.java
Normal file
318
hw3/src/view/PhotoAlbumView.java
Normal file
@@ -0,0 +1,318 @@
|
||||
package view;
|
||||
|
||||
import controller.PhotoAlbumController;
|
||||
import model.Photo;
|
||||
import model.PhotoAlbumModel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A view that displays a photo album user interface.
|
||||
* <p>
|
||||
* This class is responsible for displaying the photo album's graphical interface,
|
||||
* including the list of photos, current photo display, and control buttons.
|
||||
* The view implements the {@link PhotoAlbumModel.ModelChangeListener} interface
|
||||
* to receive notifications of model changes.
|
||||
* <p>
|
||||
* The view provides user interface components for:
|
||||
* <ul>
|
||||
* <li>Displaying a list of photos with thumbnails and details
|
||||
* <li>Showing the currently selected photo
|
||||
* <li>Navigation controls (previous/next)
|
||||
* <li>Photo management (add/delete)
|
||||
* <li>Sorting options
|
||||
* </ul>
|
||||
*
|
||||
* @author Yuri Tatishchev
|
||||
* @version 0.1 2025-03-26
|
||||
* @see PhotoAlbumModel
|
||||
* @see PhotoAlbumController
|
||||
* @see Photo
|
||||
*/
|
||||
public class PhotoAlbumView extends JFrame implements PhotoAlbumModel.ModelChangeListener {
|
||||
private PhotoAlbumModel model;
|
||||
|
||||
private JList<String> photoList;
|
||||
private DefaultListModel<String> listModel;
|
||||
private JLabel currentPhotoLabel;
|
||||
private JButton addButton;
|
||||
private JButton deleteButton;
|
||||
private JButton previousButton;
|
||||
private JButton nextButton;
|
||||
private JComboBox<String> sortingCombo;
|
||||
|
||||
public PhotoAlbumView() {
|
||||
initializeComponents();
|
||||
setupLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all UI components of the photo album view.
|
||||
* <p>
|
||||
* Sets up the frame properties, creates buttons, list components,
|
||||
* and initializes them with default states.
|
||||
*/
|
||||
private void initializeComponents() {
|
||||
setTitle("Photo Album Manager");
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setSize(800, 600);
|
||||
|
||||
listModel = new DefaultListModel<>();
|
||||
photoList = new JList<>(listModel);
|
||||
currentPhotoLabel = new JLabel("No photo selected", SwingConstants.CENTER);
|
||||
|
||||
addButton = new JButton("Add Photo");
|
||||
deleteButton = new JButton("Delete Photo");
|
||||
previousButton = new JButton("Previous");
|
||||
nextButton = new JButton("Next");
|
||||
// Disable navigation buttons by default
|
||||
previousButton.setEnabled(false);
|
||||
nextButton.setEnabled(false);
|
||||
deleteButton.setEnabled(false);
|
||||
|
||||
String[] sortOptions = {"Sort by Name", "Sort by Date", "Sort by Size"};
|
||||
sortingCombo = new JComboBox<>(sortOptions);
|
||||
|
||||
listModel = new DefaultListModel<>();
|
||||
photoList = new JList<>(listModel);
|
||||
photoList.setEnabled(false);
|
||||
photoList.setCellRenderer(new PhotoListCellRenderer());
|
||||
photoList.setFixedCellHeight(60); // Accommodate thumbnails
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the layout of the photo album view.
|
||||
* <p>
|
||||
* Arranges the UI components using BorderLayout with:
|
||||
* <ul>
|
||||
* <li>Photo list in the WEST
|
||||
* <li>Current photo display in the CENTER
|
||||
* <li>Control buttons in the SOUTH
|
||||
* </ul>
|
||||
*/
|
||||
private void setupLayout() {
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
// Left panel with photo list
|
||||
JScrollPane listScrollPane = new JScrollPane(photoList);
|
||||
listScrollPane.setPreferredSize(new Dimension(200, 0));
|
||||
add(listScrollPane, BorderLayout.WEST);
|
||||
|
||||
// Center panel with current photo
|
||||
JPanel centerPanel = new JPanel(new BorderLayout());
|
||||
centerPanel.add(currentPhotoLabel, BorderLayout.CENTER);
|
||||
add(centerPanel, BorderLayout.CENTER);
|
||||
|
||||
// Bottom panel with controls
|
||||
JPanel controlPanel = new JPanel();
|
||||
controlPanel.add(previousButton);
|
||||
controlPanel.add(nextButton);
|
||||
controlPanel.add(addButton);
|
||||
controlPanel.add(deleteButton);
|
||||
controlPanel.add(sortingCombo);
|
||||
add(controlPanel, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the controller for the photo album view.
|
||||
* <p>
|
||||
* Attaches the controller to the view and sets up event listeners
|
||||
* for the control buttons and sorting combo box.
|
||||
*
|
||||
* @param controller the controller to set
|
||||
*/
|
||||
public void setController(PhotoAlbumController controller) {
|
||||
this.model = controller.getModel();
|
||||
model.addListener(this);
|
||||
|
||||
addButton.addActionListener(e -> controller.handleAddPhoto());
|
||||
deleteButton.addActionListener(e -> controller.handleDeletePhoto());
|
||||
nextButton.addActionListener(e -> controller.handleNext());
|
||||
previousButton.addActionListener(e -> controller.handlePrevious());
|
||||
sortingCombo.addActionListener(e -> controller.handleSort(sortingCombo.getSelectedIndex()));
|
||||
|
||||
// Select default sorting option
|
||||
sortingCombo.setSelectedIndex(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModelChanged() {
|
||||
updatePhotoList();
|
||||
updateCurrentPhoto();
|
||||
updateNavigationButtons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the list of photos displayed in the sidebar.
|
||||
* <p>
|
||||
* Clears the current list model and populates it with
|
||||
* names of photos from the model.
|
||||
*/
|
||||
private void updatePhotoList() {
|
||||
listModel.clear();
|
||||
List<Photo> photos = model.getPhotos();
|
||||
for (Photo photo : photos) {
|
||||
listModel.addElement(photo.name());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the display of the current photo in the main view area.
|
||||
* <p>
|
||||
* If a photo is selected, loads and displays its image.
|
||||
* If loading fails, displays an error message.
|
||||
* If no photo is selected, displays a default message.
|
||||
*/
|
||||
private void updateCurrentPhoto() {
|
||||
Photo current = model.getCurrentPhoto();
|
||||
if (current != null) {
|
||||
ImageIcon icon = loadImage(current.filePath());
|
||||
if (icon != null) {
|
||||
currentPhotoLabel.setIcon(icon);
|
||||
currentPhotoLabel.setText("");
|
||||
} else {
|
||||
currentPhotoLabel.setIcon(null);
|
||||
currentPhotoLabel.setText("Unable to load image");
|
||||
}
|
||||
} else {
|
||||
currentPhotoLabel.setIcon(null);
|
||||
currentPhotoLabel.setText("No photo selected");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the enabled state of navigation buttons.
|
||||
* <p>
|
||||
* Enables or disables the previous/next buttons based on
|
||||
* the current position in the photo album.
|
||||
* Enables the delete button only when the album is not empty.
|
||||
*/
|
||||
private void updateNavigationButtons() {
|
||||
previousButton.setEnabled(model.hasPrevious());
|
||||
nextButton.setEnabled(model.hasNext());
|
||||
deleteButton.setEnabled(model.getCurrentPhoto() != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and scales an image from the given path.
|
||||
* <p>
|
||||
* Creates a scaled version of the image suitable for the main display area.
|
||||
*
|
||||
* @param path the file path of the image to load
|
||||
* @return a scaled ImageIcon, or null if loading fails
|
||||
*/
|
||||
private ImageIcon loadImage(String path) {
|
||||
try {
|
||||
ImageIcon icon = new ImageIcon(path);
|
||||
Image img = icon.getImage();
|
||||
Image scaledImg = img.getScaledInstance(400, 300, Image.SCALE_SMOOTH);
|
||||
return new ImageIcon(scaledImg);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom list cell renderer for displaying photos with thumbnails and details.
|
||||
* <p>
|
||||
* This inner class renders each photo in the list with:
|
||||
* <ul>
|
||||
* <li>A thumbnail image
|
||||
* <li>The photo name
|
||||
* <li>Additional details (date and file size)
|
||||
* </ul>
|
||||
* <p>
|
||||
* It also implements thumbnail caching to improve performance.
|
||||
*/
|
||||
private class PhotoListCellRenderer extends JPanel implements ListCellRenderer<String> {
|
||||
private final JLabel imageLabel = new JLabel();
|
||||
private final JLabel textLabel = new JLabel();
|
||||
private final JLabel detailsLabel = new JLabel();
|
||||
|
||||
private final Map<String, ImageIcon> thumbnailCache = new HashMap<>();
|
||||
|
||||
public PhotoListCellRenderer() {
|
||||
setLayout(new BorderLayout(5, 0));
|
||||
|
||||
// Left side - image
|
||||
imageLabel.setPreferredSize(new Dimension(50, 50));
|
||||
add(imageLabel, BorderLayout.WEST);
|
||||
|
||||
// Center - name and details
|
||||
JPanel textPanel = new JPanel(new GridLayout(2, 1));
|
||||
textPanel.setOpaque(false);
|
||||
textLabel.setFont(textLabel.getFont().deriveFont(Font.BOLD));
|
||||
detailsLabel.setForeground(Color.GRAY);
|
||||
detailsLabel.setFont(detailsLabel.getFont().deriveFont(10.0f));
|
||||
textPanel.add(textLabel);
|
||||
textPanel.add(detailsLabel);
|
||||
add(textPanel, BorderLayout.CENTER);
|
||||
|
||||
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
setOpaque(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends String> list,
|
||||
String name,
|
||||
int index,
|
||||
boolean isSelected,
|
||||
boolean cellHasFocus
|
||||
) {
|
||||
textLabel.setText(name);
|
||||
|
||||
Photo photo = model.getPhotos().get(index);
|
||||
ImageIcon thumbnail = thumbnailCache.computeIfAbsent(photo.filePath(), this::loadThumbnail);
|
||||
// ImageIcon thumbnail = loadThumbnail(photo.filePath());
|
||||
imageLabel.setIcon(thumbnail);
|
||||
|
||||
// Format date
|
||||
String date = String.format("%tF", photo.dateAdded());
|
||||
// Format file size
|
||||
String size = formatFileSize(photo.fileSize());
|
||||
detailsLabel.setText(date + " • " + size);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a file size in bytes to a human-readable string.
|
||||
* <p>
|
||||
* Converts the size to kilobytes or megabytes with one decimal place.
|
||||
*
|
||||
* @param size the file size in bytes
|
||||
* @return a formatted string with the size in KB or MB
|
||||
*/
|
||||
private String formatFileSize(long size) {
|
||||
if (size < 1024 * 1024) {
|
||||
return String.format("%.1f KB", size / 1024.0);
|
||||
} else {
|
||||
return String.format("%.1f MB", size / (1024.0 * 1024));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and scales a thumbnail image from the given path.
|
||||
* <p>
|
||||
* Creates a scaled version of the image suitable for the list display.
|
||||
*
|
||||
* @param path the file path of the image to load
|
||||
* @return a scaled ImageIcon, or null if loading fails
|
||||
*/
|
||||
private ImageIcon loadThumbnail(String path) {
|
||||
try {
|
||||
ImageIcon icon = new ImageIcon(path);
|
||||
Image img = icon.getImage();
|
||||
Image scaledImg = img.getScaledInstance(50, 50, Image.SCALE_AREA_AVERAGING);
|
||||
return new ImageIcon(scaledImg);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
hw4/.gitignore
vendored
Normal file
29
hw4/.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
### IntelliJ IDEA ###
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
8
hw4/.idea/.gitignore
generated
vendored
Normal file
8
hw4/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
5
hw4/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
hw4/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
6
hw4/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
hw4/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
6
hw4/.idea/misc.xml
generated
Normal file
6
hw4/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-22" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
8
hw4/.idea/modules.xml
generated
Normal file
8
hw4/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/hw4.iml" filepath="$PROJECT_DIR$/hw4.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
hw4/.idea/vcs.xml
generated
Normal file
6
hw4/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
24
hw4/README.md
Normal file
24
hw4/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Programming Assignment 4 - Shape Displayer
|
||||
|
||||
## Requirements
|
||||
|
||||
The user interacts with the application as follows:
|
||||
|
||||
- Initially, the program displays three buttons on the top of the frame and a
|
||||
blank large area of a user-defined JPanel below the buttons. The buttons
|
||||
display a car, a snow man, and a composite shape of your choice, respectively.
|
||||
You are allowed to borrow the CarShape.java from the text book. Make sure to
|
||||
acknowledge that the code is from the text book. The composite shape of your
|
||||
choice should consist of at least 4 primitive shapes, but doesn't have to be
|
||||
fancy.
|
||||
- When the user clicks on one of the buttons, the shape displayed on the clicked
|
||||
button becomes the current shape. The icon representing the current shape
|
||||
should outline the shape as shown below in the sample output. When a mouse is
|
||||
clicked on the user-defined JPanel, the current shape is drawn on the position
|
||||
which the mouse was pressed on.
|
||||
- The current shape can be changed by clicking one of the buttons.
|
||||
- The application should be reusable by any CompositeShape.
|
||||
- Your design should be object-oriented including ShapeDisplayer(with main
|
||||
method), CarShape, SnowMan, and Your_own_shape classes. Also, it should
|
||||
include interface CompositeShape.java and a concrete class ShapeIcon. You may
|
||||
include more classes to your design.
|
||||
BIN
hw4/diagram.png
Normal file
BIN
hw4/diagram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
80
hw4/diagram.uml
Normal file
80
hw4/diagram.uml
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Diagram>
|
||||
<ID>JAVA</ID>
|
||||
<OriginalElement />
|
||||
<nodes>
|
||||
<node x="-90.5" y="176.0">ShapeIcon</node>
|
||||
<node x="-76.0" y="-4.0">CompositeShape</node>
|
||||
<node x="-50.0" y="296.0">ShapeDisplayer</node>
|
||||
<node x="12.5" y="116.0">CarShape</node>
|
||||
<node x="208.0" y="116.0">SnowmanShape</node>
|
||||
<node x="101.5" y="176.0">HouseShape</node>
|
||||
</nodes>
|
||||
<notes />
|
||||
<edges>
|
||||
<edge source="CarShape" target="CompositeShape" relationship="REALIZATION">
|
||||
<point x="62.5" y="0.0" />
|
||||
<point x="173.0" y="130.0" />
|
||||
<point x="173.0" y="10.0" />
|
||||
<point x="91.0" y="0.0" />
|
||||
</edge>
|
||||
<edge source="ShapeDisplayer" target="CompositeShape" relationship="TO_ONE">
|
||||
<point x="-2.0" y="-14.0" />
|
||||
<point x="33.0" y="221.5" />
|
||||
<point x="-108.0" y="221.5" />
|
||||
<point x="-108.0" y="10.0" />
|
||||
<point x="-91.0" y="0.0" />
|
||||
</edge>
|
||||
<edge source="ShapeDisplayer" target="SnowmanShape" relationship="CREATE">
|
||||
<point x="85.0" y="0.0" />
|
||||
<point x="295.0" y="310.0" />
|
||||
<point x="0.0" y="14.0" />
|
||||
</edge>
|
||||
<edge source="ShapeDisplayer" target="HouseShape" relationship="CREATE">
|
||||
<point x="77.5" y="-14.0" />
|
||||
<point x="-62.5" y="14.0" />
|
||||
</edge>
|
||||
<edge source="ShapeIcon" target="CompositeShape" relationship="TO_ONE">
|
||||
<point x="98.125" y="-0.08333333333334281" />
|
||||
<point x="-298.1666666666667" y="189.91666666666666" />
|
||||
<point x="-298.1666666666667" y="212.33333333333337" />
|
||||
<point x="-108.0" y="212.33333333333337" />
|
||||
<point x="-108.0" y="10.0" />
|
||||
<point x="-91.0" y="0.0" />
|
||||
</edge>
|
||||
<edge source="SnowmanShape" target="CompositeShape" relationship="REALIZATION">
|
||||
<point x="-87.0" y="0.0" />
|
||||
<point x="173.0" y="130.0" />
|
||||
<point x="173.0" y="10.0" />
|
||||
<point x="91.0" y="0.0" />
|
||||
</edge>
|
||||
<edge source="ShapeDisplayer" target="ShapeIcon" relationship="TO_ONE">
|
||||
<point x="-60.0" y="-14.0" />
|
||||
<point x="0.0" y="14.0" />
|
||||
</edge>
|
||||
<edge source="ShapeDisplayer" target="CarShape" relationship="CREATE">
|
||||
<point x="-85.0" y="0.0" />
|
||||
<point x="-123.0" y="310.0" />
|
||||
<point x="-123.0" y="130.0" />
|
||||
<point x="-62.5" y="0.0" />
|
||||
</edge>
|
||||
<edge source="ShapeDisplayer" target="ShapeIcon" relationship="CREATE">
|
||||
<point x="-45.0" y="-14.0" />
|
||||
<point x="15.0" y="14.0" />
|
||||
</edge>
|
||||
<edge source="HouseShape" target="CompositeShape" relationship="REALIZATION">
|
||||
<point x="73.5" y="0.0" />
|
||||
<point x="399.5" y="190.0" />
|
||||
<point x="399.5" y="60.5" />
|
||||
<point x="173.0" y="60.5" />
|
||||
<point x="173.0" y="10.0" />
|
||||
<point x="91.0" y="0.0" />
|
||||
</edge>
|
||||
</edges>
|
||||
<settings layout="Hierarchic Compact" zoom="1.2" showDependencies="true" x="233.54166666666663" y="134.91666666666666" />
|
||||
<SelectedNodes />
|
||||
<Categories />
|
||||
<SCOPE>All</SCOPE>
|
||||
<VISIBILITY>private</VISIBILITY>
|
||||
</Diagram>
|
||||
|
||||
11
hw4/hw4.iml
Normal file
11
hw4/hw4.iml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
77
hw4/src/CarShape.java
Normal file
77
hw4/src/CarShape.java
Normal file
@@ -0,0 +1,77 @@
|
||||
import java.awt.*;
|
||||
import java.awt.geom.*;
|
||||
|
||||
/**
|
||||
* A car shape.
|
||||
* Partially taken from the textbook (Cay S. Horstmann - OO Design & Patterns, 2nd ed.)
|
||||
*/
|
||||
public class CarShape implements CompositeShape {
|
||||
private final int x; // Base x-coordinate (relative to drawing origin)
|
||||
private final int y; // Base y-coordinate (relative to drawing origin)
|
||||
private final int width;
|
||||
|
||||
public CarShape() {
|
||||
this(0, 0, 60); // Default position and width
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a car shape.
|
||||
*
|
||||
* @param x the left of the bounding rectangle
|
||||
* @param y the top of the bounding rectangle
|
||||
* @param width the width of the bounding rectangle
|
||||
*/
|
||||
public CarShape(int x, int y, int width) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return width; // Width of the car body
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
// Approximate height: body height + wheel radius
|
||||
return width / 2 + width / 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g2) {
|
||||
Rectangle2D.Double body = new Rectangle2D.Double(x, y + width / 6.0, width - 1, width / 6.0);
|
||||
Ellipse2D.Double frontTire = new Ellipse2D.Double(
|
||||
x + width / 6.0,
|
||||
y + width / 3.0,
|
||||
width / 6.0,
|
||||
width / 6.0
|
||||
);
|
||||
Ellipse2D.Double rearTire = new Ellipse2D.Double(
|
||||
x + width * 2 / 3.0,
|
||||
y + width / 3.0,
|
||||
width / 6.0,
|
||||
width / 6.0
|
||||
);
|
||||
|
||||
// The bottom of the front windshield
|
||||
Point2D.Double r1 = new Point2D.Double(x + width / 6.0, y + width / 6.0);
|
||||
// The front of the roof
|
||||
Point2D.Double r2 = new Point2D.Double(x + width / 3.0, y);
|
||||
// The rear of the roof
|
||||
Point2D.Double r3 = new Point2D.Double(x + width * 2 / 3.0, y);
|
||||
// The bottom of the rear windshield
|
||||
Point2D.Double r4 = new Point2D.Double(x + width * 5 / 6.0, y + width / 6.0);
|
||||
Line2D.Double frontWindshield = new Line2D.Double(r1, r2);
|
||||
Line2D.Double roofTop = new Line2D.Double(r2, r3);
|
||||
Line2D.Double rearWindshield = new Line2D.Double(r3, r4);
|
||||
|
||||
// Draw the parts
|
||||
g2.draw(body);
|
||||
g2.draw(frontTire);
|
||||
g2.draw(rearTire);
|
||||
g2.draw(frontWindshield);
|
||||
g2.draw(roofTop);
|
||||
g2.draw(rearWindshield);
|
||||
}
|
||||
}
|
||||
27
hw4/src/CompositeShape.java
Normal file
27
hw4/src/CompositeShape.java
Normal file
@@ -0,0 +1,27 @@
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
/**
|
||||
* An interface for shapes that can be composed of multiple primitive shapes
|
||||
* and can be drawn.
|
||||
*/
|
||||
public interface CompositeShape {
|
||||
/**
|
||||
* Draws the shape.
|
||||
* @param g2 the graphics context
|
||||
*/
|
||||
void draw(Graphics2D g2);
|
||||
|
||||
/**
|
||||
* Gets the width of the shape's bounding box.
|
||||
* Used for creating icons of a consistent size.
|
||||
* @return the width
|
||||
*/
|
||||
int getWidth();
|
||||
|
||||
/**
|
||||
* Gets the height of the shape's bounding box.
|
||||
* Used for creating icons of a consistent size.
|
||||
* @return the height
|
||||
*/
|
||||
int getHeight();
|
||||
}
|
||||
68
hw4/src/HouseShape.java
Normal file
68
hw4/src/HouseShape.java
Normal file
@@ -0,0 +1,68 @@
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
/**
|
||||
* A simple house shape composed of a body (rectangle), roof (triangle),
|
||||
* door (rectangle), and window (rectangle).
|
||||
*/
|
||||
public class HouseShape implements CompositeShape {
|
||||
private final int width; // Width of the house body
|
||||
private final int height; // Height of the house body (excluding roof)
|
||||
|
||||
public HouseShape() {
|
||||
this(50, 40); // Default width and body height
|
||||
}
|
||||
|
||||
public HouseShape(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
// Total height = body height + roof height (approx 0.5 * width)
|
||||
return height + (int) (width * 0.5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g2) {
|
||||
// Drawing relative to (0,0) - top-left of bounding box.
|
||||
|
||||
// Body
|
||||
double bodyY = width * 0.5; // Start body below roof peak
|
||||
Rectangle2D.Double body = new Rectangle2D.Double(0, bodyY, width, height);
|
||||
|
||||
// Roof (triangle using Path2D)
|
||||
Path2D.Double roof = new Path2D.Double();
|
||||
roof.moveTo(0, bodyY); // Bottom-left corner of roof
|
||||
roof.lineTo(width / 2.0, 0); // Peak of roof
|
||||
roof.lineTo(width, bodyY); // Bottom-right corner of roof
|
||||
// No need to close path for drawing outline
|
||||
|
||||
// Door (small rectangle)
|
||||
double doorWidth = width * 0.25;
|
||||
double doorHeight = height * 0.5;
|
||||
double doorX = (width - doorWidth) / 2.0; // Centered horizontally
|
||||
double doorY = bodyY + height - doorHeight; // At the bottom of the body
|
||||
Rectangle2D.Double door = new Rectangle2D.Double(doorX, doorY, doorWidth, doorHeight);
|
||||
|
||||
// Window (small square rectangle)
|
||||
double windowSize = width * 0.2;
|
||||
double windowX = width * 0.15; // Positioned left of center
|
||||
double windowY = bodyY + height * 0.2; // Positioned near top of body
|
||||
Rectangle2D.Double window = new Rectangle2D.Double(windowX, windowY, windowSize, windowSize);
|
||||
|
||||
|
||||
// Draw parts
|
||||
g2.draw(body);
|
||||
g2.draw(roof);
|
||||
g2.draw(door);
|
||||
g2.draw(window);
|
||||
}
|
||||
}
|
||||
187
hw4/src/ShapeDisplayer.java
Normal file
187
hw4/src/ShapeDisplayer.java
Normal file
@@ -0,0 +1,187 @@
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ShapeDisplayer extends JFrame {
|
||||
|
||||
private static final int ICON_WIDTH = 50;
|
||||
private static final int ICON_HEIGHT = 50;
|
||||
|
||||
private final DrawingPanel drawingPanel;
|
||||
private CompositeShape currentShape = null; // The shape currently selected
|
||||
|
||||
private final JButton carButton;
|
||||
private final JButton snowmanButton;
|
||||
private final JButton houseButton;
|
||||
private final ShapeIcon carIcon;
|
||||
private final ShapeIcon snowmanIcon;
|
||||
private final ShapeIcon houseIcon;
|
||||
|
||||
// Helper class to store drawn shapes and their positions
|
||||
private static class PlacedShape {
|
||||
final CompositeShape shape;
|
||||
final Point position; // Top-left corner where the shape should be drawn
|
||||
|
||||
PlacedShape(CompositeShape shape, Point position) {
|
||||
this.shape = shape;
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
// Custom JPanel for drawing
|
||||
private class DrawingPanel extends JPanel {
|
||||
private final List<PlacedShape> drawnShapes = new ArrayList<>();
|
||||
|
||||
public DrawingPanel() {
|
||||
setBackground(Color.WHITE);
|
||||
setPreferredSize(new Dimension(600, 400)); // Set preferred size
|
||||
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (currentShape != null) {
|
||||
// Create a *new instance* for drawing, if shapes had state.
|
||||
// For these stateless shapes, reusing is fine, but creating
|
||||
// new instances is conceptually safer if shapes could change.
|
||||
// Example: drawnShapes.add(new PlacedShape(createNewShapeInstance(currentShape), e.getPoint()));
|
||||
|
||||
// Simpler approach for current stateless shapes:
|
||||
drawnShapes.add(new PlacedShape(currentShape, e.getPoint()));
|
||||
repaint(); // Trigger redraw
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setCurrentShape(CompositeShape shape) {
|
||||
currentShape = shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
Graphics2D g2 = (Graphics2D) g.create(); // Work on a copy
|
||||
|
||||
// Enable antialiasing for smoother graphics
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
|
||||
// Draw all the placed shapes
|
||||
for (PlacedShape ps : drawnShapes) {
|
||||
// Save the current transform state
|
||||
AffineTransform savedTransform = g2.getTransform();
|
||||
|
||||
// Translate to the position where the mouse was clicked
|
||||
g2.translate(ps.position.x, ps.position.y);
|
||||
|
||||
// Draw the shape (its draw method assumes drawing at 0,0)
|
||||
ps.shape.draw(g2);
|
||||
|
||||
// Restore the original transform for the next shape
|
||||
g2.setTransform(savedTransform);
|
||||
}
|
||||
|
||||
g2.dispose(); // Clean up the graphics copy
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ShapeDisplayer() {
|
||||
super("Shape Displayer"); // Frame title
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
|
||||
// --- Create Shapes ---
|
||||
CompositeShape car = new CarShape();
|
||||
CompositeShape snowman = new SnowmanShape();
|
||||
CompositeShape house = new HouseShape(); // Your custom shape
|
||||
|
||||
// --- Create Icons ---
|
||||
carIcon = new ShapeIcon(car, ICON_WIDTH, ICON_HEIGHT);
|
||||
snowmanIcon = new ShapeIcon(snowman, ICON_WIDTH, ICON_HEIGHT);
|
||||
houseIcon = new ShapeIcon(house, ICON_WIDTH, ICON_HEIGHT);
|
||||
|
||||
// --- Create Buttons ---
|
||||
carButton = new JButton(carIcon);
|
||||
snowmanButton = new JButton(snowmanIcon);
|
||||
houseButton = new JButton(houseIcon);
|
||||
|
||||
// Configure buttons (remove text, set margins if needed)
|
||||
carButton.setMargin(new Insets(2, 2, 2, 2));
|
||||
snowmanButton.setMargin(new Insets(2, 2, 2, 2));
|
||||
houseButton.setMargin(new Insets(2, 2, 2, 2));
|
||||
|
||||
// --- Create Panels ---
|
||||
drawingPanel = new DrawingPanel();
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); // Align buttons left
|
||||
buttonPanel.add(carButton);
|
||||
buttonPanel.add(snowmanButton);
|
||||
buttonPanel.add(houseButton);
|
||||
|
||||
// --- Layout ---
|
||||
setLayout(new BorderLayout());
|
||||
add(buttonPanel, BorderLayout.NORTH);
|
||||
add(drawingPanel, BorderLayout.CENTER);
|
||||
|
||||
// --- Add Action Listeners ---
|
||||
carButton.addActionListener(e -> selectShape(car, carIcon));
|
||||
snowmanButton.addActionListener(e -> selectShape(snowman, snowmanIcon));
|
||||
houseButton.addActionListener(e -> selectShape(house, houseIcon));
|
||||
|
||||
|
||||
// Initially, no shape is selected
|
||||
deselectAllIcons();
|
||||
|
||||
pack(); // Size the frame based on components
|
||||
setLocationRelativeTo(null); // Center on screen
|
||||
}
|
||||
|
||||
// Method to handle shape selection
|
||||
private void selectShape(CompositeShape shape, ShapeIcon icon) {
|
||||
drawingPanel.setCurrentShape(shape);
|
||||
deselectAllIcons(); // Clear previous selection outline
|
||||
icon.setOutline(true); // Set outline on the selected icon
|
||||
// Force repaint of the button containing the icon
|
||||
if (icon == carIcon) carButton.repaint();
|
||||
else if (icon == snowmanIcon) snowmanButton.repaint();
|
||||
else if (icon == houseIcon) houseButton.repaint();
|
||||
|
||||
// Optional: Add a border to the button itself for clearer selection
|
||||
// resetBorders(); // Reset borders on all buttons
|
||||
// if (icon == carIcon) carButton.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2));
|
||||
// else if (icon == snowManIcon) snowManButton.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2));
|
||||
// else if (icon == houseIcon) houseButton.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2));
|
||||
|
||||
}
|
||||
|
||||
// Method to remove outline from all icons
|
||||
private void deselectAllIcons() {
|
||||
carIcon.setOutline(false);
|
||||
snowmanIcon.setOutline(false);
|
||||
houseIcon.setOutline(false);
|
||||
// Repaint all buttons to remove outlines
|
||||
carButton.repaint();
|
||||
snowmanButton.repaint();
|
||||
houseButton.repaint();
|
||||
}
|
||||
|
||||
// Optional: Helper to reset button borders
|
||||
// private void resetBorders() {
|
||||
// Border defaultBorder = UIManager.getBorder("Button.border");
|
||||
// carButton.setBorder(defaultBorder);
|
||||
// snowManButton.setBorder(defaultBorder);
|
||||
// houseButton.setBorder(defaultBorder);
|
||||
// }
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Run the GUI creation on the Event Dispatch Thread (EDT)
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
ShapeDisplayer displayer = new ShapeDisplayer();
|
||||
displayer.setVisible(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
90
hw4/src/ShapeIcon.java
Normal file
90
hw4/src/ShapeIcon.java
Normal file
@@ -0,0 +1,90 @@
|
||||
import javax.swing.Icon;
|
||||
import java.awt.Component;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.AffineTransform;
|
||||
|
||||
/**
|
||||
* An icon that draws a CompositeShape.
|
||||
*/
|
||||
public class ShapeIcon implements Icon {
|
||||
private final CompositeShape shape;
|
||||
private final int width;
|
||||
private final int height;
|
||||
private boolean outline = false; // Flag to indicate if the icon should be outlined
|
||||
|
||||
/**
|
||||
* Constructs a ShapeIcon.
|
||||
* @param shape the shape to draw
|
||||
* @param width the desired width of the icon
|
||||
* @param height the desired height of the icon
|
||||
*/
|
||||
public ShapeIcon(CompositeShape shape, int width, int height) {
|
||||
this.shape = shape;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this icon should draw an outline (e.g., when selected).
|
||||
* @param outline true to draw outline, false otherwise.
|
||||
*/
|
||||
public void setOutline(boolean outline) {
|
||||
this.outline = outline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||
Graphics2D g2 = (Graphics2D) g.create(); // Work on a copy
|
||||
|
||||
// Enable antialiasing for smoother graphics
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
// Translate to the icon's drawing position
|
||||
g2.translate(x, y);
|
||||
|
||||
double shapeWidth = shape.getWidth();
|
||||
double shapeHeight = shape.getHeight();
|
||||
|
||||
// Calculate scaling factor to fit shape within icon bounds
|
||||
double scaleX = (shapeWidth == 0) ? 1 : (double) width / shapeWidth;
|
||||
double scaleY = (shapeHeight == 0) ? 1 : (double) height / shapeHeight;
|
||||
double scale = Math.min(scaleX, scaleY) * 0.8; // Use 80% of space for padding
|
||||
|
||||
// Calculate translation to center the scaled shape
|
||||
double dx = (width - shapeWidth * scale) / 2.0;
|
||||
double dy = (height - shapeHeight * scale) / 2.0;
|
||||
|
||||
// Apply centering translation and scaling
|
||||
AffineTransform originalTransform = g2.getTransform();
|
||||
g2.translate(dx, dy);
|
||||
g2.scale(scale, scale);
|
||||
|
||||
// Draw the shape
|
||||
shape.draw(g2);
|
||||
|
||||
// Restore original transform before drawing outline (if any)
|
||||
g2.setTransform(originalTransform);
|
||||
|
||||
// Draw outline if selected
|
||||
if (outline) {
|
||||
// Simple rectangle outline around the icon area
|
||||
g2.setColor(java.awt.Color.GRAY);
|
||||
g2.drawRect(0, 0, width - 1, height - 1);
|
||||
g2.drawRect(1, 1, width - 3, height - 3); // Inner outline
|
||||
}
|
||||
|
||||
g2.dispose(); // Clean up the graphics copy
|
||||
}
|
||||
}
|
||||
60
hw4/src/SnowmanShape.java
Normal file
60
hw4/src/SnowmanShape.java
Normal file
@@ -0,0 +1,60 @@
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
|
||||
/**
|
||||
* A snowman shape composed of three circles.
|
||||
*/
|
||||
public class SnowmanShape implements CompositeShape {
|
||||
private final int width; // Base diameter of the bottom circle
|
||||
|
||||
public SnowmanShape() {
|
||||
this(40); // Default width
|
||||
}
|
||||
|
||||
public SnowmanShape(int width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
// Total height is approx sum of diameters: w + 0.75w + 0.5w
|
||||
return (int) (width + width * 0.75 + width * 0.5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g2) {
|
||||
// All drawing relative to (0,0) which represents the top-left
|
||||
// of the bounding box for the *icon*. The actual drawing will be centered.
|
||||
|
||||
// Calculate diameters
|
||||
double bottomDiameter = width;
|
||||
double middleDiameter = width * 0.75;
|
||||
double topDiameter = width * 0.5;
|
||||
|
||||
// Calculate positions (y-coordinates are offset from the top)
|
||||
double bottomY = topDiameter + middleDiameter;
|
||||
double middleY = topDiameter;
|
||||
double topY = 0;
|
||||
|
||||
// Center horizontally (x-coordinates)
|
||||
double bottomX = (width - bottomDiameter) / 2.0;
|
||||
double middleX = (width - middleDiameter) / 2.0;
|
||||
double topX = (width - topDiameter) / 2.0;
|
||||
|
||||
|
||||
// Create circles
|
||||
Ellipse2D.Double bottomCircle = new Ellipse2D.Double(bottomX, bottomY, bottomDiameter, bottomDiameter);
|
||||
Ellipse2D.Double middleCircle = new Ellipse2D.Double(middleX, middleY, middleDiameter, middleDiameter);
|
||||
Ellipse2D.Double topCircle = new Ellipse2D.Double(topX, topY, topDiameter, topDiameter);
|
||||
|
||||
// Draw circles
|
||||
g2.draw(bottomCircle);
|
||||
g2.draw(middleCircle);
|
||||
g2.draw(topCircle);
|
||||
}
|
||||
}
|
||||
29
midterm1/.gitignore
vendored
Normal file
29
midterm1/.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
### IntelliJ IDEA ###
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
8
midterm1/.idea/.gitignore
generated
vendored
Normal file
8
midterm1/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
6
midterm1/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
midterm1/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
6
midterm1/.idea/misc.xml
generated
Normal file
6
midterm1/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-22" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
8
midterm1/.idea/modules.xml
generated
Normal file
8
midterm1/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/midterm1.iml" filepath="$PROJECT_DIR$/midterm1.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
midterm1/.idea/vcs.xml
generated
Normal file
6
midterm1/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
11
midterm1/midterm1.iml
Normal file
11
midterm1/midterm1.iml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
30
midterm1/src/Animation.java
Normal file
30
midterm1/src/Animation.java
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Animation Program that draws and moves a simple shape (e.g. butterfly, kite, etc.) on a timer event.
|
||||
*/
|
||||
public class Animation {
|
||||
public static void main(String[] args) {
|
||||
javax.swing.SwingUtilities.invokeLater(() -> {
|
||||
javax.swing.JFrame frame = new javax.swing.JFrame("Animation");
|
||||
frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
|
||||
frame.setSize(400, 400);
|
||||
|
||||
javax.swing.JPanel panel = new javax.swing.JPanel() {
|
||||
@Override
|
||||
protected void paintComponent(java.awt.Graphics g) {
|
||||
super.paintComponent(g);
|
||||
g.setColor(java.awt.Color.RED);
|
||||
g.fillOval(getX(), getY(), 30, 30);
|
||||
}
|
||||
};
|
||||
frame.add(panel);
|
||||
|
||||
javax.swing.Timer timer = new javax.swing.Timer(100, (e) -> {
|
||||
panel.setLocation((panel.getX() + 1) % 20, (panel.getY() + 1) % 20);
|
||||
panel.repaint();
|
||||
});
|
||||
timer.start();
|
||||
|
||||
frame.setVisible(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
58
midterm1/src/C4Q14.java
Normal file
58
midterm1/src/C4Q14.java
Normal file
@@ -0,0 +1,58 @@
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
/**
|
||||
* Write a program that shows a frame with three buttons labeled “Red”,
|
||||
* “Green”, and “Blue”, and a label containing an icon showing a circle that is
|
||||
* initially red. As the user clicks the buttons, the fill color of the circle should
|
||||
* change. When you change the color, you need to invoke the repaint
|
||||
* method on the label. The call to repaint ensures that the paintIcon method
|
||||
* is called so that the icon can be repainted with the new color.
|
||||
*/
|
||||
public class C4Q14 {
|
||||
public static void main(String[] args) {
|
||||
javax.swing.SwingUtilities.invokeLater(() -> {
|
||||
javax.swing.JFrame frame = new javax.swing.JFrame("C4Q14");
|
||||
frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
|
||||
frame.setSize(400, 400);
|
||||
// frame.setLayout(new java.awt.BorderLayout());
|
||||
|
||||
javax.swing.JLabel label = new javax.swing.JLabel(new CircleIcon(java.awt.Color.RED, 300));
|
||||
frame.add(label, java.awt.BorderLayout.CENTER);
|
||||
|
||||
javax.swing.JPanel panel = new javax.swing.JPanel();
|
||||
panel.setLayout(new java.awt.FlowLayout());
|
||||
|
||||
javax.swing.JButton redButton = new javax.swing.JButton("Red");
|
||||
redButton.addActionListener((e) -> {
|
||||
label.setIcon(new CircleIcon(java.awt.Color.RED, 300));
|
||||
label.repaint();
|
||||
});
|
||||
panel.add(redButton);
|
||||
|
||||
javax.swing.JButton greenButton = new javax.swing.JButton("Green");
|
||||
greenButton.addActionListener((e) -> {
|
||||
label.setIcon(new CircleIcon(java.awt.Color.GREEN, 300));
|
||||
label.repaint();
|
||||
});
|
||||
greenButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
label.setIcon(new CircleIcon(java.awt.Color.GREEN, 300));
|
||||
label.repaint();
|
||||
}
|
||||
});
|
||||
panel.add(greenButton);
|
||||
|
||||
javax.swing.JButton blueButton = new javax.swing.JButton("Blue");
|
||||
blueButton.addActionListener((e) -> {
|
||||
label.setIcon(new CircleIcon(java.awt.Color.BLUE, 300));
|
||||
label.repaint();
|
||||
});
|
||||
panel.add(blueButton);
|
||||
|
||||
// frame.add(panel, java.awt.BorderLayout.SOUTH);
|
||||
frame.setVisible(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
29
midterm1/src/C4Q6.java
Normal file
29
midterm1/src/C4Q6.java
Normal file
@@ -0,0 +1,29 @@
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
|
||||
public class C4Q6 {
|
||||
public static void main(String[] args) {
|
||||
ArrayList<String> a = new ArrayList<>(10);
|
||||
a.add("a");
|
||||
a.add("aa");
|
||||
a.add("aaa");
|
||||
a.add("aaaaa");
|
||||
a.add("aaaa");
|
||||
|
||||
String max = maximum(a, (s1, s2) -> {
|
||||
return Integer.compare(s1.length(), s2.length());
|
||||
});
|
||||
|
||||
System.out.println(max);
|
||||
}
|
||||
|
||||
public static String maximum(ArrayList<String> a, Comparator<String> c) {
|
||||
String max = a.getFirst();
|
||||
for (String s : a) {
|
||||
if (c.compare(s, max) > 0) {
|
||||
max = s;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
}
|
||||
21
midterm1/src/C4Q9.java
Normal file
21
midterm1/src/C4Q9.java
Normal file
@@ -0,0 +1,21 @@
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class C4Q9 {
|
||||
public static void main(String[] args) {
|
||||
String[] a = {"a", "aa", "aaa", "aaaa", "aaaaa"};
|
||||
String[] res = filter(a, (s) -> {
|
||||
return s.length() <= 3;
|
||||
});
|
||||
|
||||
for (String s : res) {
|
||||
System.out.println(s);
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] filter(String[] a, Filter f) {
|
||||
return Arrays.stream(a).filter((s) -> {
|
||||
return f.accept(s);
|
||||
}).toArray(String[]::new);
|
||||
}
|
||||
}
|
||||
29
midterm1/src/CircleIcon.java
Normal file
29
midterm1/src/CircleIcon.java
Normal file
@@ -0,0 +1,29 @@
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class CircleIcon implements Icon {
|
||||
private Color color;
|
||||
private int size;
|
||||
|
||||
public CircleIcon(Color color, int size) {
|
||||
this.color = color;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||
g.setColor(color);
|
||||
g.fillOval(x, y, size, size);
|
||||
}
|
||||
|
||||
public int getIconWidth() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public int getIconHeight() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setColor(Color color) {
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
3
midterm1/src/Filter.java
Normal file
3
midterm1/src/Filter.java
Normal file
@@ -0,0 +1,3 @@
|
||||
public interface Filter {
|
||||
boolean accept(String x);
|
||||
}
|
||||
24
midterm1/src/MidtermQ1.java
Normal file
24
midterm1/src/MidtermQ1.java
Normal file
@@ -0,0 +1,24 @@
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class MidtermQ1 {
|
||||
public static void main(String[] args) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class CirclePanel extends JPanel {
|
||||
private Color color;
|
||||
|
||||
public void paintComponent(Graphics g) {
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
g2.setColor(color);
|
||||
g2.draw(new Ellipse2D.Double(0, 0, 100, 100));
|
||||
|
||||
}
|
||||
}
|
||||
29
midterm2/.gitignore
vendored
Normal file
29
midterm2/.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
### IntelliJ IDEA ###
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
8
midterm2/.idea/.gitignore
generated
vendored
Normal file
8
midterm2/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
6
midterm2/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
midterm2/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
6
midterm2/.idea/misc.xml
generated
Normal file
6
midterm2/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-22" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
8
midterm2/.idea/modules.xml
generated
Normal file
8
midterm2/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/midterm2.iml" filepath="$PROJECT_DIR$/midterm2.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
midterm2/.idea/vcs.xml
generated
Normal file
6
midterm2/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
11
midterm2/midterm2.iml
Normal file
11
midterm2/midterm2.iml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
96
midterm2/src/BarFrame.java
Normal file
96
midterm2/src/BarFrame.java
Normal file
@@ -0,0 +1,96 @@
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.geom.*;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A class that implements an Observer object that displays a barchart view of
|
||||
* a data model.
|
||||
*/
|
||||
public class BarFrame extends JFrame implements ChangeListener {
|
||||
/**
|
||||
* Constructs a BarFrame object
|
||||
*
|
||||
* @param dataModel the data that is displayed in the barchart
|
||||
*/
|
||||
public BarFrame(DataModel dataModel) {
|
||||
this.dataModel = dataModel;
|
||||
a = dataModel.getData();
|
||||
|
||||
setLocation(0, 200);
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
JLabel barLabel = new JLabel();
|
||||
barLabel.setIcon(new BarIcon());
|
||||
barLabel.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
int mouseX = e.getX();
|
||||
int mouseY = e.getY();
|
||||
|
||||
double max = a.stream().max(Double::compare).orElse(1.0);
|
||||
double barHeight = ICON_HEIGHT / (double) a.size();
|
||||
|
||||
// Find the nearest bar to the mouse click
|
||||
int index = (int) (mouseY / barHeight);
|
||||
if (index >= 0 && index < a.size()) {
|
||||
double newValue = max * mouseX / ICON_WIDTH;
|
||||
dataModel.update(index, newValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add(barLabel);
|
||||
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
pack();
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the data in the model is changed.
|
||||
*
|
||||
* @param e the event representing the change
|
||||
*/
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
a = dataModel.getData();
|
||||
repaint();
|
||||
}
|
||||
|
||||
private class BarIcon implements Icon {
|
||||
public int getIconWidth() {
|
||||
return ICON_WIDTH;
|
||||
}
|
||||
|
||||
public int getIconHeight() {
|
||||
return ICON_HEIGHT;
|
||||
}
|
||||
|
||||
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
|
||||
g2.setColor(Color.red);
|
||||
|
||||
double max = a.stream().max(Double::compare).orElse(1.0);
|
||||
double barHeight = getIconHeight() / (double) a.size();
|
||||
|
||||
int i = 0;
|
||||
for (Double value : a) {
|
||||
double barLength = getIconWidth() * value / max;
|
||||
|
||||
Rectangle2D.Double rectangle = new Rectangle2D.Double(
|
||||
0, barHeight * i, barLength, barHeight);
|
||||
i++;
|
||||
g2.fill(rectangle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList<Double> a;
|
||||
private DataModel dataModel;
|
||||
|
||||
private static final int ICON_WIDTH = 200;
|
||||
private static final int ICON_HEIGHT = 200;
|
||||
}
|
||||
51
midterm2/src/DataModel.java
Normal file
51
midterm2/src/DataModel.java
Normal file
@@ -0,0 +1,51 @@
|
||||
import java.util.ArrayList;
|
||||
import javax.swing.event.*;
|
||||
|
||||
/**
|
||||
* A Subject class for the observer pattern.
|
||||
*/
|
||||
public class DataModel {
|
||||
/**
|
||||
* Constructs a DataModel object
|
||||
*
|
||||
* @param d the data to model
|
||||
*/
|
||||
public DataModel(ArrayList<Double> d) {
|
||||
data = d;
|
||||
listeners = new ArrayList<ChangeListener>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a DataModel object
|
||||
*
|
||||
* @return the data in an ArrayList
|
||||
*/
|
||||
public ArrayList<Double> getData() {
|
||||
return (ArrayList<Double>) (data.clone());
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a listener to the Model
|
||||
*
|
||||
* @param c the listener
|
||||
*/
|
||||
public void attach(ChangeListener c) {
|
||||
listeners.add(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the data in the model at a particular location
|
||||
*
|
||||
* @param location the index of the field to change
|
||||
* @param value the new value
|
||||
*/
|
||||
public void update(int location, double value) {
|
||||
data.set(location, new Double(value));
|
||||
for (ChangeListener l : listeners) {
|
||||
l.stateChanged(new ChangeEvent(this));
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<Double> data;
|
||||
ArrayList<ChangeListener> listeners;
|
||||
}
|
||||
14
midterm2/src/Main.java
Normal file
14
midterm2/src/Main.java
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Write a program that contains two frames, one with a column of text fields
|
||||
* containing numbers, and another that draws a bar graph showing the values of
|
||||
* the numbers. When the user edits one of the numbers, the graph should be
|
||||
* redrawn. Use the observer pattern. Store the data in a model. Attach the graph
|
||||
* view as a listener. When a number is updated, the number view should update the
|
||||
* model, and the model should tell the graph view that a change has occured. As a
|
||||
* result, the graph view should repaint itself.
|
||||
*/
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
|
||||
}
|
||||
}
|
||||
29
midterm2/src/ObserverTester.java
Normal file
29
midterm2/src/ObserverTester.java
Normal file
@@ -0,0 +1,29 @@
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A class for testing an implementation of the Observer pattern.
|
||||
*/
|
||||
public class ObserverTester {
|
||||
/**
|
||||
* Creates a DataModel and attaches barchart and textfield listeners
|
||||
*
|
||||
* @param args unused
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
ArrayList<Double> data = new ArrayList<Double>();
|
||||
|
||||
data.add(33.0);
|
||||
data.add(44.0);
|
||||
data.add(22.0);
|
||||
data.add(22.0);
|
||||
|
||||
DataModel model = new DataModel(data);
|
||||
|
||||
TextFrame frame = new TextFrame(model);
|
||||
|
||||
BarFrame barFrame = new BarFrame(model);
|
||||
|
||||
model.attach(frame);
|
||||
model.attach(barFrame);
|
||||
}
|
||||
}
|
||||
76
midterm2/src/TextFrame.java
Normal file
76
midterm2/src/TextFrame.java
Normal file
@@ -0,0 +1,76 @@
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A class for displaying the model as a column of textfields in a frame.
|
||||
*/
|
||||
public class TextFrame extends JFrame implements ChangeListener {
|
||||
/**
|
||||
* Constructs a JFrame that contains the textfields containing the data
|
||||
* in the model.
|
||||
*
|
||||
* @param d the model to display
|
||||
*/
|
||||
public TextFrame(DataModel d) {
|
||||
dataModel = d;
|
||||
|
||||
final Container contentPane = this.getContentPane();
|
||||
setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
|
||||
|
||||
ArrayList<Double> a = dataModel.getData();
|
||||
fieldList = new JTextField[a.size()];
|
||||
|
||||
// A listener for action events in the text fields
|
||||
ActionListener l = new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
// Figure out which field generated the event
|
||||
JTextField c = (JTextField) e.getSource();
|
||||
int i = 0;
|
||||
int count = fieldList.length;
|
||||
while (i < count && fieldList[i] != c)
|
||||
i++;
|
||||
|
||||
String text = c.getText().trim();
|
||||
|
||||
try {
|
||||
double value = Double.parseDouble(text);
|
||||
dataModel.update(i, value);
|
||||
} catch (Exception exc) {
|
||||
c.setText("Error. No update");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final int FIELD_WIDTH = 11;
|
||||
for (int i = 0; i < a.size(); i++) {
|
||||
JTextField textField = new JTextField(a.get(i).toString(), FIELD_WIDTH);
|
||||
textField.addActionListener(l);
|
||||
add(textField);
|
||||
fieldList[i] = textField;
|
||||
}
|
||||
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
pack();
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the data in the model is changed.
|
||||
*
|
||||
* @param e the event representing the change
|
||||
*/
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
ArrayList<Double> a = dataModel.getData();
|
||||
for (int i = 0; i < a.size(); i++) {
|
||||
fieldList[i].setText(a.get(i).toString());
|
||||
}
|
||||
}
|
||||
|
||||
DataModel dataModel;
|
||||
JTextField[] fieldList;
|
||||
}
|
||||
Reference in New Issue
Block a user