Compare commits

..

8 Commits

41 changed files with 1379 additions and 10 deletions

1
hw3/googledoc.txt Normal file
View File

@@ -0,0 +1 @@
https://docs.google.com/document/d/1iuiCyrvFca-Ygdt_7QWl_Ie5XfvbfI1GYd8HVLfGGH0/edit?usp=sharing

View File

@@ -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
@@ -16,4 +24,4 @@ public class PhotoAlbumApp {
// Display the main window // Display the main window
javax.swing.SwingUtilities.invokeLater(() -> view.setVisible(true)); javax.swing.SwingUtilities.invokeLater(() -> view.setVisible(true));
} }
} }

View File

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

View File

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

View File

@@ -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()) {

View File

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

View File

@@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

80
hw4/diagram.uml Normal file
View 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
View 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
View 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);
}
}

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

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

View 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;
}

View 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
View 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) {
}
}

View 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);
}
}

View 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;
}