Compare commits
8 Commits
c10ba4b2fc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
f6d205cd29
|
|||
|
573498ddb4
|
|||
|
857802227d
|
|||
|
34a71d5ef7
|
|||
|
1be8ca6bf4
|
|||
|
8dd6533d47
|
|||
|
19141831c8
|
|||
|
f098922149
|
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
|
||||||
@@ -1,8 +1,16 @@
|
|||||||
import controller.PhotoAlbumController;
|
import controller.PhotoAlbumController;
|
||||||
import model.PhotoAlbumModel;
|
import model.PhotoAlbumModel;
|
||||||
import strategy.SortByDate;
|
|
||||||
import view.PhotoAlbumView;
|
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 class PhotoAlbumApp {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
// Create MVC components
|
// Create MVC components
|
||||||
|
|||||||
@@ -12,22 +12,55 @@ import javax.swing.filechooser.FileNameExtensionFilter;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Date;
|
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 {
|
public class PhotoAlbumController {
|
||||||
private final PhotoAlbumModel model;
|
private final PhotoAlbumModel model;
|
||||||
private final PhotoAlbumView view;
|
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) {
|
public PhotoAlbumController(PhotoAlbumModel model, PhotoAlbumView view) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.view = view;
|
this.view = view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the photo album model being controlled.
|
||||||
|
*
|
||||||
|
* @return the photo album model
|
||||||
|
*/
|
||||||
public PhotoAlbumModel getModel() {
|
public PhotoAlbumModel getModel() {
|
||||||
return model;
|
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() {
|
public void handleAddPhoto() {
|
||||||
JFileChooser fileChooser = new JFileChooser();
|
JFileChooser fileChooser = new JFileChooser();
|
||||||
fileChooser.setFileFilter(new FileNameExtensionFilter("Image files", "jpg", "jpeg", "png", "gif"));
|
fileChooser.setFileFilter(new FileNameExtensionFilter(
|
||||||
|
"Image files",
|
||||||
|
"jpg", "jpeg", "png", "gif"
|
||||||
|
));
|
||||||
|
|
||||||
if (fileChooser.showOpenDialog(view) == JFileChooser.APPROVE_OPTION) {
|
if (fileChooser.showOpenDialog(view) == JFileChooser.APPROVE_OPTION) {
|
||||||
File file = fileChooser.getSelectedFile();
|
File file = fileChooser.getSelectedFile();
|
||||||
@@ -45,6 +78,10 @@ public class PhotoAlbumController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the deletion of a photo from the album.
|
||||||
|
* Prompts for the name of the photo to delete.
|
||||||
|
*/
|
||||||
public void handleDeletePhoto() {
|
public void handleDeletePhoto() {
|
||||||
String name = JOptionPane.showInputDialog(view, "Enter photo name to delete:");
|
String name = JOptionPane.showInputDialog(view, "Enter photo name to delete:");
|
||||||
if (name != null && !name.trim().isEmpty()) {
|
if (name != null && !name.trim().isEmpty()) {
|
||||||
@@ -52,18 +89,36 @@ public class PhotoAlbumController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles navigation to the next photo in the album.
|
||||||
|
* Moves to the next photo if one exists.
|
||||||
|
*/
|
||||||
public void handleNext() {
|
public void handleNext() {
|
||||||
if (model.hasNext()) {
|
if (model.hasNext()) {
|
||||||
model.next();
|
model.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles navigation to the previous photo in the album.
|
||||||
|
* Moves to the previous photo if one exists.
|
||||||
|
*/
|
||||||
public void handlePrevious() {
|
public void handlePrevious() {
|
||||||
if (model.hasPrevious()) {
|
if (model.hasPrevious()) {
|
||||||
model.previous();
|
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) {
|
public void handleSort(int strategy) {
|
||||||
switch (strategy) {
|
switch (strategy) {
|
||||||
case 0:
|
case 0:
|
||||||
|
|||||||
@@ -4,10 +4,32 @@ import model.Photo;
|
|||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for album iterators.
|
||||||
|
*/
|
||||||
public interface AlbumIterator extends Iterator<Photo> {
|
public interface AlbumIterator extends Iterator<Photo> {
|
||||||
boolean hasNext(); // to check if there is a next element
|
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
|
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 current(); // to get the photo at the current position
|
||||||
|
|
||||||
Photo next(); // to advance the iterator to the next 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
|
Photo previous(); // to advance the iterator to the previous position
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,37 @@
|
|||||||
package iterator;
|
package iterator;
|
||||||
|
|
||||||
import model.Photo;
|
import model.Photo;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.NoSuchElementException;
|
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 {
|
public class PhotoIterator implements AlbumIterator {
|
||||||
private final List<Photo> photos;
|
private final List<Photo> photos;
|
||||||
private int currentPosition;
|
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) {
|
public PhotoIterator(List<Photo> photos) {
|
||||||
this.photos = photos;
|
this.photos = photos;
|
||||||
this.currentPosition = 0;
|
this.currentPosition = 0;
|
||||||
@@ -31,6 +55,9 @@ public class PhotoIterator implements AlbumIterator {
|
|||||||
return photos.get(currentPosition);
|
return photos.get(currentPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws NoSuchElementException if there is no next photo
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Photo next() {
|
public Photo next() {
|
||||||
if (!hasNext()) {
|
if (!hasNext()) {
|
||||||
@@ -39,6 +66,9 @@ public class PhotoIterator implements AlbumIterator {
|
|||||||
return photos.get(++currentPosition);
|
return photos.get(++currentPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws NoSuchElementException if there is no previous photo
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Photo previous() {
|
public Photo previous() {
|
||||||
if (!hasPrevious()) {
|
if (!hasPrevious()) {
|
||||||
|
|||||||
@@ -2,5 +2,21 @@ package model;
|
|||||||
|
|
||||||
import java.util.Date;
|
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) {
|
public record Photo(String name, String filePath, Date dateAdded, long fileSize) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,40 @@ package model;
|
|||||||
import strategy.SortingStrategy;
|
import strategy.SortingStrategy;
|
||||||
import iterator.AlbumIterator;
|
import iterator.AlbumIterator;
|
||||||
import iterator.PhotoIterator;
|
import iterator.PhotoIterator;
|
||||||
|
|
||||||
import java.util.*;
|
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 {
|
public class PhotoAlbumModel {
|
||||||
private List<Photo> photos;
|
private List<Photo> photos;
|
||||||
private SortingStrategy<Photo> sortingStrategy;
|
private SortingStrategy<Photo> sortingStrategy;
|
||||||
private final List<ModelChangeListener> listeners;
|
private final List<ModelChangeListener> listeners;
|
||||||
private AlbumIterator iterator;
|
private AlbumIterator iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener interface for model changes.
|
||||||
|
* The method {@link #onModelChanged()} is called when the model changes.
|
||||||
|
*/
|
||||||
public interface ModelChangeListener {
|
public interface ModelChangeListener {
|
||||||
|
/**
|
||||||
|
* Called when the model changes.
|
||||||
|
*/
|
||||||
void onModelChanged();
|
void onModelChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,24 +46,40 @@ public class PhotoAlbumModel {
|
|||||||
iterator = new PhotoIterator(photos);
|
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) {
|
public void addPhoto(Photo photo) {
|
||||||
photos.add(photo);
|
photos.add(photo);
|
||||||
sortPhotos();
|
sortPhotos();
|
||||||
iterator = new PhotoIterator(photos);
|
|
||||||
notifyListeners();
|
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) {
|
public void deletePhoto(String name) {
|
||||||
Photo currentPhoto = iterator.current();
|
Photo currentPhoto = iterator.current();
|
||||||
photos.removeIf(photo -> photo.name().equals(name));
|
photos.removeIf(photo -> photo.name().equals(name));
|
||||||
if (photos.isEmpty()) {
|
if (photos.isEmpty() || (currentPhoto != null && currentPhoto.name().equals(name))) {
|
||||||
iterator = new PhotoIterator(photos);
|
|
||||||
} else if (currentPhoto != null && currentPhoto.name().equals(name)) {
|
|
||||||
iterator = new PhotoIterator(photos);
|
iterator = new PhotoIterator(photos);
|
||||||
}
|
}
|
||||||
notifyListeners();
|
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) {
|
public void setSortingStrategy(SortingStrategy<Photo> strategy) {
|
||||||
this.sortingStrategy = strategy;
|
this.sortingStrategy = strategy;
|
||||||
sortPhotos();
|
sortPhotos();
|
||||||
@@ -53,20 +94,39 @@ public class PhotoAlbumModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a listener for model changes.
|
||||||
|
*
|
||||||
|
* @param listener the listener to add
|
||||||
|
*/
|
||||||
public void addListener(ModelChangeListener listener) {
|
public void addListener(ModelChangeListener listener) {
|
||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies all listeners that the model has changed,
|
||||||
|
* calling the {@link ModelChangeListener#onModelChanged()} method.
|
||||||
|
*/
|
||||||
private void notifyListeners() {
|
private void notifyListeners() {
|
||||||
for (ModelChangeListener listener : listeners) {
|
for (ModelChangeListener listener : listeners) {
|
||||||
listener.onModelChanged();
|
listener.onModelChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an unmodifiable list of photos in the album.
|
||||||
|
*
|
||||||
|
* @return an unmodifiable list of photos
|
||||||
|
*/
|
||||||
public List<Photo> getPhotos() {
|
public List<Photo> getPhotos() {
|
||||||
return Collections.unmodifiableList(photos);
|
return Collections.unmodifiableList(photos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the photo in the album that the iterator is currently pointing to.
|
||||||
|
*
|
||||||
|
* @return the current photo
|
||||||
|
*/
|
||||||
public Photo getCurrentPhoto() {
|
public Photo getCurrentPhoto() {
|
||||||
try {
|
try {
|
||||||
return iterator.current();
|
return iterator.current();
|
||||||
@@ -75,14 +135,30 @@ public class PhotoAlbumModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
public boolean hasNext() {
|
||||||
return iterator.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() {
|
public boolean hasPrevious() {
|
||||||
return iterator.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() {
|
public Photo next() {
|
||||||
try {
|
try {
|
||||||
Photo next = iterator.next();
|
Photo next = iterator.next();
|
||||||
@@ -93,6 +169,12 @@ public class PhotoAlbumModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
public Photo previous() {
|
||||||
try {
|
try {
|
||||||
Photo prev = iterator.previous();
|
Photo prev = iterator.previous();
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import model.Photo;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sorting strategy that sorts photos by date.
|
||||||
|
*/
|
||||||
public class SortByDate implements SortingStrategy<Photo> {
|
public class SortByDate implements SortingStrategy<Photo> {
|
||||||
@Override
|
@Override
|
||||||
public List<Photo> sort(List<Photo> photos) {
|
public List<Photo> sort(List<Photo> photos) {
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import model.Photo;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sorting strategy that sorts photos by name.
|
||||||
|
*/
|
||||||
public class SortByName implements SortingStrategy<Photo> {
|
public class SortByName implements SortingStrategy<Photo> {
|
||||||
@Override
|
@Override
|
||||||
public List<Photo> sort(List<Photo> photos) {
|
public List<Photo> sort(List<Photo> photos) {
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import model.Photo;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sorting strategy that sorts photos by size.
|
||||||
|
*/
|
||||||
public class SortBySize implements SortingStrategy<Photo> {
|
public class SortBySize implements SortingStrategy<Photo> {
|
||||||
@Override
|
@Override
|
||||||
public List<Photo> sort(List<Photo> photos) {
|
public List<Photo> sort(List<Photo> photos) {
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ package strategy;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for sorting strategies.
|
||||||
|
*
|
||||||
|
* @param <T> the type of elements to be sorted
|
||||||
|
*/
|
||||||
public interface SortingStrategy<T> {
|
public interface SortingStrategy<T> {
|
||||||
List<T> sort(List<T> photos);
|
List<T> sort(List<T> photos);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,29 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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 {
|
public class PhotoAlbumView extends JFrame implements PhotoAlbumModel.ModelChangeListener {
|
||||||
private PhotoAlbumModel model;
|
private PhotoAlbumModel model;
|
||||||
|
|
||||||
@@ -27,6 +50,12 @@ public class PhotoAlbumView extends JFrame implements PhotoAlbumModel.ModelChang
|
|||||||
setupLayout();
|
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() {
|
private void initializeComponents() {
|
||||||
setTitle("Photo Album Manager");
|
setTitle("Photo Album Manager");
|
||||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
@@ -47,8 +76,6 @@ public class PhotoAlbumView extends JFrame implements PhotoAlbumModel.ModelChang
|
|||||||
|
|
||||||
String[] sortOptions = {"Sort by Name", "Sort by Date", "Sort by Size"};
|
String[] sortOptions = {"Sort by Name", "Sort by Date", "Sort by Size"};
|
||||||
sortingCombo = new JComboBox<>(sortOptions);
|
sortingCombo = new JComboBox<>(sortOptions);
|
||||||
// Select default sorting option
|
|
||||||
sortingCombo.setSelectedIndex(1);
|
|
||||||
|
|
||||||
listModel = new DefaultListModel<>();
|
listModel = new DefaultListModel<>();
|
||||||
photoList = new JList<>(listModel);
|
photoList = new JList<>(listModel);
|
||||||
@@ -57,6 +84,17 @@ public class PhotoAlbumView extends JFrame implements PhotoAlbumModel.ModelChang
|
|||||||
photoList.setFixedCellHeight(60); // Accommodate thumbnails
|
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() {
|
private void setupLayout() {
|
||||||
setLayout(new BorderLayout());
|
setLayout(new BorderLayout());
|
||||||
|
|
||||||
@@ -80,6 +118,14 @@ public class PhotoAlbumView extends JFrame implements PhotoAlbumModel.ModelChang
|
|||||||
add(controlPanel, BorderLayout.SOUTH);
|
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) {
|
public void setController(PhotoAlbumController controller) {
|
||||||
this.model = controller.getModel();
|
this.model = controller.getModel();
|
||||||
model.addListener(this);
|
model.addListener(this);
|
||||||
@@ -89,6 +135,9 @@ public class PhotoAlbumView extends JFrame implements PhotoAlbumModel.ModelChang
|
|||||||
nextButton.addActionListener(e -> controller.handleNext());
|
nextButton.addActionListener(e -> controller.handleNext());
|
||||||
previousButton.addActionListener(e -> controller.handlePrevious());
|
previousButton.addActionListener(e -> controller.handlePrevious());
|
||||||
sortingCombo.addActionListener(e -> controller.handleSort(sortingCombo.getSelectedIndex()));
|
sortingCombo.addActionListener(e -> controller.handleSort(sortingCombo.getSelectedIndex()));
|
||||||
|
|
||||||
|
// Select default sorting option
|
||||||
|
sortingCombo.setSelectedIndex(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -98,6 +147,12 @@ public class PhotoAlbumView extends JFrame implements PhotoAlbumModel.ModelChang
|
|||||||
updateNavigationButtons();
|
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() {
|
private void updatePhotoList() {
|
||||||
listModel.clear();
|
listModel.clear();
|
||||||
List<Photo> photos = model.getPhotos();
|
List<Photo> photos = model.getPhotos();
|
||||||
@@ -106,6 +161,13 @@ public class PhotoAlbumView extends JFrame implements PhotoAlbumModel.ModelChang
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
private void updateCurrentPhoto() {
|
||||||
Photo current = model.getCurrentPhoto();
|
Photo current = model.getCurrentPhoto();
|
||||||
if (current != null) {
|
if (current != null) {
|
||||||
@@ -123,12 +185,27 @@ public class PhotoAlbumView extends JFrame implements PhotoAlbumModel.ModelChang
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
private void updateNavigationButtons() {
|
||||||
previousButton.setEnabled(model.hasPrevious());
|
previousButton.setEnabled(model.hasPrevious());
|
||||||
nextButton.setEnabled(model.hasNext());
|
nextButton.setEnabled(model.hasNext());
|
||||||
deleteButton.setEnabled(model.getCurrentPhoto() != null);
|
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) {
|
private ImageIcon loadImage(String path) {
|
||||||
try {
|
try {
|
||||||
ImageIcon icon = new ImageIcon(path);
|
ImageIcon icon = new ImageIcon(path);
|
||||||
@@ -140,6 +217,18 @@ public class PhotoAlbumView extends JFrame implements PhotoAlbumModel.ModelChang
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 class PhotoListCellRenderer extends JPanel implements ListCellRenderer<String> {
|
||||||
private final JLabel imageLabel = new JLabel();
|
private final JLabel imageLabel = new JLabel();
|
||||||
private final JLabel textLabel = new JLabel();
|
private final JLabel textLabel = new JLabel();
|
||||||
@@ -170,7 +259,11 @@ public class PhotoAlbumView extends JFrame implements PhotoAlbumModel.ModelChang
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Component getListCellRendererComponent(JList<? extends String> list,
|
public Component getListCellRendererComponent(JList<? extends String> list,
|
||||||
String name, int index, boolean isSelected, boolean cellHasFocus) {
|
String name,
|
||||||
|
int index,
|
||||||
|
boolean isSelected,
|
||||||
|
boolean cellHasFocus
|
||||||
|
) {
|
||||||
textLabel.setText(name);
|
textLabel.setText(name);
|
||||||
|
|
||||||
Photo photo = model.getPhotos().get(index);
|
Photo photo = model.getPhotos().get(index);
|
||||||
@@ -187,6 +280,14 @@ public class PhotoAlbumView extends JFrame implements PhotoAlbumModel.ModelChang
|
|||||||
return this;
|
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) {
|
private String formatFileSize(long size) {
|
||||||
if (size < 1024 * 1024) {
|
if (size < 1024 * 1024) {
|
||||||
return String.format("%.1f KB", size / 1024.0);
|
return String.format("%.1f KB", size / 1024.0);
|
||||||
@@ -195,6 +296,14 @@ public class PhotoAlbumView extends JFrame implements PhotoAlbumModel.ModelChang
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
private ImageIcon loadThumbnail(String path) {
|
||||||
try {
|
try {
|
||||||
ImageIcon icon = new ImageIcon(path);
|
ImageIcon icon = new ImageIcon(path);
|
||||||
|
|||||||
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
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