Compare commits

..

8 Commits

13 changed files with 520 additions and 13 deletions

2
hw3/.idea/misc.xml generated
View File

@ -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>

View File

@ -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
}

View File

@ -0,0 +1,19 @@
import controller.PhotoAlbumController;
import model.PhotoAlbumModel;
import strategy.SortByDate;
import view.PhotoAlbumView;
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));
}
}

View File

@ -0,0 +1,80 @@
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;
public class PhotoAlbumController {
private final PhotoAlbumModel model;
private final PhotoAlbumView view;
public PhotoAlbumController(PhotoAlbumModel model, PhotoAlbumView view) {
this.model = model;
this.view = view;
}
public PhotoAlbumModel getModel() {
return model;
}
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);
}
}
}
public void handleDeletePhoto() {
String name = JOptionPane.showInputDialog(view, "Enter photo name to delete:");
if (name != null && !name.trim().isEmpty()) {
model.deletePhoto(name);
}
}
public void handleNext() {
if (model.hasNext()) {
model.next();
}
}
public void handlePrevious() {
if (model.hasPrevious()) {
model.previous();
}
}
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;
}
}
}

View File

@ -1,4 +1,10 @@
public interface AlbumIterator { package iterator;
import model.Photo;
import java.util.Iterator;
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
boolean hasPrevious(); // to check if there is a previous element boolean hasPrevious(); // to check if there is a previous element
Photo current(); // to get the photo at the current position Photo current(); // to get the photo at the current position

View File

@ -0,0 +1,49 @@
package iterator;
import model.Photo;
import java.util.List;
import java.util.NoSuchElementException;
public class PhotoIterator implements AlbumIterator {
private final List<Photo> photos;
private int currentPosition;
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);
}
@Override
public Photo next() {
if (!hasNext()) {
throw new NoSuchElementException("No next photo in the album.");
}
return photos.get(++currentPosition);
}
@Override
public Photo previous() {
if (!hasPrevious()) {
throw new NoSuchElementException("No previous photo in the album.");
}
return photos.get(--currentPosition);
}
}

6
hw3/src/model/Photo.java Normal file
View File

@ -0,0 +1,6 @@
package model;
import java.util.Date;
public record Photo(String name, String filePath, Date dateAdded, long fileSize) {
}

View File

@ -0,0 +1,105 @@
package model;
import strategy.SortingStrategy;
import iterator.AlbumIterator;
import iterator.PhotoIterator;
import java.util.*;
public class PhotoAlbumModel {
private List<Photo> photos;
private SortingStrategy<Photo> sortingStrategy;
private final List<ModelChangeListener> listeners;
private AlbumIterator iterator;
public interface ModelChangeListener {
void onModelChanged();
}
public PhotoAlbumModel() {
photos = new ArrayList<>();
listeners = new ArrayList<>();
iterator = new PhotoIterator(photos);
}
public void addPhoto(Photo photo) {
photos.add(photo);
sortPhotos();
iterator = new PhotoIterator(photos);
notifyListeners();
}
public void deletePhoto(String name) {
Photo currentPhoto = iterator.current();
photos.removeIf(photo -> photo.name().equals(name));
if (photos.isEmpty()) {
iterator = new PhotoIterator(photos);
} else if (currentPhoto != null && currentPhoto.name().equals(name)) {
iterator = new PhotoIterator(photos);
}
notifyListeners();
}
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);
}
}
public void addListener(ModelChangeListener listener) {
listeners.add(listener);
}
private void notifyListeners() {
for (ModelChangeListener listener : listeners) {
listener.onModelChanged();
}
}
public List<Photo> getPhotos() {
return Collections.unmodifiableList(photos);
}
public Photo getCurrentPhoto() {
try {
return iterator.current();
} catch (NoSuchElementException e) {
return null;
}
}
public boolean hasNext() {
return iterator.hasNext();
}
public boolean hasPrevious() {
return iterator.hasPrevious();
}
public Photo next() {
try {
Photo next = iterator.next();
notifyListeners();
return next;
} catch (NoSuchElementException e) {
return null;
}
}
public Photo previous() {
try {
Photo prev = iterator.previous();
notifyListeners();
return prev;
} catch (NoSuchElementException e) {
return null;
}
}
}

View File

@ -0,0 +1,14 @@
package strategy;
import model.Photo;
import java.util.Comparator;
import java.util.List;
public class SortByDate implements SortingStrategy<Photo> {
@Override
public List<Photo> sort(List<Photo> photos) {
photos.sort(Comparator.comparing(Photo::dateAdded));
return photos;
}
}

View File

@ -0,0 +1,14 @@
package strategy;
import model.Photo;
import java.util.Comparator;
import java.util.List;
public class SortByName implements SortingStrategy<Photo> {
@Override
public List<Photo> sort(List<Photo> photos) {
photos.sort(Comparator.comparing(Photo::name));
return photos;
}
}

View File

@ -0,0 +1,14 @@
package strategy;
import model.Photo;
import java.util.Comparator;
import java.util.List;
public class SortBySize implements SortingStrategy<Photo> {
@Override
public List<Photo> sort(List<Photo> photos) {
photos.sort(Comparator.comparing(Photo::fileSize));
return photos;
}
}

View File

@ -1,6 +1,7 @@
package strategy;
import java.util.List; import java.util.List;
public interface SortingStrategy<T> { public interface SortingStrategy<T> {
List<T> sort(List<T> photos); List<T> sort(List<T> photos);
} }

View File

@ -0,0 +1,209 @@
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;
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();
}
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);
// Select default sorting option
sortingCombo.setSelectedIndex(1);
listModel = new DefaultListModel<>();
photoList = new JList<>(listModel);
photoList.setEnabled(false);
photoList.setCellRenderer(new PhotoListCellRenderer());
photoList.setFixedCellHeight(60); // Accommodate thumbnails
}
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);
}
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()));
}
@Override
public void onModelChanged() {
updatePhotoList();
updateCurrentPhoto();
updateNavigationButtons();
}
private void updatePhotoList() {
listModel.clear();
List<Photo> photos = model.getPhotos();
for (Photo photo : photos) {
listModel.addElement(photo.name());
}
}
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");
}
}
private void updateNavigationButtons() {
previousButton.setEnabled(model.hasPrevious());
nextButton.setEnabled(model.hasNext());
deleteButton.setEnabled(model.getCurrentPhoto() != null);
}
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;
}
}
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;
}
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));
}
}
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;
}
}
}
}