Compare commits
12 Commits
593840bd51
...
hw3-backup
| Author | SHA1 | Date | |
|---|---|---|---|
|
f2ee03502d
|
|||
|
19141831c8
|
|||
|
f098922149
|
|||
|
c10ba4b2fc
|
|||
|
d4705dc11e
|
|||
|
0fb5fd0654
|
|||
|
b12cf9f6d2
|
|||
|
528254c9bc
|
|||
|
63864e4e25
|
|||
|
f8b38bae8f
|
|||
|
31302dd0d5
|
|||
|
81abdd064d
|
29
hw3/.gitignore
vendored
Normal file
29
hw3/.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
### IntelliJ IDEA ###
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
8
hw3/.idea/.gitignore
generated
vendored
Normal file
8
hw3/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
6
hw3/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
hw3/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
6
hw3/.idea/misc.xml
generated
Normal file
6
hw3/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-22" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
8
hw3/.idea/modules.xml
generated
Normal file
8
hw3/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/hw3.iml" filepath="$PROJECT_DIR$/hw3.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
hw3/.idea/vcs.xml
generated
Normal file
6
hw3/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
79
hw3/README.md
Normal file
79
hw3/README.md
Normal file
@@ -0,0 +1,79 @@
|
||||
## Objectives
|
||||
|
||||
Design and implement a desktop-based photo album manager using Java Swing. The application should demonstrate the use of the Iterator pattern, the MVC pattern, and the Strategy pattern to allow flexible behavior for sorting and displaying images.
|
||||
|
||||
## Problem Statement
|
||||
|
||||
Create a photo album manager application that allows users to:
|
||||
|
||||
- Add, view, and delete photos in the album.
|
||||
- Navigate through the album using next/previous buttons.
|
||||
- Display the photos in three different sorting orders by name, date added, and file size.
|
||||
- The sorting should be implemented using the Strategy pattern, allowing different sorting strategies to be selected.
|
||||
- The album should be iterated through using the Iterator pattern.
|
||||
- The application should follow the MVC (Model-View-Controller) pattern to separate business logic, user interface, and control flow.
|
||||
|
||||
## Functional Requirements
|
||||
|
||||
- Photo Management:
|
||||
- The program manages real images.
|
||||
- Users should be able to add new photos (with a name, file path, and date).
|
||||
- Users can delete photos by name from the album.
|
||||
- Display a list of photos (name and thumbnail preview).
|
||||
- Display the current photo.
|
||||
- Navigation:
|
||||
- The "Next" and "Previous" buttons change the current photo to the next photo and the previous photo, respectively. The next and previous photo are determined by the current sorting order of the list of photos.
|
||||
- The application must implement an Iterator to traverse the photo collection.
|
||||
- Sorting Photos:
|
||||
- Users can sort the list of photos by name, date added, or file size.
|
||||
- The sorting behavior should be dynamic and implemented using the Strategy pattern.
|
||||
- The system should handle the following edge cases: empty photo albums (e.g. can't delete from an empty photo album) and non-existent file paths.
|
||||
|
||||
## Use of Patterns
|
||||
|
||||
### MVC
|
||||
|
||||
- Model (`PhotoAlbumModel`) Represents the photo collection and the current state of the album. Notifies the view when there is a change in the photo collection (by adding or deleting). Photo class contains attributes like name, file path, and date added.
|
||||
- View (`PhotoAlbumView`) A Swing-based UI that displays the up-to-date list of photos in the order of date by default and the current photo.
|
||||
- Controller (`PhotoAlbumController`) Provides controls (buttons for adding, deleting, sorting, navigating). Has buttons like "Add Photo", "Delete Photo", "Next", "Previous", and "Sort By" (Name/Date/Size). Responds to user actions and modifies the model accordingly. Manages the connection between user input (e.g., button clicks) and the photo album (model).
|
||||
|
||||
### Iterator Pattern
|
||||
|
||||
Implement an AlbumIterator class (class that implements the AlbumIterator interface) to iterate over the photos in the album.
|
||||
|
||||
### Strategy Pattern
|
||||
|
||||
Create three different sorting strategies, specifically SortByName, SortByDate, SortBySize for sorting the photos. The user can select different strategies dynamically to change the sorting order of the photos.
|
||||
|
||||
## Rough Layout of the User Interface
|
||||
|
||||
You should adhere to the layout provided below as closely as possible, but some reasonable flexibility is allowed.
|
||||

|
||||
|
||||
## Submission
|
||||
|
||||
Submit hw3.zip through the course web site. The file hw3.zip should contain the following:
|
||||
|
||||
- All source files
|
||||
- PhotoAlbumApp.java (with main), Photo.java, PhotoAlbumController.java, PhotoAlbumModel.java, PhotoAlbumView.java, SortByDate.java, SortByName.java, SortBySize.java, SortingStrategy.java.
|
||||
- Put javadoc comments in the source codes.
|
||||
- Remove all package statements from the source files.
|
||||
- All .java files (not .class files)
|
||||
- googledoc.txt: In this text file, specify a link to your google doc, titled as CS151_HW3_YOUR ID. Be sure to make this document available to ANYONE WHO HAS THE LINK (There will be point deductions if this requirement is not met.) The google doc should not be modified after the due date . The followings are required to present in the google document.
|
||||
- Screen 1: Initial screen without any photos.
|
||||
- Screen 2: After adding the first photo, the list and the current photo change.
|
||||
- Screen 3: After adding the second photo, the list updates. Photos are displayed in order of date, with the first photo in the list set as the current photo.
|
||||
- Screen 4: After adding the third photo, the list updates. Photos are displayed in order of date, with the first photo in the list set as the current photo.
|
||||
- Screen 5: After sorting the list by name, both the list and the current photo change.
|
||||
- Screen 6: After sorting the list by size, both the list and the current photo change.
|
||||
- Screen 7: After sorting the list by date, both the list and the current photo change.
|
||||
- Screen 8: After the next button is clicked, the list remains the same but the current photo changes.
|
||||
- Screen 9: After the previous button is clicked, the list remains the same but the current photo changes.
|
||||
- Screen 10: After deleting one of the photos by name. The list changes. The current photo only changes if the deleted photo was the current one. If so, the first photo in the updated list becomes the new current photo.
|
||||
- Show the correct use of patterns (copy and paste the specified code from your Java programs and label them clearly):
|
||||
- MVC Pattern
|
||||
- Controller: The action listener code for the "Add" button, which calls the mutator of the model.
|
||||
- Model: The data structure and the mutator that updates the data structure and notifies the view.
|
||||
- View: The code that calls the model's accessor and triggers a repaint.
|
||||
- Strategy Pattern: A class diagram documenting the use of the Strategy pattern. One interface and three concrete classes are expected.
|
||||
- Iterator Pattern: The Iterator class. (That is, a class that implements the AlbumnIterator class.)
|
||||
1
hw3/googledoc.txt
Normal file
1
hw3/googledoc.txt
Normal file
@@ -0,0 +1 @@
|
||||
https://docs.google.com/document/d/1iuiCyrvFca-Ygdt_7QWl_Ie5XfvbfI1GYd8HVLfGGH0/edit?usp=sharing
|
||||
11
hw3/hw3.iml
Normal file
11
hw3/hw3.iml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
BIN
hw3/photomanager.png
Normal file
BIN
hw3/photomanager.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 149 KiB |
27
hw3/src/PhotoAlbumApp.java
Normal file
27
hw3/src/PhotoAlbumApp.java
Normal file
@@ -0,0 +1,27 @@
|
||||
import controller.PhotoAlbumController;
|
||||
import model.PhotoAlbumModel;
|
||||
import view.PhotoAlbumView;
|
||||
|
||||
|
||||
/**
|
||||
* Photo Album Manager application entry point.
|
||||
* <p>
|
||||
* This class creates the model, view, and controller objects and initializes the view.
|
||||
*
|
||||
* @author Yuri Tatishchev
|
||||
* @version 0.1 2025-03-26
|
||||
*/
|
||||
public class PhotoAlbumApp {
|
||||
public static void main(String[] args) {
|
||||
// Create MVC components
|
||||
PhotoAlbumModel model = new PhotoAlbumModel();
|
||||
PhotoAlbumView view = new PhotoAlbumView();
|
||||
PhotoAlbumController controller = new PhotoAlbumController(model, view);
|
||||
|
||||
// Initialize view with the controller
|
||||
view.setController(controller);
|
||||
|
||||
// Display the main window
|
||||
javax.swing.SwingUtilities.invokeLater(() -> view.setVisible(true));
|
||||
}
|
||||
}
|
||||
135
hw3/src/controller/PhotoAlbumController.java
Normal file
135
hw3/src/controller/PhotoAlbumController.java
Normal file
@@ -0,0 +1,135 @@
|
||||
package controller;
|
||||
|
||||
import model.Photo;
|
||||
import model.PhotoAlbumModel;
|
||||
import strategy.SortByDate;
|
||||
import strategy.SortByName;
|
||||
import strategy.SortBySize;
|
||||
import view.PhotoAlbumView;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A controller that manages interactions between the photo album model and view.
|
||||
* <p>
|
||||
* This class handles user actions from the view, such as adding and deleting photos,
|
||||
* navigating through the album, and changing the sorting strategy.
|
||||
* It acts as an intermediary between the {@link PhotoAlbumModel} and {@link PhotoAlbumView},
|
||||
* translating user interface events into model operations.
|
||||
*
|
||||
* @author Yuri Tatishchev
|
||||
* @version 0.1 2025-03-26
|
||||
* @see PhotoAlbumModel
|
||||
* @see PhotoAlbumView
|
||||
* @see Photo
|
||||
*/
|
||||
public class PhotoAlbumController {
|
||||
private final PhotoAlbumModel model;
|
||||
private final PhotoAlbumView view;
|
||||
|
||||
/**
|
||||
* Constructs a new photo album controller.
|
||||
*
|
||||
* @param model the photo album model to control
|
||||
* @param view the view to handle user interactions
|
||||
*/
|
||||
public PhotoAlbumController(PhotoAlbumModel model, PhotoAlbumView view) {
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the photo album model being controlled.
|
||||
*
|
||||
* @return the photo album model
|
||||
*/
|
||||
public PhotoAlbumModel getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the addition of a new photo to the album.
|
||||
* Opens a file chooser dialog for selecting an image file and
|
||||
* prompts for a photo name.
|
||||
*/
|
||||
public void handleAddPhoto() {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setFileFilter(new FileNameExtensionFilter(
|
||||
"Image files",
|
||||
"jpg", "jpeg", "png", "gif"
|
||||
));
|
||||
|
||||
if (fileChooser.showOpenDialog(view) == JFileChooser.APPROVE_OPTION) {
|
||||
File file = fileChooser.getSelectedFile();
|
||||
String name = JOptionPane.showInputDialog(view, "Enter photo name:");
|
||||
|
||||
if (name != null && !name.trim().isEmpty()) {
|
||||
Photo photo = new Photo(
|
||||
name,
|
||||
file.getAbsolutePath(),
|
||||
new Date(),
|
||||
file.length()
|
||||
);
|
||||
model.addPhoto(photo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the deletion of a photo from the album.
|
||||
* Prompts for the name of the photo to delete.
|
||||
*/
|
||||
public void handleDeletePhoto() {
|
||||
String name = JOptionPane.showInputDialog(view, "Enter photo name to delete:");
|
||||
if (name != null && !name.trim().isEmpty()) {
|
||||
model.deletePhoto(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles navigation to the next photo in the album.
|
||||
* Moves to the next photo if one exists.
|
||||
*/
|
||||
public void handleNext() {
|
||||
if (model.hasNext()) {
|
||||
model.next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles navigation to the previous photo in the album.
|
||||
* Moves to the previous photo if one exists.
|
||||
*/
|
||||
public void handlePrevious() {
|
||||
if (model.hasPrevious()) {
|
||||
model.previous();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles changing the sorting strategy for photos in the album.
|
||||
*
|
||||
* @param strategy the sorting strategy to use:
|
||||
* <ul>
|
||||
* <li>0 for name-based sorting,
|
||||
* <li>1 for date-based sorting,
|
||||
* <li>2 for size-based sorting
|
||||
* </ul>
|
||||
*/
|
||||
public void handleSort(int strategy) {
|
||||
switch (strategy) {
|
||||
case 0:
|
||||
model.setSortingStrategy(new SortByName());
|
||||
break;
|
||||
case 1:
|
||||
model.setSortingStrategy(new SortByDate());
|
||||
break;
|
||||
case 2:
|
||||
model.setSortingStrategy(new SortBySize());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
hw3/src/iterator/AlbumIterator.java
Normal file
35
hw3/src/iterator/AlbumIterator.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package iterator;
|
||||
|
||||
import model.Photo;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Interface for album iterators.
|
||||
*/
|
||||
public interface AlbumIterator extends Iterator<Photo> {
|
||||
boolean hasNext(); // to check if there is a next element
|
||||
|
||||
/**
|
||||
* Returns whether there is a previous photo in the album.
|
||||
*
|
||||
* @return {@code true} if there is a previous photo, {@code false} otherwise
|
||||
*/
|
||||
boolean hasPrevious(); // to check if there is a previous element
|
||||
|
||||
/**
|
||||
* Returns the current photo in the album.
|
||||
*
|
||||
* @return the current photo
|
||||
*/
|
||||
Photo current(); // to get the photo at the current position
|
||||
|
||||
Photo next(); // to advance the iterator to the next position
|
||||
|
||||
/**
|
||||
* Moves to and returns the previous photo in the album.
|
||||
*
|
||||
* @return the previous photo
|
||||
*/
|
||||
Photo previous(); // to advance the iterator to the previous position
|
||||
}
|
||||
79
hw3/src/iterator/PhotoIterator.java
Normal file
79
hw3/src/iterator/PhotoIterator.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package iterator;
|
||||
|
||||
import model.Photo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* An iterator implementation for navigating through photos in a photo album.
|
||||
* <p>
|
||||
* This class provides functionality to traverse a list of photos in both
|
||||
* forward and backward directions. It maintains a current position and
|
||||
* provides methods to check for the existence of next and previous photos,
|
||||
* as well as to retrieve the current, next, and previous photos.
|
||||
* <p>
|
||||
* The iterator throws {@link NoSuchElementException} when attempting to
|
||||
* access elements beyond the bounds of the photo list.
|
||||
*
|
||||
* @author Yuri Tatishchev
|
||||
* @version 0.1 2025-03-26
|
||||
* @see Photo
|
||||
* @see AlbumIterator
|
||||
* @see NoSuchElementException
|
||||
*/
|
||||
public class PhotoIterator implements AlbumIterator {
|
||||
private final List<Photo> photos;
|
||||
private int currentPosition;
|
||||
|
||||
/**
|
||||
* Constructs a new photo iterator for the given list of photos.
|
||||
* The iterator starts at position 0.
|
||||
*
|
||||
* @param photos the list of photos to iterate over
|
||||
*/
|
||||
public PhotoIterator(List<Photo> photos) {
|
||||
this.photos = photos;
|
||||
this.currentPosition = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return currentPosition < photos.size() - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrevious() {
|
||||
return currentPosition > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Photo current() {
|
||||
if (photos.isEmpty()) {
|
||||
throw new NoSuchElementException("The photo album is empty.");
|
||||
}
|
||||
return photos.get(currentPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NoSuchElementException if there is no next photo
|
||||
*/
|
||||
@Override
|
||||
public Photo next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException("No next photo in the album.");
|
||||
}
|
||||
return photos.get(++currentPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NoSuchElementException if there is no previous photo
|
||||
*/
|
||||
@Override
|
||||
public Photo previous() {
|
||||
if (!hasPrevious()) {
|
||||
throw new NoSuchElementException("No previous photo in the album.");
|
||||
}
|
||||
return photos.get(--currentPosition);
|
||||
}
|
||||
}
|
||||
22
hw3/src/model/Photo.java
Normal file
22
hw3/src/model/Photo.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A record class that represents a photo.
|
||||
* <p>
|
||||
* A photo has a:
|
||||
* <ul>
|
||||
* <li>name
|
||||
* <li>file path
|
||||
* <li>date added
|
||||
* <li>file size
|
||||
* </ul>
|
||||
* <p>
|
||||
* A photo is immutable.
|
||||
*
|
||||
* @author Yuri Tatishchev
|
||||
* @version 0.1 2022-03-26
|
||||
*/
|
||||
public record Photo(String name, String filePath, Date dateAdded, long fileSize) {
|
||||
}
|
||||
183
hw3/src/model/PhotoAlbumModel.java
Normal file
183
hw3/src/model/PhotoAlbumModel.java
Normal file
@@ -0,0 +1,183 @@
|
||||
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 it exists).
|
||||
* Resets the iterator and notifies the listeners.
|
||||
*
|
||||
* @param name the name of the photo to delete
|
||||
*/
|
||||
public void deletePhoto(String name) {
|
||||
photos.removeIf(photo -> photo.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user