package app.musicplayer.view; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.ResourceBundle; import java.util.concurrent.CountDownLatch; import app.musicplayer.MusicPlayer; import app.musicplayer.model.Album; import app.musicplayer.model.Artist; import app.musicplayer.model.Library; import app.musicplayer.model.Song; import app.musicplayer.util.ClippedTableCell; import app.musicplayer.util.ControlPanelTableCell; import app.musicplayer.util.PlayingTableCell; import app.musicplayer.util.SubView; import javafx.animation.Animation; import javafx.animation.Interpolator; import javafx.animation.Transition; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.concurrent.Task; import javafx.css.PseudoClass; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.geometry.Rectangle2D; import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.control.OverrunStyle; import javafx.scene.control.ScrollPane; import javafx.scene.control.SelectionMode; import javafx.scene.control.Separator; import javafx.scene.control.TableColumn; import javafx.scene.control.TableRow; import javafx.scene.control.TableView; import javafx.scene.control.TableView.TableViewSelectionModel; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.image.ImageView; import javafx.scene.input.ClipboardContent; import javafx.scene.input.Dragboard; import javafx.scene.input.KeyCode; import javafx.scene.input.MouseEvent; import javafx.scene.input.TransferMode; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.util.Duration; public class ArtistsMainController implements Initializable, SubView { private class ArtistCell extends ListCell<Artist> { private HBox cell = new HBox(); private ImageView artistImage = new ImageView(); private Label title = new Label(); private Artist artist; ArtistCell() { super(); artistImage.setFitWidth(40); artistImage.setFitHeight(40); artistImage.setPreserveRatio(true); artistImage.setSmooth(true); artistImage.setCache(true); title.setTextOverrun(OverrunStyle.CLIP); cell.getChildren().addAll(artistImage, title); cell.setAlignment(Pos.CENTER_LEFT); HBox.setMargin(artistImage, new Insets(0, 10, 0, 0)); this.setPrefWidth(248); this.setOnMouseClicked(event -> artistList.getSelectionModel().select(artist)); this.setOnDragDetected(event -> { Dragboard db = this.startDragAndDrop(TransferMode.ANY); ClipboardContent content = new ClipboardContent(); content.putString("Artist"); db.setContent(content); MusicPlayer.setDraggedItem(artist); db.setDragView(this.snapshot(null, null), 125, 25); event.consume(); }); } @Override protected void updateItem(Artist artist, boolean empty) { super.updateItem(artist, empty); this.artist = artist; if (empty){ setGraphic(null); } else { title.setText(artist.getTitle()); artistImage.imageProperty().bind(artist.artistImageProperty()); setGraphic(cell); } } } private class AlbumCell extends ListCell<Album> { private ImageView albumArtwork = new ImageView(); private Album album; AlbumCell() { super(); setAlignment(Pos.CENTER); setPrefHeight(140); setPrefWidth(140); albumArtwork.setFitWidth(130); albumArtwork.setFitHeight(130); albumArtwork.setPreserveRatio(true); albumArtwork.setSmooth(true); albumArtwork.setCache(true); this.setOnMouseClicked(event -> albumList.getSelectionModel().select(album)); this.setOnDragDetected(event -> { Dragboard db = this.startDragAndDrop(TransferMode.ANY); ClipboardContent content = new ClipboardContent(); content.putString("Album"); db.setContent(content); MusicPlayer.setDraggedItem(album); db.setDragView(this.snapshot(null, null), 75, 75); event.consume(); }); } @Override protected void updateItem(Album album, boolean empty) { super.updateItem(album, empty); this.album = album; if (empty){ setGraphic(null); } else { albumArtwork.setImage(album.getArtwork()); setGraphic(albumArtwork); } } } @FXML private ListView<Artist> artistList; @FXML private ListView<Album> albumList; @FXML private TableView<Song> songTable; @FXML private TableColumn<Song, Boolean> playingColumn; @FXML private TableColumn<Song, String> titleColumn; @FXML private TableColumn<Song, String> lengthColumn; @FXML private TableColumn<Song, Integer> playsColumn; @FXML private Label artistLabel; @FXML private Label albumLabel; @FXML private Separator separator; @FXML private VBox subViewRoot; @FXML private ScrollPane scrollPane; @FXML private ScrollPane artistListScrollPane; private Song selectedSong; private Album selectedAlbum; private Artist selectedArtist; private double expandedHeight = 50; private double collapsedHeight = 0; private CountDownLatch loadedLatch; @Override public void initialize(URL location, ResourceBundle resources) { songTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); loadedLatch = new CountDownLatch(1); artistLoadAnimation.setOnFinished(x -> loadedLatch.countDown()); albumLoadAnimation.setOnFinished(x -> loadedLatch.countDown()); titleColumn.prefWidthProperty().bind(songTable.widthProperty().subtract(50).multiply(0.5)); lengthColumn.prefWidthProperty().bind(songTable.widthProperty().subtract(50).multiply(0.25)); playsColumn.prefWidthProperty().bind(songTable.widthProperty().subtract(50).multiply(0.25)); playingColumn.setCellFactory(x -> new PlayingTableCell<>()); titleColumn.setCellFactory(x -> new ControlPanelTableCell<>()); lengthColumn.setCellFactory(x -> new ClippedTableCell<>()); playsColumn.setCellFactory(x -> new ClippedTableCell<>()); playingColumn.setCellValueFactory(new PropertyValueFactory<>("playing")); titleColumn.setCellValueFactory(new PropertyValueFactory<>("title")); lengthColumn.setCellValueFactory(new PropertyValueFactory<>("length")); playsColumn.setCellValueFactory(new PropertyValueFactory<>("playCount")); albumList.setCellFactory(listView -> new AlbumCell()); artistList.setCellFactory(listView -> new ArtistCell()); artistList.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> event.consume()); albumList.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> event.consume()); songTable.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> { songTable.requestFocus(); event.consume(); }); ObservableList<Artist> artists = Library.getArtists(); Collections.sort(artists); artistList.setItems(artists); artistList.setOnMouseClicked(event -> { if (event.getClickCount() == 2) { ObservableList<Song> songs = FXCollections.observableArrayList(); ObservableList<Album> albums = FXCollections.observableArrayList(); for (Album album : selectedArtist.getAlbums()) { albums.add(album); songs.addAll(album.getSongs()); } if (MusicPlayer.isShuffleActive()) { Collections.shuffle(songs); } else { Collections.sort(songs, (first, second) -> { Album firstAlbum = albums.stream().filter(x -> x.getTitle().equals(first.getAlbum())).findFirst().get(); Album secondAlbum = albums.stream().filter(x -> x.getTitle().equals(second.getAlbum())).findFirst().get(); if (firstAlbum.compareTo(secondAlbum) != 0) { return firstAlbum.compareTo(secondAlbum); } else { return first.compareTo(second); } }); } Song song = songs.get(0); MusicPlayer.setNowPlayingList(songs); MusicPlayer.setNowPlaying(song); MusicPlayer.play(); } else { Task<Void> task = new Task<Void>() { @Override protected Void call() throws Exception { Platform.runLater(() -> { subViewRoot.setVisible(false); selectedArtist = artistList.getSelectionModel().getSelectedItem(); showAllSongs(selectedArtist, false); artistLabel.setText(selectedArtist.getTitle()); albumList.setPrefWidth(albumList.getItems().size() * 150 + 2); albumList.setMaxWidth(albumList.getItems().size() * 150 + 2); albumList.scrollTo(0); }); return null; } }; task.setOnSucceeded(x -> Platform.runLater(() -> { subViewRoot.setVisible(true); artistLoadAnimation.play(); })); Thread thread = new Thread(task); artistUnloadAnimation.setOnFinished(x -> thread.start()); artistUnloadAnimation.play(); } }); albumList.setOnMouseClicked(event -> { Album album = albumList.getSelectionModel().getSelectedItem(); if (event.getClickCount() == 2) { if (album != selectedAlbum) { selectAlbum(album); } ArrayList<Song> songs = selectedAlbum.getSongs(); if (MusicPlayer.isShuffleActive()) { Collections.shuffle(songs); } else { Collections.sort(songs); } MusicPlayer.setNowPlayingList(songs); MusicPlayer.setNowPlaying(songs.get(0)); MusicPlayer.play(); } else { Task<Void> task = new Task<Void>() { @Override protected Void call() throws Exception { Platform.runLater(() -> { songTable.setVisible(false); selectAlbum(album); }); return null; } }; task.setOnSucceeded(x -> Platform.runLater(() -> { songTable.setVisible(true); albumLoadAnimation.play(); })); Thread thread = new Thread(task); albumUnloadAnimation.setOnFinished(x -> thread.start()); albumUnloadAnimation.play(); } }); songTable.setRowFactory(x -> { TableRow<Song> row = new TableRow<>(); PseudoClass playing = PseudoClass.getPseudoClass("playing"); ChangeListener<Boolean> changeListener = (obs, oldValue, newValue) -> { row.pseudoClassStateChanged(playing, newValue); }; row.itemProperty().addListener((obs, previousSong, currentSong) -> { if (previousSong != null) { previousSong.playingProperty().removeListener(changeListener); } if (currentSong != null) { currentSong.playingProperty().addListener(changeListener); row.pseudoClassStateChanged(playing, currentSong.getPlaying()); } else { row.pseudoClassStateChanged(playing, false); } }); row.setOnMouseClicked(event -> { TableViewSelectionModel<Song> sm = songTable.getSelectionModel(); if (event.getClickCount() == 2 && !row.isEmpty()) { play(); } else if (event.isShiftDown()) { ArrayList<Integer> indices = new ArrayList<>(sm.getSelectedIndices()); if (indices.size() < 1) { if (indices.contains(row.getIndex())) { sm.clearSelection(row.getIndex()); } else { sm.select(row.getItem()); } } else { sm.clearSelection(); indices.sort((first, second) -> first.compareTo(second)); int max = indices.get(indices.size() - 1); int min = indices.get(0); if (min < row.getIndex()) { for (int i = min; i <= row.getIndex(); i++) { sm.select(i); } } else { for (int i = row.getIndex(); i <= max; i++) { sm.select(i); } } } } else if (event.isControlDown()) { if (sm.getSelectedIndices().contains(row.getIndex())) { sm.clearSelection(row.getIndex()); } else { sm.select(row.getItem()); } } else { if (sm.getSelectedIndices().size() > 1) { sm.clearSelection(); sm.select(row.getItem()); } else if (sm.getSelectedIndices().contains(row.getIndex())) { sm.clearSelection(); } else { sm.clearSelection(); sm.select(row.getItem()); } } }); row.setOnDragDetected(event -> { Dragboard db = row.startDragAndDrop(TransferMode.ANY); ClipboardContent content = new ClipboardContent(); if (songTable.getSelectionModel().getSelectedIndices().size() > 1) { content.putString("List"); db.setContent(content); MusicPlayer.setDraggedItem(songTable.getSelectionModel().getSelectedItems()); } else { content.putString("Song"); db.setContent(content); MusicPlayer.setDraggedItem(row.getItem()); } ImageView image = new ImageView(row.snapshot(null, null)); Rectangle2D rectangle = new Rectangle2D(0, 0, 250, 50); image.setViewport(rectangle); db.setDragView(image.snapshot(null, null), 125, 25); event.consume(); }); return row ; }); songTable.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> { if (oldSelection != null) { oldSelection.setSelected(false); } if (newSelection != null && songTable.getSelectionModel().getSelectedIndices().size() == 1) { newSelection.setSelected(true); selectedSong = newSelection; } }); // Plays selected song when enter key is pressed. songTable.setOnKeyPressed(event -> { if (event.getCode().equals(KeyCode.ENTER)) { play(); } }); artistList.setMinHeight(0); artistList.setPrefHeight(0); double height = artists.size() * 50; Animation artistListLoadAnimation = new Transition() { { setCycleDuration(Duration.millis(250)); setInterpolator(Interpolator.EASE_BOTH); } protected void interpolate(double frac) { artistList.setMinHeight(frac * height); artistList.setPrefHeight(frac * height); } }; artistListLoadAnimation.play(); } void selectAlbum(Album album) { if (selectedAlbum == album) { albumList.getSelectionModel().clearSelection(); showAllSongs(artistList.getSelectionModel().getSelectedItem(), false); } else { if (selectedSong != null) { selectedSong.setSelected(false); } selectedSong = null; selectedAlbum = album; albumList.getSelectionModel().select(selectedAlbum); ObservableList<Song> songs = FXCollections.observableArrayList(); songs.addAll(album.getSongs()); Collections.sort(songs); songTable.getSelectionModel().clearSelection(); songTable.setItems(songs); scrollPane.setVvalue(0); albumLabel.setText(album.getTitle()); songTable.setMinHeight(0); songTable.setPrefHeight(0); songTable.setVisible(true); double height = (songs.size() + 1) * 50 + 2; Animation songTableLoadAnimation = new Transition() { { setCycleDuration(Duration.millis(250)); setInterpolator(Interpolator.EASE_BOTH); } protected void interpolate(double frac) { songTable.setMinHeight(frac * height); songTable.setPrefHeight(frac * height); } }; new Thread(() -> { try { loadedLatch.await(); loadedLatch = new CountDownLatch(1); } catch (Exception e) { e.printStackTrace(); } songTableLoadAnimation.play(); }).start(); } } void selectArtist(Artist artist) { selectedArtist = artist; artistList.getSelectionModel().select(artist); CountDownLatch latch = new CountDownLatch(1); artistListScrollPane.heightProperty().addListener((x, y, z) -> { if (z.doubleValue() != 0) { latch.countDown(); } }); new Thread(() -> { try { latch.await(); int selectedCell = artistList.getSelectionModel().getSelectedIndex(); double vValue = (selectedCell * 50) / (Library.getArtists().size() * 50 - artistListScrollPane.getHeight()); artistListScrollPane.setVvalue(vValue); } catch (Exception e) { e.printStackTrace(); } }).start(); showAllSongs(artist, true); albumList.setPrefWidth(artist.getAlbums().size() * 150 + 2); albumList.setMaxWidth(artist.getAlbums().size() * 150 + 2); artistLabel.setText(artist.getTitle()); separator.setVisible(true); } void selectSong(Song song) { new Thread(() -> { try { loadedLatch.await(); loadedLatch = new CountDownLatch(1); Platform.runLater(() -> { songTable.getSelectionModel().select(song); scrollPane.requestFocus(); }); } catch (Exception e) { e.printStackTrace(); } }).start(); } public Song getSelectedSong() { return selectedSong; } private void showAllSongs(Artist artist, boolean fromMainController) { ObservableList<Album> albums = FXCollections.observableArrayList(); ObservableList<Song> songs = FXCollections.observableArrayList(); for (Album album : artist.getAlbums()) { albums.add(album); songs.addAll(album.getSongs()); } Collections.sort(songs, (first, second) -> { Album firstAlbum = albums.stream().filter(x -> x.getTitle().equals(first.getAlbum())).findFirst().get(); Album secondAlbum = albums.stream().filter(x -> x.getTitle().equals(second.getAlbum())).findFirst().get(); if (firstAlbum.compareTo(secondAlbum) != 0) { return firstAlbum.compareTo(secondAlbum); } else { return first.compareTo(second); } }); Collections.sort(albums); if (selectedSong != null) { selectedSong.setSelected(false); } selectedSong = null; selectedAlbum = null; albumList.getSelectionModel().clearSelection(); albumList.setItems(albums); songTable.setItems(songs); songTable.getSelectionModel().clearSelection(); scrollPane.setVvalue(0); albumLabel.setText("All Songs"); songTable.setMinHeight(0); songTable.setPrefHeight(0); songTable.setVisible(true); double height = (songs.size() + 1) * 50 + 2; Animation songTableLoadAnimation = new Transition() { { setCycleDuration(Duration.millis(250)); setInterpolator(Interpolator.EASE_BOTH); } protected void interpolate(double frac) { songTable.setMinHeight(frac * height); songTable.setPrefHeight(frac * height); } }; songTableLoadAnimation.setOnFinished(x -> loadedLatch.countDown()); new Thread(() -> { try { if (fromMainController) { MusicPlayer.getMainController().getLatch().await(); MusicPlayer.getMainController().resetLatch(); } else { loadedLatch.await(); loadedLatch = new CountDownLatch(1); } } catch (Exception e) { e.printStackTrace(); } songTableLoadAnimation.play(); }).start(); } @Override public void play() { Song song = selectedSong; ArrayList<Song> songs = new ArrayList<>(); if (selectedAlbum != null) { songs.addAll(selectedAlbum.getSongs()); } else { for (Album album : selectedArtist.getAlbums()) { songs.addAll(album.getSongs()); } } if (MusicPlayer.isShuffleActive()) { Collections.shuffle(songs); songs.remove(song); songs.add(0, song); } else { Collections.sort(songs, (first, second) -> { Album firstAlbum = Library.getAlbum(first.getAlbum()); Album secondAlbum = Library.getAlbum(second.getAlbum()); if (firstAlbum.compareTo(secondAlbum) != 0) { return firstAlbum.compareTo(secondAlbum); } else { return first.compareTo(second); } }); } MusicPlayer.setNowPlayingList(songs); MusicPlayer.setNowPlaying(song); MusicPlayer.play(); } @Override public void scroll(char letter) { ObservableList<Artist> artistListItems = artistList.getItems(); int selectedCell = 0; for (Artist artist : artistListItems) { // Removes article from artist title and compares it to selected letter. String artistTitle = artist.getTitle(); char firstLetter = removeArticle(artistTitle).charAt(0); if (firstLetter < letter) { selectedCell++; } } double startVvalue = artistListScrollPane.getVvalue(); double finalVvalue = (double) (selectedCell * 50) / (Library.getArtists().size() * 50 - artistListScrollPane.getHeight()); Animation scrollAnimation = new Transition() { { setCycleDuration(Duration.millis(500)); } protected void interpolate(double frac) { double vValue = startVvalue + ((finalVvalue - startVvalue) * frac); artistListScrollPane.setVvalue(vValue); } }; scrollAnimation.play(); } private String removeArticle(String title) { String arr[] = title.split(" ", 2); if (arr.length < 2) { return title; } else { String firstWord = arr[0]; String theRest = arr[1]; switch (firstWord) { case "A": case "An": case "The": return theRest; default: return title; } } } private Animation artistLoadAnimation = new Transition() { { setCycleDuration(Duration.millis(250)); setInterpolator(Interpolator.EASE_BOTH); } protected void interpolate(double frac) { double curHeight = collapsedHeight + (expandedHeight - collapsedHeight) * (frac); subViewRoot.setTranslateY(expandedHeight - curHeight); subViewRoot.setOpacity(frac); } }; private Animation artistUnloadAnimation = new Transition() { { setCycleDuration(Duration.millis(250)); setInterpolator(Interpolator.EASE_BOTH); } protected void interpolate(double frac) { double curHeight = collapsedHeight + (expandedHeight - collapsedHeight) * (1 - frac); subViewRoot.setTranslateY(expandedHeight - curHeight); subViewRoot.setOpacity(1 - frac); } }; private Animation albumLoadAnimation = new Transition() { { setCycleDuration(Duration.millis(250)); setInterpolator(Interpolator.EASE_BOTH); } protected void interpolate(double frac) { double curHeight = collapsedHeight + (expandedHeight - collapsedHeight) * (frac); songTable.setTranslateY(expandedHeight - curHeight); songTable.setOpacity(frac); } }; private Animation albumUnloadAnimation = new Transition() { { setCycleDuration(Duration.millis(250)); setInterpolator(Interpolator.EASE_BOTH); } protected void interpolate(double frac) { double curHeight = collapsedHeight + (expandedHeight - collapsedHeight) * (1 - frac); songTable.setTranslateY(expandedHeight - curHeight); songTable.setOpacity(1 - frac); songTable.setMinHeight(1 - frac); songTable.setPrefHeight(1 - frac); } }; }