Compare commits
10 Commits
81abdd064d
...
19141831c8
Author | SHA1 | Date | |
---|---|---|---|
19141831c8 | |||
f098922149 | |||
c10ba4b2fc | |||
d4705dc11e | |||
0fb5fd0654 | |||
b12cf9f6d2 | |||
528254c9bc | |||
63864e4e25 | |||
f8b38bae8f | |||
31302dd0d5 |
2
hw3/.idea/misc.xml
generated
2
hw3/.idea/misc.xml
generated
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="23" project-jdk-type="JavaSDK">
|
<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" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
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,7 +0,0 @@
|
|||||||
public interface AlbumIterator {
|
|
||||||
boolean hasNext(); // to check if there is a next element
|
|
||||||
boolean hasPrevious(); // to check if there is a previous element
|
|
||||||
Photo current(); // to get the photo at the current position
|
|
||||||
Photo next(); // to advance the iterator to the next position
|
|
||||||
Photo previous(); // to advance the iterator to the previous position
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class Photo {
|
|
||||||
private String name;
|
|
||||||
private String filePath;
|
|
||||||
private Date dateAdded;
|
|
||||||
private long fileSize;
|
|
||||||
|
|
||||||
// Constructor, getters, and setters
|
|
||||||
}
|
|
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));
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +0,0 @@
|
|||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface SortingStrategy<T> {
|
|
||||||
List<T> sort(List<T> photos);
|
|
||||||
|
|
||||||
}
|
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user