Heavily refactored PlayerGUI to remove coupling with library and playlist functionality.
This commit is contained in:
parent
225a403de6
commit
974ff2c8c1
1
pom.xml
1
pom.xml
@ -101,6 +101,7 @@
|
|||||||
<groupId>com.google.inject</groupId>
|
<groupId>com.google.inject</groupId>
|
||||||
<artifactId>guice</artifactId>
|
<artifactId>guice</artifactId>
|
||||||
<version>4.0</version>
|
<version>4.0</version>
|
||||||
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package musicplayer;
|
package musicplayer;
|
||||||
|
|
||||||
import musicplayer.swingmodels.LibraryListModel;
|
import musicplayer.util.ConfigManager;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
class LibraryConfigGUI {
|
class LibraryConfigGUI {
|
||||||
@ -73,4 +74,74 @@ class LibraryConfigGUI {
|
|||||||
files.forEach(listModel::removeFile);
|
files.forEach(listModel::removeFile);
|
||||||
saveLibraryList();
|
saveLibraryList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class LibraryListModel<T extends File> extends AbstractListModel<T> {
|
||||||
|
|
||||||
|
private List<T> libraryFolders = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return libraryFolders.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T getElementAt(int index) {
|
||||||
|
return libraryFolders.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a directory to the list of directories used for library indexing.
|
||||||
|
* @param folder Directory to add.
|
||||||
|
*/
|
||||||
|
public void addFolder(T folder){
|
||||||
|
if(folder.exists() && folder.isDirectory()) {
|
||||||
|
libraryFolders.add(folder);
|
||||||
|
fireAllContentsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return List of directories currently used for library indexing.
|
||||||
|
*/
|
||||||
|
public List<T> currentFolderList(){
|
||||||
|
return new ArrayList<>(libraryFolders); // Copy? Don't want modification via here.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this File already exists in libraryFolders.
|
||||||
|
* @param file File to find.
|
||||||
|
* @return Does file exist in libraryFolders.
|
||||||
|
*/
|
||||||
|
public boolean contains(T file){
|
||||||
|
return libraryFolders.contains(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the list contained in libraryFolders.
|
||||||
|
* @param folderList New directory list.
|
||||||
|
*/
|
||||||
|
public void setFolderList(List<T> folderList){
|
||||||
|
if(folderList != null && folderList.size() > 0)
|
||||||
|
libraryFolders = new ArrayList<>(folderList);
|
||||||
|
else
|
||||||
|
libraryFolders = new ArrayList<>();
|
||||||
|
fireAllContentsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a directory from libraryFolders.
|
||||||
|
* @param file Directory to be removed.
|
||||||
|
*/
|
||||||
|
public void removeFile(T file){
|
||||||
|
libraryFolders.remove(file);
|
||||||
|
fireAllContentsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fire that libraryFolders contents has changed.
|
||||||
|
*/
|
||||||
|
private void fireAllContentsChanged(){
|
||||||
|
fireContentsChanged(this, 0, libraryFolders.size() == 0 ? 0 : libraryFolders.size() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
package musicplayer;
|
|
||||||
|
|
||||||
import musicplayer.model.Album;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
|
||||||
import javax.swing.tree.TreeCellRenderer;
|
|
||||||
import java.awt.*;
|
|
||||||
|
|
||||||
class LibraryTreeCellRenderer implements TreeCellRenderer {
|
|
||||||
|
|
||||||
private final JLabel label;
|
|
||||||
private static Icon missingIcon = new ImageIcon(LibraryTreeCellRenderer.class.getClassLoader().getResource("missing.gif"));
|
|
||||||
|
|
||||||
LibraryTreeCellRenderer() {
|
|
||||||
label = new JLabel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
|
|
||||||
Object o = ((DefaultMutableTreeNode) value).getUserObject();
|
|
||||||
label.setIcon(null);
|
|
||||||
if (o != null) {
|
|
||||||
label.setText(o.toString());
|
|
||||||
if (o instanceof Album) {
|
|
||||||
Album album = (Album) o;
|
|
||||||
label.setIcon(missingIcon);
|
|
||||||
album.getAlbumArt().ifPresent(x -> label.setIcon(new ImageIcon(x)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,17 +3,14 @@ package musicplayer;
|
|||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import musicplayer.callbacks.LibraryCallbackInterface;
|
|
||||||
import musicplayer.callbacks.PlayerCallbackInterface;
|
import musicplayer.callbacks.PlayerCallbackInterface;
|
||||||
import musicplayer.db.IDatabase;
|
import musicplayer.db.IDatabase;
|
||||||
import musicplayer.model.Album;
|
import musicplayer.library.ILibrary;
|
||||||
import musicplayer.model.Artist;
|
|
||||||
import musicplayer.model.HasSongs;
|
|
||||||
import musicplayer.model.Song;
|
import musicplayer.model.Song;
|
||||||
import musicplayer.player.IPlayer;
|
import musicplayer.player.IPlayer;
|
||||||
import musicplayer.swingmodels.PlaylistTableModel;
|
import musicplayer.playlist.IPlaylist;
|
||||||
|
import musicplayer.util.ConfigManager;
|
||||||
import musicplayer.util.Icons;
|
import musicplayer.util.Icons;
|
||||||
import musicplayer.util.LibraryUtils;
|
|
||||||
import musicplayer.util.PlaylistUtils;
|
import musicplayer.util.PlaylistUtils;
|
||||||
import org.jnativehook.GlobalScreen;
|
import org.jnativehook.GlobalScreen;
|
||||||
import org.jnativehook.NativeHookException;
|
import org.jnativehook.NativeHookException;
|
||||||
@ -23,12 +20,7 @@ import org.jnativehook.keyboard.NativeKeyListener;
|
|||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.event.HyperlinkEvent;
|
import javax.swing.event.HyperlinkEvent;
|
||||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
|
||||||
import javax.swing.tree.DefaultTreeModel;
|
|
||||||
import javax.swing.tree.TreeNode;
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ItemEvent;
|
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.awt.event.MouseListener;
|
import java.awt.event.MouseListener;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
@ -36,35 +28,25 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterface {
|
public class PlayerGUI extends JPanel implements PlayerCallbackInterface {
|
||||||
private JPanel mainPanel;
|
private final IPlaylist playlist;
|
||||||
private final JTree libraryView = new JTree();
|
|
||||||
private final JTable playList = new JTable();
|
|
||||||
private JSlider seekBar;
|
private JSlider seekBar;
|
||||||
private final JComboBox<String> libraryDisplayType = new JComboBox<>();
|
|
||||||
private final IPlayer player;
|
private final IPlayer player;
|
||||||
private final IDatabase database;
|
private final ILibrary library;
|
||||||
private final PlaylistTableModel playlistTableModel = new PlaylistTableModel(new ArrayList<>());
|
|
||||||
private static final DefaultMutableTreeNode updatingNode = new DefaultMutableTreeNode();
|
|
||||||
private boolean libraryUpdating = false;
|
|
||||||
private final AtomicBoolean seeking = new AtomicBoolean(false);
|
private final AtomicBoolean seeking = new AtomicBoolean(false);
|
||||||
|
|
||||||
private final Map<String, Runnable> libraryDisplayVariants = createDisplayVariantMap();
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PlayerGUI(IPlayer player, IDatabase database) {
|
public PlayerGUI(IPlayer player, IPlaylist playlist, ILibrary library) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.database = database;
|
this.playlist = playlist;
|
||||||
|
this.library = library;
|
||||||
createUI();
|
createUI();
|
||||||
resetTree();
|
|
||||||
Thread seekBarUpdater = new Thread(() -> {
|
Thread seekBarUpdater = new Thread(() -> {
|
||||||
boolean running = true;
|
boolean running = true;
|
||||||
while (running) {
|
while (running) {
|
||||||
@ -99,15 +81,15 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
|||||||
System.out.println(ex.getMessage());
|
System.out.println(ex.getMessage());
|
||||||
System.out.println(Arrays.toString(ex.getStackTrace()));
|
System.out.println(Arrays.toString(ex.getStackTrace()));
|
||||||
}
|
}
|
||||||
refreshLibrary();
|
library.refreshLibrary();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
Injector injector = Guice.createInjector();
|
Injector injector = Guice.createInjector(new SwingUIModule());
|
||||||
PlayerGUI playerGUI = injector.getInstance(PlayerGUI.class);
|
PlayerGUI playerGUI = injector.getInstance(PlayerGUI.class);
|
||||||
JFrame frame = new JFrame();
|
JFrame frame = new JFrame();
|
||||||
frame.setMinimumSize(new Dimension(600, 400));
|
frame.setMinimumSize(new Dimension(600, 400));
|
||||||
frame.setContentPane(playerGUI.mainPanel);
|
frame.setContentPane(playerGUI);
|
||||||
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||||
frame.pack();
|
frame.pack();
|
||||||
frame.setVisible(true);
|
frame.setVisible(true);
|
||||||
@ -125,61 +107,6 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the libraryView to contain nothing.
|
|
||||||
*/
|
|
||||||
private void resetTree() {
|
|
||||||
libraryView.removeAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T extends HasSongs & Comparable<T>> void populateLibraryWithGroupedSongs(List<T> libraryData){
|
|
||||||
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
|
|
||||||
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
|
|
||||||
Collections.sort(libraryData);
|
|
||||||
libraryData.forEach(x -> {
|
|
||||||
DefaultMutableTreeNode outerNode = new DefaultMutableTreeNode(x);
|
|
||||||
addNodeToTreeModel(model, parentNode, outerNode);
|
|
||||||
x.getSongs().forEach(y -> addNodeToTreeModel(model, outerNode, new DefaultMutableTreeNode(y)));
|
|
||||||
});
|
|
||||||
libraryView.setModel(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populate the library with all songs.
|
|
||||||
*
|
|
||||||
* @param libraryData List of songs.
|
|
||||||
*/
|
|
||||||
private void populateLibraryWithSongsOnly(List<Song> libraryData) {
|
|
||||||
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
|
|
||||||
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
|
|
||||||
Collections.sort(libraryData);
|
|
||||||
libraryData.forEach(x -> addNodeToTreeModel(model, parentNode, new DefaultMutableTreeNode(x)));
|
|
||||||
libraryView.setModel(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an item to libraryView.
|
|
||||||
*
|
|
||||||
* @param parentNode Node that should be the parent of this node.
|
|
||||||
* @param node This node.
|
|
||||||
*/
|
|
||||||
private void addNodeToTreeModel(DefaultTreeModel model, DefaultMutableTreeNode parentNode, DefaultMutableTreeNode node) {
|
|
||||||
model.insertNodeInto(node, parentNode, parentNode.getChildCount());
|
|
||||||
if (parentNode == model.getRoot()) {
|
|
||||||
model.nodeStructureChanged((TreeNode) model.getRoot());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a song to the current playlist.
|
|
||||||
*
|
|
||||||
* @param song Song to be added to the playlist.
|
|
||||||
*/
|
|
||||||
private void addToPlaylist(Song song) {
|
|
||||||
playlistTableModel.addSong(song);
|
|
||||||
playList.revalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the highlighted song in the playlist to this song.
|
* Set the highlighted song in the playlist to this song.
|
||||||
*
|
*
|
||||||
@ -187,17 +114,14 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setSongHighlighting(Song playingSong) {
|
public void setSongHighlighting(Song playingSong) {
|
||||||
int index = playlistTableModel.getSongIndex(playingSong);
|
playlist.setPlayingSong(playingSong);
|
||||||
if(index >= 0)
|
|
||||||
SwingUtilities.invokeLater(() -> playList.setRowSelectionInterval(index, index));
|
|
||||||
playlistTableModel.setPlayingRow(index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void playSong(Optional<Song> song){
|
private void playSong(Optional<Song> song){
|
||||||
try {
|
try {
|
||||||
player.playSong(song);
|
player.playSong(song);
|
||||||
} catch (StartPlayingException e) {
|
} catch (StartPlayingException e) {
|
||||||
JOptionPane.showMessageDialog(mainPanel, "Failed to play " + song.get().toString() + ".\n" +
|
JOptionPane.showMessageDialog(this, "Failed to play " + song.get().toString() + ".\n" +
|
||||||
"If file path contains non-ASCII characters, please restart the application with UTF-8 encoding.");
|
"If file path contains non-ASCII characters, please restart the application with UTF-8 encoding.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,7 +131,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
|||||||
*/
|
*/
|
||||||
public void playNextSong() {
|
public void playNextSong() {
|
||||||
SwingUtilities.invokeLater(() -> seekBar.setValue(0));
|
SwingUtilities.invokeLater(() -> seekBar.setValue(0));
|
||||||
playSong(playlistTableModel.nextSong(player.getCurrentSong()));
|
playSong(playlist.getNext(player.getCurrentSong()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -215,7 +139,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
|||||||
*/
|
*/
|
||||||
public void playPreviousSong() {
|
public void playPreviousSong() {
|
||||||
SwingUtilities.invokeLater(() -> seekBar.setValue(0));
|
SwingUtilities.invokeLater(() -> seekBar.setValue(0));
|
||||||
playSong(playlistTableModel.previous(player.getCurrentSong()));
|
playSong(playlist.getPrevious(player.getCurrentSong()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -229,76 +153,16 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeInvalidSong(Song invalidSong) {
|
public void removeInvalidSong(Song invalidSong) {
|
||||||
playlistTableModel.removeSong(invalidSong);
|
playlist.delete(invalidSong);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void playerStopped(){
|
public void playerStopped(){
|
||||||
setSongHighlighting(player.getCurrentSong());
|
setSongHighlighting(player.getCurrentSong());
|
||||||
playlistTableModel.setPlayingRow(-1);
|
playlist.setPlayingSong(null);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh the library with songs in the currently selected display format.
|
|
||||||
*/
|
|
||||||
private void refreshLibrary() {
|
|
||||||
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
|
|
||||||
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
|
|
||||||
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("Refreshing Library...");
|
|
||||||
addNodeToTreeModel(model, parentNode, newNode);
|
|
||||||
libraryView.setModel(model);
|
|
||||||
Thread populateThread = new Thread(() -> {
|
|
||||||
libraryDisplayVariants.get(libraryDisplayType.getSelectedItem().toString()).run();
|
|
||||||
// If we get here and the model hasn't changed, the library must be empty.
|
|
||||||
if(libraryView.getModel() == model){
|
|
||||||
DefaultTreeModel failedModel = new DefaultTreeModel(new DefaultMutableTreeNode());
|
|
||||||
DefaultMutableTreeNode failedParentNode = (DefaultMutableTreeNode) failedModel.getRoot();
|
|
||||||
DefaultMutableTreeNode failedNode = new DefaultMutableTreeNode("Library is empty!");
|
|
||||||
addNodeToTreeModel(failedModel, failedParentNode, failedNode);
|
|
||||||
libraryView.setModel(failedModel);
|
|
||||||
}
|
|
||||||
}, "libraryRefresh");
|
|
||||||
populateThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateLibrary(){
|
|
||||||
libraryUpdating = true;
|
|
||||||
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
|
|
||||||
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
|
|
||||||
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("Updating Library...");
|
|
||||||
addNodeToTreeModel(model, parentNode, newNode);
|
|
||||||
addNodeToTreeModel(model, parentNode, updatingNode);
|
|
||||||
libraryView.setModel(model);
|
|
||||||
List<Path> dirs = ConfigManager.getLibraryDirectories().stream().map(File::toPath).collect(Collectors.toList());
|
|
||||||
LibraryUtils.processSongsWithCallback(database, dirs, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void libraryUpdated(boolean successful) {
|
|
||||||
if (successful)
|
|
||||||
refreshLibrary();
|
|
||||||
libraryUpdating = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void currentlyUpdating(String name) {
|
|
||||||
updatingNode.setUserObject(name);
|
|
||||||
((DefaultTreeModel)libraryView.getModel()).nodeChanged(updatingNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Map of display types for the library paired code to populate the library with data in the correct format.
|
|
||||||
*/
|
|
||||||
private Map<String, Runnable> createDisplayVariantMap() {
|
|
||||||
Map<String, Runnable> value = new HashMap<>();
|
|
||||||
value.put("Song", () -> database.listAllT(Song.class).ifPresent(this::populateLibraryWithSongsOnly));
|
|
||||||
value.put("Album/Song", () -> database.listAllT(Album.class).ifPresent(this::populateLibraryWithGroupedSongs));
|
|
||||||
value.put("Artist/Song", () -> database.listAllT(Artist.class).ifPresent(this::populateLibraryWithGroupedSongs));
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
mainPanel
|
this
|
||||||
+--------------------+
|
+--------------------+
|
||||||
|menuBar |
|
|menuBar |
|
||||||
+---------+----------+
|
+---------+----------+
|
||||||
@ -311,79 +175,22 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
|||||||
+--------------------+
|
+--------------------+
|
||||||
*/
|
*/
|
||||||
private void createUI() {
|
private void createUI() {
|
||||||
mainPanel = new JPanel();
|
this.setLayout(new BorderLayout(0, 0));
|
||||||
mainPanel.setLayout(new BorderLayout(0, 0));
|
|
||||||
final JSplitPane libraryAndPlaylistPane = new JSplitPane();
|
final JSplitPane libraryAndPlaylistPane = new JSplitPane();
|
||||||
libraryAndPlaylistPane.setDividerLocation(240);
|
libraryAndPlaylistPane.setDividerLocation(240);
|
||||||
libraryAndPlaylistPane.setRightComponent(createPlaylistArea());
|
libraryAndPlaylistPane.setRightComponent((JScrollPane)playlist);
|
||||||
mainPanel.add(libraryAndPlaylistPane, BorderLayout.CENTER);
|
this.add(libraryAndPlaylistPane, BorderLayout.CENTER);
|
||||||
final JPanel libraryAndComboboxPane = new JPanel();
|
libraryAndPlaylistPane.setLeftComponent((JPanel)library);
|
||||||
libraryAndComboboxPane.setLayout(new BorderLayout(0, 0));
|
|
||||||
libraryAndPlaylistPane.setLeftComponent(libraryAndComboboxPane);
|
|
||||||
libraryDisplayVariants.keySet().forEach(libraryDisplayType::addItem);
|
|
||||||
libraryDisplayType.setSelectedIndex(ConfigManager.getLastDisplayTypeIndex());
|
|
||||||
libraryDisplayType.addItemListener(e -> {
|
|
||||||
if (e.getStateChange() == ItemEvent.SELECTED) {
|
|
||||||
ConfigManager.setLastDisplayTypeIndex(libraryDisplayType.getSelectedIndex());
|
|
||||||
if(!libraryUpdating)
|
|
||||||
libraryDisplayVariants.get(libraryDisplayType.getSelectedItem().toString()).run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
libraryAndComboboxPane.add(libraryDisplayType, BorderLayout.NORTH);
|
|
||||||
final JScrollPane libraryPane = new JScrollPane();
|
|
||||||
libraryAndComboboxPane.add(libraryPane, BorderLayout.CENTER);
|
|
||||||
libraryPane.setViewportView(createLibraryArea());
|
|
||||||
final JPanel controlPane = new JPanel();
|
final JPanel controlPane = new JPanel();
|
||||||
controlPane.setLayout(new BorderLayout(0, 0));
|
controlPane.setLayout(new BorderLayout(0, 0));
|
||||||
mainPanel.add(controlPane, BorderLayout.SOUTH);
|
this.add(controlPane, BorderLayout.SOUTH);
|
||||||
seekBar = new JSlider();
|
seekBar = new JSlider();
|
||||||
seekBar.setMinorTickSpacing(1);
|
seekBar.setMinorTickSpacing(1);
|
||||||
seekBar.setValue(0);
|
seekBar.setValue(0);
|
||||||
seekBar.addMouseListener(new SeekbarMouseListener());
|
seekBar.addMouseListener(new SeekbarMouseListener());
|
||||||
controlPane.add(seekBar, BorderLayout.NORTH);
|
controlPane.add(seekBar, BorderLayout.NORTH);
|
||||||
controlPane.add(createControlButtons(), BorderLayout.SOUTH);
|
controlPane.add(createControlButtons(), BorderLayout.SOUTH);
|
||||||
mainPanel.add(createMenuBar(), BorderLayout.NORTH);
|
this.add(createMenuBar(), BorderLayout.NORTH);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Area containing the library.
|
|
||||||
*/
|
|
||||||
private JTree createLibraryArea() {
|
|
||||||
libraryView.setRootVisible(false);
|
|
||||||
libraryView.setToggleClickCount(1);
|
|
||||||
libraryView.setCellRenderer(new LibraryTreeCellRenderer());
|
|
||||||
libraryView.addMouseListener(new LibraryMouseAdapter());
|
|
||||||
libraryView.setScrollsOnExpand(false);
|
|
||||||
return libraryView;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Area containing the playlist.
|
|
||||||
*/
|
|
||||||
private JScrollPane createPlaylistArea() {
|
|
||||||
JScrollPane playlistScroll = new JScrollPane();
|
|
||||||
playList.setRowSelectionAllowed(true);
|
|
||||||
playList.setModel(playlistTableModel);
|
|
||||||
playList.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
|
||||||
playList.getColumnModel().getColumn(0).setPreferredWidth(10);
|
|
||||||
playList.getColumnModel().getColumn(0).setMaxWidth(10);
|
|
||||||
playList.getColumnModel().getColumn(1).setPreferredWidth(40);
|
|
||||||
playList.getColumnModel().getColumn(1).setMaxWidth(40);
|
|
||||||
JPopupMenu popupMenu = new JPopupMenu();
|
|
||||||
JMenuItem menuItem = new JMenuItem("Remove");
|
|
||||||
menuItem.addActionListener((e) -> playlistTableModel.removeSong(playList.getSelectedRows()));
|
|
||||||
popupMenu.add(menuItem);
|
|
||||||
playList.setComponentPopupMenu(popupMenu);
|
|
||||||
popupMenu = new JPopupMenu();
|
|
||||||
menuItem = new JMenuItem("Clear all");
|
|
||||||
menuItem.addActionListener((e) -> {
|
|
||||||
playlistTableModel.removeAll();
|
|
||||||
player.stop();
|
|
||||||
});
|
|
||||||
popupMenu.add(menuItem);
|
|
||||||
playlistScroll.setComponentPopupMenu(popupMenu);
|
|
||||||
playlistScroll.setViewportView(playList);
|
|
||||||
return playlistScroll;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -394,16 +201,10 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
|||||||
toolBar.setFloatable(false);
|
toolBar.setFloatable(false);
|
||||||
JPanel controlBar = new JPanel(new GridLayout(1, 5));
|
JPanel controlBar = new JPanel(new GridLayout(1, 5));
|
||||||
JButton playButton = new JButton();
|
JButton playButton = new JButton();
|
||||||
|
playButton.setName("playButton");
|
||||||
playButton.setIcon(Icons.playIcon);
|
playButton.setIcon(Icons.playIcon);
|
||||||
playButton.setMnemonic('P');
|
playButton.setMnemonic('P');
|
||||||
playButton.addActionListener(e -> {
|
playButton.addActionListener(e -> playSong(playlist.getActive()));
|
||||||
if (playList.getRowCount() > 0) {
|
|
||||||
if (playList.getSelectedRowCount() > 0)
|
|
||||||
playSong(playlistTableModel.getSong(playList.getSelectedRow()));
|
|
||||||
else
|
|
||||||
playSong(playlistTableModel.getFirst());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
controlBar.add(playButton);
|
controlBar.add(playButton);
|
||||||
JButton pauseButton = new JButton();
|
JButton pauseButton = new JButton();
|
||||||
pauseButton.setIcon(Icons.pauseIcon);
|
pauseButton.setIcon(Icons.pauseIcon);
|
||||||
@ -465,12 +266,12 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
|||||||
// Tools menu
|
// Tools menu
|
||||||
JMenu tools = new JMenu("Tools");
|
JMenu tools = new JMenu("Tools");
|
||||||
JMenuItem menuItem = new JMenuItem("Refresh Library");
|
JMenuItem menuItem = new JMenuItem("Refresh Library");
|
||||||
menuItem.addActionListener(e -> refreshLibrary());
|
menuItem.addActionListener(e -> library.refreshLibrary());
|
||||||
tools.add(menuItem);
|
tools.add(menuItem);
|
||||||
|
|
||||||
menuItem = new JMenuItem("Update Library");
|
menuItem = new JMenuItem("Update Library");
|
||||||
menuItem.setToolTipText("Reindex and refresh library.");
|
menuItem.setToolTipText("Reindex and refresh library.");
|
||||||
menuItem.addActionListener(e -> updateLibrary());
|
menuItem.addActionListener(e -> library.updateLibrary());
|
||||||
tools.add(menuItem);
|
tools.add(menuItem);
|
||||||
|
|
||||||
menuItem = new JMenuItem("Library Settings");
|
menuItem = new JMenuItem("Library Settings");
|
||||||
@ -483,7 +284,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
|||||||
JMenu playlistTools = new JMenu("Playlist");
|
JMenu playlistTools = new JMenu("Playlist");
|
||||||
menuItem = new JMenuItem("Clear All");
|
menuItem = new JMenuItem("Clear All");
|
||||||
menuItem.addActionListener((e) -> {
|
menuItem.addActionListener((e) -> {
|
||||||
playlistTableModel.removeAll();
|
playlist.deleteAll();
|
||||||
player.stop();
|
player.stop();
|
||||||
});
|
});
|
||||||
playlistTools.add(menuItem);
|
playlistTools.add(menuItem);
|
||||||
@ -491,17 +292,17 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
|||||||
FileNameExtensionFilter m3uExtensionFilter = new FileNameExtensionFilter("m3u playlist", "m3u");
|
FileNameExtensionFilter m3uExtensionFilter = new FileNameExtensionFilter("m3u playlist", "m3u");
|
||||||
menuItem = new JMenuItem("Save Playlist");
|
menuItem = new JMenuItem("Save Playlist");
|
||||||
menuItem.addActionListener(e -> {
|
menuItem.addActionListener(e -> {
|
||||||
if(!playlistTableModel.isEmpty()) {
|
if(!playlist.isEmpty()) {
|
||||||
JFileChooser fileChooser = new JFileChooser();
|
JFileChooser fileChooser = new JFileChooser();
|
||||||
fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
|
fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
|
||||||
fileChooser.setSelectedFile(new File("playlist.m3u"));
|
fileChooser.setSelectedFile(new File("playlist.m3u"));
|
||||||
fileChooser.setFileFilter(m3uExtensionFilter);
|
fileChooser.setFileFilter(m3uExtensionFilter);
|
||||||
if (fileChooser.showSaveDialog(mainPanel) == JFileChooser.APPROVE_OPTION) {
|
if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||||
String filename = fileChooser.getSelectedFile().toString();
|
String filename = fileChooser.getSelectedFile().toString();
|
||||||
if (!filename.endsWith(".m3u"))
|
if (!filename.endsWith(".m3u"))
|
||||||
filename += ".m3u";
|
filename += ".m3u";
|
||||||
try {
|
try {
|
||||||
PlaylistUtils.writePlaylistToFile(playlistTableModel.getSongList(), new File(filename));
|
PlaylistUtils.writePlaylistToFile(playlist.getSongList(), new File(filename));
|
||||||
} catch (IOException e1) {
|
} catch (IOException e1) {
|
||||||
JOptionPane.showMessageDialog(null, e1.getMessage() + "\n" + Arrays.toString(e1.getStackTrace()), "Error", JOptionPane.ERROR_MESSAGE);
|
JOptionPane.showMessageDialog(null, e1.getMessage() + "\n" + Arrays.toString(e1.getStackTrace()), "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
@ -515,8 +316,8 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
|||||||
JFileChooser fileChooser = new JFileChooser();
|
JFileChooser fileChooser = new JFileChooser();
|
||||||
fileChooser.setDialogType(JFileChooser.OPEN_DIALOG);
|
fileChooser.setDialogType(JFileChooser.OPEN_DIALOG);
|
||||||
fileChooser.setFileFilter(m3uExtensionFilter);
|
fileChooser.setFileFilter(m3uExtensionFilter);
|
||||||
if(fileChooser.showOpenDialog(mainPanel) == JFileChooser.APPROVE_OPTION){
|
if(fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION){
|
||||||
PlaylistUtils.readPlaylistFromFile(fileChooser.getSelectedFile()).stream().forEach(playlistTableModel::addSong);
|
PlaylistUtils.readPlaylistFromFile(fileChooser.getSelectedFile()).stream().forEach(playlist::addSong);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
playlistTools.add(menuItem);
|
playlistTools.add(menuItem);
|
||||||
@ -540,7 +341,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
|||||||
e1.printStackTrace();
|
e1.printStackTrace();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
JOptionPane.showMessageDialog(mainPanel, messagePane, "About", JOptionPane.INFORMATION_MESSAGE);
|
JOptionPane.showMessageDialog(this, messagePane, "About", JOptionPane.INFORMATION_MESSAGE);
|
||||||
} catch (IOException e1) {
|
} catch (IOException e1) {
|
||||||
e1.printStackTrace();
|
e1.printStackTrace();
|
||||||
}
|
}
|
||||||
@ -551,25 +352,6 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
|||||||
return menuBar;
|
return menuBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LibraryMouseAdapter extends MouseAdapter {
|
|
||||||
@Override
|
|
||||||
public void mousePressed(MouseEvent e) {
|
|
||||||
int selRow = libraryView.getRowForLocation(e.getX(), e.getY());
|
|
||||||
if(e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2 && selRow != -1)
|
|
||||||
try {
|
|
||||||
Object selectedItem = ((DefaultMutableTreeNode) libraryView.getPathForLocation(e.getX(), e.getY()).getLastPathComponent()).getUserObject();
|
|
||||||
if (selectedItem != null) {
|
|
||||||
if (selectedItem instanceof Song) {
|
|
||||||
addToPlaylist((Song) selectedItem);
|
|
||||||
} else if (selectedItem instanceof Album) {
|
|
||||||
((Album) selectedItem).getSongs().forEach(PlayerGUI.this::addToPlaylist);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (NullPointerException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MouseListener implementation for the seekbar.
|
* MouseListener implementation for the seekbar.
|
||||||
* Using this instead of the ChangeListener so that the polling for the current song position
|
* Using this instead of the ChangeListener so that the polling for the current song position
|
||||||
@ -624,12 +406,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
|||||||
setSongHighlighting(player.getCurrentSong()); // Resume won't function if a different song is selected.
|
setSongHighlighting(player.getCurrentSong()); // Resume won't function if a different song is selected.
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if (playList.getRowCount() > 0) {
|
playSong(playlist.getActive());
|
||||||
if (playList.getSelectedRowCount() > 0)
|
|
||||||
playSong(playlistTableModel.getSong(playList.getSelectedRow()));
|
|
||||||
else
|
|
||||||
playSong(playlistTableModel.getFirst());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case stop:
|
case stop:
|
||||||
|
23
src/main/java/musicplayer/SwingUIModule.java
Normal file
23
src/main/java/musicplayer/SwingUIModule.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package musicplayer;
|
||||||
|
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import musicplayer.db.HibernateDatabase;
|
||||||
|
import musicplayer.db.IDatabase;
|
||||||
|
import musicplayer.library.ILibrary;
|
||||||
|
import musicplayer.library.JTreeLibrary;
|
||||||
|
import musicplayer.player.GStreamerPlayer;
|
||||||
|
import musicplayer.player.IPlayer;
|
||||||
|
import musicplayer.playlist.IPlaylist;
|
||||||
|
import musicplayer.playlist.JTablePlaylist;
|
||||||
|
|
||||||
|
public class SwingUIModule extends AbstractModule {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
bind(IPlayer.class).to(GStreamerPlayer.class).in(Singleton.class);
|
||||||
|
bind(IDatabase.class).to(HibernateDatabase.class).in(Singleton.class);
|
||||||
|
bind(IPlaylist.class).to(JTablePlaylist.class).in(Singleton.class);
|
||||||
|
bind(ILibrary.class).to(JTreeLibrary.class).in(Singleton.class);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package musicplayer.db;
|
package musicplayer.db;
|
||||||
|
|
||||||
import musicplayer.ConfigManager;
|
import musicplayer.util.ConfigManager;
|
||||||
import musicplayer.model.*;
|
import musicplayer.model.*;
|
||||||
import musicplayer.util.AlbumArtExtractor;
|
import musicplayer.util.AlbumArtExtractor;
|
||||||
import org.hibernate.Criteria;
|
import org.hibernate.Criteria;
|
||||||
@ -18,7 +18,7 @@ import java.util.Properties;
|
|||||||
/**
|
/**
|
||||||
* Class for managing connection to Hibernate.
|
* Class for managing connection to Hibernate.
|
||||||
*/
|
*/
|
||||||
class HibernateDatabase implements IDatabase{
|
public class HibernateDatabase implements IDatabase{
|
||||||
|
|
||||||
private static SessionFactory sessionFactory;
|
private static SessionFactory sessionFactory;
|
||||||
|
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
package musicplayer.db;
|
package musicplayer.db;
|
||||||
|
|
||||||
import com.google.inject.ImplementedBy;
|
|
||||||
import musicplayer.model.*;
|
import musicplayer.model.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@ImplementedBy(HibernateDatabase.class)
|
|
||||||
public interface IDatabase {
|
public interface IDatabase {
|
||||||
Optional<Album> getOneAlbum(String name);
|
Optional<Album> getOneAlbum(String name);
|
||||||
Optional<Artist> getOneArtist(String name);
|
Optional<Artist> getOneArtist(String name);
|
||||||
|
10
src/main/java/musicplayer/library/ILibrary.java
Normal file
10
src/main/java/musicplayer/library/ILibrary.java
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package musicplayer.library;
|
||||||
|
|
||||||
|
import musicplayer.model.HasSongs;
|
||||||
|
|
||||||
|
public interface ILibrary {
|
||||||
|
void showSongs();
|
||||||
|
<T extends HasSongs & Comparable<T>> void showGroupedSongs(Class<T> grouping);
|
||||||
|
void updateLibrary();
|
||||||
|
void refreshLibrary();
|
||||||
|
}
|
222
src/main/java/musicplayer/library/JTreeLibrary.java
Normal file
222
src/main/java/musicplayer/library/JTreeLibrary.java
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
package musicplayer.library;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import musicplayer.util.ConfigManager;
|
||||||
|
import musicplayer.callbacks.LibraryCallbackInterface;
|
||||||
|
import musicplayer.db.IDatabase;
|
||||||
|
import musicplayer.model.Album;
|
||||||
|
import musicplayer.model.Artist;
|
||||||
|
import musicplayer.model.HasSongs;
|
||||||
|
import musicplayer.model.Song;
|
||||||
|
import musicplayer.playlist.IPlaylist;
|
||||||
|
import musicplayer.util.LibraryUtils;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
import javax.swing.tree.DefaultTreeModel;
|
||||||
|
import javax.swing.tree.TreeCellRenderer;
|
||||||
|
import javax.swing.tree.TreeNode;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ItemEvent;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class JTreeLibrary extends JPanel implements ILibrary, LibraryCallbackInterface{
|
||||||
|
|
||||||
|
IDatabase database;
|
||||||
|
IPlaylist playlist;
|
||||||
|
private static final DefaultMutableTreeNode updatingNode = new DefaultMutableTreeNode();
|
||||||
|
private final AtomicBoolean libraryUpdating = new AtomicBoolean(false);
|
||||||
|
private final JComboBox<String> libraryDisplayType = new JComboBox<>();
|
||||||
|
private final JTree libraryTree = new JTree();
|
||||||
|
private final Map<String, Runnable> libraryDisplayVariants = createDisplayVariantMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Map of display types for the library paired code to populate the library with data in the correct format.
|
||||||
|
*/
|
||||||
|
private Map<String, Runnable> createDisplayVariantMap() {
|
||||||
|
Map<String, Runnable> value = new HashMap<>();
|
||||||
|
value.put("Song", this::showSongs);
|
||||||
|
value.put("Album/Song", () -> this.showGroupedSongs(Album.class));
|
||||||
|
value.put("Artist/Song", () -> this.showGroupedSongs(Artist.class));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public JTreeLibrary(IDatabase database, IPlaylist playlist){
|
||||||
|
this.database = database;
|
||||||
|
this.playlist = playlist;
|
||||||
|
this.setLayout(new BorderLayout(0, 0));
|
||||||
|
libraryTree.setRootVisible(false);
|
||||||
|
libraryTree.setToggleClickCount(1);
|
||||||
|
libraryTree.setCellRenderer(new LibraryTreeCellRenderer());
|
||||||
|
libraryTree.addMouseListener(new LibraryMouseAdapter());
|
||||||
|
libraryTree.setScrollsOnExpand(false);
|
||||||
|
final JScrollPane libraryPane = new JScrollPane(libraryTree);
|
||||||
|
this.add(libraryPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
libraryDisplayVariants.keySet().forEach(libraryDisplayType::addItem);
|
||||||
|
libraryDisplayType.setSelectedIndex(ConfigManager.getLastDisplayTypeIndex());
|
||||||
|
libraryDisplayType.addItemListener(e -> {
|
||||||
|
if (e.getStateChange() == ItemEvent.SELECTED) {
|
||||||
|
ConfigManager.setLastDisplayTypeIndex(libraryDisplayType.getSelectedIndex());
|
||||||
|
refreshLibrary();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.add(libraryDisplayType, BorderLayout.NORTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an item to libraryView.
|
||||||
|
*
|
||||||
|
* @param parentNode Node that should be the parent of this node.
|
||||||
|
* @param node This node.
|
||||||
|
*/
|
||||||
|
private void addNodeToTreeModel(DefaultTreeModel model, DefaultMutableTreeNode parentNode, DefaultMutableTreeNode node) {
|
||||||
|
model.insertNodeInto(node, parentNode, parentNode.getChildCount());
|
||||||
|
if (parentNode == model.getRoot()) {
|
||||||
|
model.nodeStructureChanged((TreeNode) model.getRoot());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showEmpty(){
|
||||||
|
DefaultTreeModel failedModel = new DefaultTreeModel(new DefaultMutableTreeNode());
|
||||||
|
DefaultMutableTreeNode failedParentNode = (DefaultMutableTreeNode) failedModel.getRoot();
|
||||||
|
DefaultMutableTreeNode failedNode = new DefaultMutableTreeNode("Library is empty!");
|
||||||
|
addNodeToTreeModel(failedModel, failedParentNode, failedNode);
|
||||||
|
libraryTree.setModel(failedModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showSongs() {
|
||||||
|
Optional<List<Song>> dbQuery = database.listAllT(Song.class);
|
||||||
|
dbQuery.ifPresent(x -> {
|
||||||
|
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
|
||||||
|
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
|
||||||
|
Collections.sort(x);
|
||||||
|
x.forEach(y -> addNodeToTreeModel(model, parentNode, new DefaultMutableTreeNode(y)));
|
||||||
|
libraryTree.setModel(model);
|
||||||
|
});
|
||||||
|
if (!dbQuery.isPresent())
|
||||||
|
showEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends HasSongs & Comparable<T>> void showGroupedSongs(Class<T> grouping) {
|
||||||
|
Optional<List<T>> dbQuery = database.listAllT(grouping);
|
||||||
|
dbQuery.ifPresent(x -> {
|
||||||
|
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
|
||||||
|
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
|
||||||
|
Collections.sort(x);
|
||||||
|
x.forEach(y -> {
|
||||||
|
DefaultMutableTreeNode outerNode = new DefaultMutableTreeNode(y);
|
||||||
|
addNodeToTreeModel(model, parentNode, outerNode);
|
||||||
|
y.getSongs().forEach(z -> addNodeToTreeModel(model, outerNode, new DefaultMutableTreeNode(z)));
|
||||||
|
});
|
||||||
|
libraryTree.setModel(model);
|
||||||
|
});
|
||||||
|
if (!dbQuery.isPresent())
|
||||||
|
showEmpty();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateLibrary() {
|
||||||
|
if(!libraryUpdating.get()) {
|
||||||
|
libraryUpdating.set(true);
|
||||||
|
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
|
||||||
|
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
|
||||||
|
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("Updating Library...");
|
||||||
|
addNodeToTreeModel(model, parentNode, newNode);
|
||||||
|
addNodeToTreeModel(model, parentNode, updatingNode);
|
||||||
|
libraryTree.setModel(model);
|
||||||
|
List<Path> dirs = ConfigManager.getLibraryDirectories().stream().map(File::toPath).collect(Collectors.toList());
|
||||||
|
LibraryUtils.processSongsWithCallback(database, dirs, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refreshLibrary() {
|
||||||
|
if(!libraryUpdating.get()) { // Don't try to refresh while updating!!
|
||||||
|
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
|
||||||
|
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
|
||||||
|
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("Refreshing Library...");
|
||||||
|
addNodeToTreeModel(model, parentNode, newNode);
|
||||||
|
libraryTree.setModel(model);
|
||||||
|
Thread populateThread = new Thread(() -> {
|
||||||
|
libraryDisplayVariants.get(libraryDisplayType.getSelectedItem().toString()).run();
|
||||||
|
// If we get here and the model hasn't changed, the library must be empty.
|
||||||
|
if (libraryTree.getModel() == model) {
|
||||||
|
DefaultTreeModel failedModel = new DefaultTreeModel(new DefaultMutableTreeNode());
|
||||||
|
DefaultMutableTreeNode failedParentNode = (DefaultMutableTreeNode) failedModel.getRoot();
|
||||||
|
DefaultMutableTreeNode failedNode = new DefaultMutableTreeNode("Library is empty!");
|
||||||
|
addNodeToTreeModel(failedModel, failedParentNode, failedNode);
|
||||||
|
libraryTree.setModel(failedModel);
|
||||||
|
}
|
||||||
|
}, "libraryRefresh");
|
||||||
|
populateThread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void libraryUpdated(boolean successful) {
|
||||||
|
libraryUpdating.set(false);
|
||||||
|
refreshLibrary();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void currentlyUpdating(String name) {
|
||||||
|
updatingNode.setUserObject(name);
|
||||||
|
((DefaultTreeModel)libraryTree.getModel()).nodeChanged(updatingNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LibraryMouseAdapter extends MouseAdapter {
|
||||||
|
@Override
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
int selRow = libraryTree.getRowForLocation(e.getX(), e.getY());
|
||||||
|
if(e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2 && selRow != -1)
|
||||||
|
try {
|
||||||
|
Object selectedItem = ((DefaultMutableTreeNode) libraryTree.getPathForLocation(e.getX(), e.getY()).getLastPathComponent()).getUserObject();
|
||||||
|
if (selectedItem != null) {
|
||||||
|
if (selectedItem instanceof Song) {
|
||||||
|
playlist.addSong((Song) selectedItem);
|
||||||
|
} else if (selectedItem instanceof HasSongs) {
|
||||||
|
((HasSongs) selectedItem).getSongs().forEach(playlist::addSong);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (NullPointerException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LibraryTreeCellRenderer implements TreeCellRenderer {
|
||||||
|
|
||||||
|
private final JLabel label;
|
||||||
|
private final Icon missingIcon = new ImageIcon(JTreeLibrary.class.getClassLoader().getResource("missing.gif"));
|
||||||
|
|
||||||
|
LibraryTreeCellRenderer() {
|
||||||
|
label = new JLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.awt.Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
|
||||||
|
Object o = ((DefaultMutableTreeNode) value).getUserObject();
|
||||||
|
label.setIcon(null);
|
||||||
|
if (o != null) {
|
||||||
|
label.setText(o.toString());
|
||||||
|
if (o instanceof Album) {
|
||||||
|
Album album = (Album) o;
|
||||||
|
label.setIcon(missingIcon);
|
||||||
|
album.getAlbumArt().ifPresent(x -> label.setIcon(new ImageIcon(x)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@ package musicplayer.model;
|
|||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public interface HasSongs {
|
public interface HasSongs extends IDBType {
|
||||||
|
|
||||||
Set<Song> getSongs();
|
Set<Song> getSongs();
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import org.gstreamer.elements.PlayBin2;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
class GStreamerPlayer implements IPlayer{
|
public class GStreamerPlayer implements IPlayer{
|
||||||
|
|
||||||
private final PlayBin2 playBin;
|
private final PlayBin2 playBin;
|
||||||
|
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
package musicplayer.player;
|
package musicplayer.player;
|
||||||
|
|
||||||
import com.google.inject.ImplementedBy;
|
|
||||||
import musicplayer.StartPlayingException;
|
import musicplayer.StartPlayingException;
|
||||||
import musicplayer.model.Song;
|
import musicplayer.model.Song;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@ImplementedBy(GStreamerPlayer.class)
|
|
||||||
public interface IPlayer {
|
public interface IPlayer {
|
||||||
void playSong(Optional<Song> inputSong) throws StartPlayingException;
|
void playSong(Optional<Song> inputSong) throws StartPlayingException;
|
||||||
void stop();
|
void stop();
|
||||||
|
24
src/main/java/musicplayer/playlist/IPlaylist.java
Normal file
24
src/main/java/musicplayer/playlist/IPlaylist.java
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package musicplayer.playlist;
|
||||||
|
|
||||||
|
import musicplayer.model.Song;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface IPlaylist {
|
||||||
|
void addSong(Song song);
|
||||||
|
Optional<Song> getFirst();
|
||||||
|
Optional<Song> getNext(Song currentSong);
|
||||||
|
Optional<Song> getPrevious(Song currentSong);
|
||||||
|
Optional<Song> get(int index);
|
||||||
|
Optional<Song> getActive();
|
||||||
|
List<Song> getSongList();
|
||||||
|
int getIndex(Song song);
|
||||||
|
void delete(Song song);
|
||||||
|
void delete(int index);
|
||||||
|
void delete(int[] indices);
|
||||||
|
void deleteAll();
|
||||||
|
boolean isEmpty();
|
||||||
|
void setPlayingSong(Song song);
|
||||||
|
|
||||||
|
}
|
295
src/main/java/musicplayer/playlist/JTablePlaylist.java
Normal file
295
src/main/java/musicplayer/playlist/JTablePlaylist.java
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
package musicplayer.playlist;
|
||||||
|
|
||||||
|
import musicplayer.model.Song;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
public class JTablePlaylist extends JScrollPane implements IPlaylist {
|
||||||
|
|
||||||
|
private final PlaylistTableModel playlistTableModel;
|
||||||
|
private final JTable playList;
|
||||||
|
|
||||||
|
public JTablePlaylist(){
|
||||||
|
playList = new JTable();
|
||||||
|
playlistTableModel = new PlaylistTableModel();
|
||||||
|
this.setViewportView(playList);
|
||||||
|
setupMouse();
|
||||||
|
playList.setRowSelectionAllowed(true);
|
||||||
|
playList.setModel(playlistTableModel);
|
||||||
|
playList.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
||||||
|
playList.getColumnModel().getColumn(0).setPreferredWidth(10);
|
||||||
|
playList.getColumnModel().getColumn(0).setMaxWidth(10);
|
||||||
|
playList.getColumnModel().getColumn(1).setPreferredWidth(40);
|
||||||
|
playList.getColumnModel().getColumn(1).setMaxWidth(40);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupMouse(){
|
||||||
|
JPopupMenu popupMenu = new JPopupMenu();
|
||||||
|
JMenuItem menuItem = new JMenuItem("Remove");
|
||||||
|
menuItem.addActionListener((e) -> delete(playList.getSelectedRows()));
|
||||||
|
popupMenu.add(menuItem);
|
||||||
|
playList.setComponentPopupMenu(popupMenu);
|
||||||
|
popupMenu = new JPopupMenu();
|
||||||
|
menuItem = new JMenuItem("Clear all");
|
||||||
|
menuItem.addActionListener((e) -> deleteAll());
|
||||||
|
popupMenu.add(menuItem);
|
||||||
|
this.setComponentPopupMenu(popupMenu);
|
||||||
|
this.setViewportView(playList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addSong(Song song) {
|
||||||
|
playlistTableModel.addSong(song);
|
||||||
|
playList.revalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Song> getFirst() {
|
||||||
|
return playlistTableModel.getFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Song> getNext(Song currentSong) {
|
||||||
|
return playlistTableModel.nextSong(currentSong);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Song> getPrevious(Song currentSong) {
|
||||||
|
return playlistTableModel.previous(currentSong);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Song> get(int index) {
|
||||||
|
return playlistTableModel.getSong(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Song> getActive() {
|
||||||
|
if (playList.getRowCount() > 0) {
|
||||||
|
if (playList.getSelectedRowCount() > 0){
|
||||||
|
return get(playList.getSelectedRow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Song> getSongList() {
|
||||||
|
return playlistTableModel.getSongList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIndex(Song song) {
|
||||||
|
return playlistTableModel.getSongIndex(song);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(Song song) {
|
||||||
|
playlistTableModel.removeSong(song);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(int index) {
|
||||||
|
playlistTableModel.removeSong(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(int[] indices) {
|
||||||
|
playlistTableModel.removeSong(indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteAll() {
|
||||||
|
playlistTableModel.removeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return playlistTableModel.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPlayingSong(Song song){
|
||||||
|
int index = playlistTableModel.getSongIndex(song);
|
||||||
|
if(index >= 0)
|
||||||
|
SwingUtilities.invokeLater(() -> playList.setRowSelectionInterval(index, index));
|
||||||
|
playlistTableModel.setPlayingRow(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PlaylistTableModel extends AbstractTableModel {
|
||||||
|
|
||||||
|
private List<Song> songList = new ArrayList<>();
|
||||||
|
private int playingRow = -1; // -1 means no song is currently playing.
|
||||||
|
|
||||||
|
public PlaylistTableModel(){ }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRowCount() {
|
||||||
|
return songList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnCount() {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||||
|
Object o = "?";
|
||||||
|
Song song = songList.get(rowIndex);
|
||||||
|
switch (columnIndex){
|
||||||
|
case 0: o = (playingRow == rowIndex) ? "▶" : " "; break;
|
||||||
|
case 1: o = song.getTrackNumber(); break;
|
||||||
|
case 2: o = song.getTitle(); break;
|
||||||
|
case 3: o = song.getArtist(); break;
|
||||||
|
case 4: o = song.getAlbum(); break;
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getColumnClass(int columnIndex) {
|
||||||
|
return Song.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName(int column) {
|
||||||
|
String name = "";
|
||||||
|
switch (column){
|
||||||
|
case 0 : name = " "; break;
|
||||||
|
case 1: name = "Track"; break;
|
||||||
|
case 2: name = "Title"; break;
|
||||||
|
case 3: name = "Artist"; break;
|
||||||
|
case 4: name = "Album"; break;
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a song to the current playlist.
|
||||||
|
* @param song Song to add to the playlist.
|
||||||
|
*/
|
||||||
|
public void addSong(Song song){
|
||||||
|
songList.add(song);
|
||||||
|
fireTableDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The first song in the playlist.
|
||||||
|
*/
|
||||||
|
public Optional<Song> getFirst(){
|
||||||
|
return songList.size() > 0 ? Optional.of(songList.get(0)) : Optional.<Song>empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next song in the playlist. (Wraps around)
|
||||||
|
* @param song The current song.
|
||||||
|
* @return The song after the current song.
|
||||||
|
*/
|
||||||
|
public Optional<Song> nextSong(Song song){
|
||||||
|
int index = songList.indexOf(song) + 1;
|
||||||
|
if(index >= songList.size()) index = 0;
|
||||||
|
return songList.size() > 0 ? Optional.of(songList.get(index)) : Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the previous song in the playlist. (Wraps around)
|
||||||
|
* @param song The current song.
|
||||||
|
* @return The song before the current song.
|
||||||
|
*/
|
||||||
|
public Optional<Song> previous(Song song){
|
||||||
|
int index = songList.indexOf(song) - 1;
|
||||||
|
if(index < 0) index = songList.size() - 1;
|
||||||
|
return songList.size() > 0 ? Optional.of(songList.get(index)) : Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param song Song to find in the current playlist.
|
||||||
|
* @return Index of song in the playlist.
|
||||||
|
*/
|
||||||
|
public int getSongIndex(Song song){
|
||||||
|
return song != null ? songList.indexOf(song) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the song at index from the playlist.
|
||||||
|
* @param index List index of the song to be removed.
|
||||||
|
*/
|
||||||
|
public void removeSong(int index){
|
||||||
|
if(songList.size() > index && index >= 0){
|
||||||
|
songList.remove(index);
|
||||||
|
fireTableRowsDeleted(index, index);
|
||||||
|
if(index < playingRow)
|
||||||
|
playingRow--;
|
||||||
|
else if(playingRow == index)
|
||||||
|
playingRow = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a number of songs from the playlist.
|
||||||
|
* @param index Indices of the songs to be removed.
|
||||||
|
*/
|
||||||
|
public void removeSong(int[] index){
|
||||||
|
List<Integer> indices = IntStream.of(index).boxed().collect(Collectors.toList());
|
||||||
|
Collections.reverse(indices); // Do removals in reverse order to prevent mis-deletion.
|
||||||
|
indices.forEach(this::removeSong);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a song from the playlist.
|
||||||
|
* @param song Song to be removed.
|
||||||
|
*/
|
||||||
|
public void removeSong(Song song){
|
||||||
|
songList.remove(song);
|
||||||
|
fireTableDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Is the playlist currently empty.
|
||||||
|
*/
|
||||||
|
public boolean isEmpty(){
|
||||||
|
return songList.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all songs from the playlist.
|
||||||
|
*/
|
||||||
|
public void removeAll(){
|
||||||
|
songList.clear();
|
||||||
|
fireTableDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a Song from the playlist based on its index.
|
||||||
|
* @param index Index of the Song to get.
|
||||||
|
* @return Song object at index.
|
||||||
|
*/
|
||||||
|
public Optional<Song> getSong(int index){
|
||||||
|
return songList.size() > 0 && index >= 0 && index < songList.size() ? Optional.of(songList.get(index)) : Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the row at index to have a playing icon.
|
||||||
|
* @param index Row index to display as playing.
|
||||||
|
*/
|
||||||
|
public void setPlayingRow(int index){
|
||||||
|
playingRow = index;
|
||||||
|
fireTableDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Songs currently in the playlist.
|
||||||
|
*/
|
||||||
|
public List<Song> getSongList(){
|
||||||
|
return songList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,76 +0,0 @@
|
|||||||
package musicplayer.swingmodels;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class LibraryListModel<T extends File> extends AbstractListModel<T> {
|
|
||||||
|
|
||||||
private List<T> libraryFolders = new ArrayList<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize() {
|
|
||||||
return libraryFolders.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T getElementAt(int index) {
|
|
||||||
return libraryFolders.get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a directory to the list of directories used for library indexing.
|
|
||||||
* @param folder Directory to add.
|
|
||||||
*/
|
|
||||||
public void addFolder(T folder){
|
|
||||||
if(folder.exists() && folder.isDirectory()) {
|
|
||||||
libraryFolders.add(folder);
|
|
||||||
fireAllContentsChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return List of directories currently used for library indexing.
|
|
||||||
*/
|
|
||||||
public List<T> currentFolderList(){
|
|
||||||
return new ArrayList<>(libraryFolders); // Copy? Don't want modification via here.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this File already exists in libraryFolders.
|
|
||||||
* @param file File to find.
|
|
||||||
* @return Does file exist in libraryFolders.
|
|
||||||
*/
|
|
||||||
public boolean contains(T file){
|
|
||||||
return libraryFolders.contains(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the list contained in libraryFolders.
|
|
||||||
* @param folderList New directory list.
|
|
||||||
*/
|
|
||||||
public void setFolderList(List<T> folderList){
|
|
||||||
if(folderList != null && folderList.size() > 0)
|
|
||||||
libraryFolders = new ArrayList<>(folderList);
|
|
||||||
else
|
|
||||||
libraryFolders = new ArrayList<>();
|
|
||||||
fireAllContentsChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a directory from libraryFolders.
|
|
||||||
* @param file Directory to be removed.
|
|
||||||
*/
|
|
||||||
public void removeFile(T file){
|
|
||||||
libraryFolders.remove(file);
|
|
||||||
fireAllContentsChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fire that libraryFolders contents has changed.
|
|
||||||
*/
|
|
||||||
private void fireAllContentsChanged(){
|
|
||||||
fireContentsChanged(this, 0, libraryFolders.size() == 0 ? 0 : libraryFolders.size() - 1);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,182 +0,0 @@
|
|||||||
package musicplayer.swingmodels;
|
|
||||||
|
|
||||||
import musicplayer.model.Song;
|
|
||||||
|
|
||||||
import javax.swing.table.AbstractTableModel;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.IntStream;
|
|
||||||
|
|
||||||
public class PlaylistTableModel extends AbstractTableModel {
|
|
||||||
|
|
||||||
private List<Song> songList;
|
|
||||||
private int playingRow = -1; // -1 means no song is currently playing.
|
|
||||||
|
|
||||||
public PlaylistTableModel(List<Song> songList){
|
|
||||||
this.songList = songList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getRowCount() {
|
|
||||||
return songList.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getColumnCount() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
|
||||||
Object o = "?";
|
|
||||||
Song song = songList.get(rowIndex);
|
|
||||||
switch (columnIndex){
|
|
||||||
case 0: o = (playingRow == rowIndex) ? "▶" : " "; break;
|
|
||||||
case 1: o = song.getTrackNumber(); break;
|
|
||||||
case 2: o = song.getTitle(); break;
|
|
||||||
case 3: o = song.getArtist(); break;
|
|
||||||
case 4: o = song.getAlbum(); break;
|
|
||||||
}
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<?> getColumnClass(int columnIndex) {
|
|
||||||
return Song.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getColumnName(int column) {
|
|
||||||
String name = "";
|
|
||||||
switch (column){
|
|
||||||
case 0 : name = " "; break;
|
|
||||||
case 1: name = "Track"; break;
|
|
||||||
case 2: name = "Title"; break;
|
|
||||||
case 3: name = "Artist"; break;
|
|
||||||
case 4: name = "Album"; break;
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a song to the current playlist.
|
|
||||||
* @param song Song to add to the playlist.
|
|
||||||
*/
|
|
||||||
public void addSong(Song song){
|
|
||||||
songList.add(song);
|
|
||||||
fireTableDataChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The first song in the playlist.
|
|
||||||
*/
|
|
||||||
public Optional<Song> getFirst(){
|
|
||||||
return songList.size() > 0 ? Optional.of(songList.get(0)) : Optional.<Song>empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the next song in the playlist. (Wraps around)
|
|
||||||
* @param song The current song.
|
|
||||||
* @return The song after the current song.
|
|
||||||
*/
|
|
||||||
public Optional<Song> nextSong(Song song){
|
|
||||||
int index = songList.indexOf(song) + 1;
|
|
||||||
if(index >= songList.size()) index = 0;
|
|
||||||
return songList.size() > 0 ? Optional.of(songList.get(index)) : Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the previous song in the playlist. (Wraps around)
|
|
||||||
* @param song The current song.
|
|
||||||
* @return The song before the current song.
|
|
||||||
*/
|
|
||||||
public Optional<Song> previous(Song song){
|
|
||||||
int index = songList.indexOf(song) - 1;
|
|
||||||
if(index < 0) index = songList.size() - 1;
|
|
||||||
return songList.size() > 0 ? Optional.of(songList.get(index)) : Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param song Song to find in the current playlist.
|
|
||||||
* @return Index of song in the playlist.
|
|
||||||
*/
|
|
||||||
public int getSongIndex(Song song){
|
|
||||||
return songList.indexOf(song);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the song at index from the playlist.
|
|
||||||
* @param index List index of the song to be removed.
|
|
||||||
*/
|
|
||||||
public void removeSong(int index){
|
|
||||||
if(songList.size() > index && index >= 0){
|
|
||||||
songList.remove(index);
|
|
||||||
fireTableRowsDeleted(index, index);
|
|
||||||
if(index < playingRow)
|
|
||||||
playingRow--;
|
|
||||||
else if(playingRow == index)
|
|
||||||
playingRow = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a number of songs from the playlist.
|
|
||||||
* @param index Indices of the songs to be removed.
|
|
||||||
*/
|
|
||||||
public void removeSong(int[] index){
|
|
||||||
List<Integer> indices = IntStream.of(index).boxed().collect(Collectors.toList());
|
|
||||||
Collections.reverse(indices); // Do removals in reverse order to prevent mis-deletion.
|
|
||||||
indices.forEach(this::removeSong);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a song from the playlist.
|
|
||||||
* @param song Song to be removed.
|
|
||||||
*/
|
|
||||||
public void removeSong(Song song){
|
|
||||||
songList.remove(song);
|
|
||||||
fireTableDataChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Is the playlist currently empty.
|
|
||||||
*/
|
|
||||||
public boolean isEmpty(){
|
|
||||||
return songList.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all songs from the playlist.
|
|
||||||
*/
|
|
||||||
public void removeAll(){
|
|
||||||
songList.clear();
|
|
||||||
fireTableDataChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a Song from the playlist based on its index.
|
|
||||||
* @param index Index of the Song to get.
|
|
||||||
* @return Song object at index.
|
|
||||||
*/
|
|
||||||
public Optional<Song> getSong(int index){
|
|
||||||
return songList.size() > 0 && index >= 0 && index < songList.size() ? Optional.of(songList.get(index)) : Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the row at index to have a playing icon.
|
|
||||||
* @param index Row index to display as playing.
|
|
||||||
*/
|
|
||||||
public void setPlayingRow(int index){
|
|
||||||
playingRow = index;
|
|
||||||
fireTableDataChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Songs currently in the playlist.
|
|
||||||
*/
|
|
||||||
public List<Song> getSongList(){
|
|
||||||
return songList;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package musicplayer;
|
package musicplayer.util;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
Loading…
Reference in New Issue
Block a user