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>
|
||||
<artifactId>guice</artifactId>
|
||||
<version>4.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
package musicplayer;
|
||||
|
||||
import musicplayer.swingmodels.LibraryListModel;
|
||||
import musicplayer.util.ConfigManager;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class LibraryConfigGUI {
|
||||
@ -73,4 +74,74 @@ class LibraryConfigGUI {
|
||||
files.forEach(listModel::removeFile);
|
||||
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.Inject;
|
||||
import com.google.inject.Injector;
|
||||
import musicplayer.callbacks.LibraryCallbackInterface;
|
||||
import musicplayer.callbacks.PlayerCallbackInterface;
|
||||
import musicplayer.db.IDatabase;
|
||||
import musicplayer.model.Album;
|
||||
import musicplayer.model.Artist;
|
||||
import musicplayer.model.HasSongs;
|
||||
import musicplayer.library.ILibrary;
|
||||
import musicplayer.model.Song;
|
||||
import musicplayer.player.IPlayer;
|
||||
import musicplayer.swingmodels.PlaylistTableModel;
|
||||
import musicplayer.playlist.IPlaylist;
|
||||
import musicplayer.util.ConfigManager;
|
||||
import musicplayer.util.Icons;
|
||||
import musicplayer.util.LibraryUtils;
|
||||
import musicplayer.util.PlaylistUtils;
|
||||
import org.jnativehook.GlobalScreen;
|
||||
import org.jnativehook.NativeHookException;
|
||||
@ -23,12 +20,7 @@ import org.jnativehook.keyboard.NativeKeyListener;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
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.event.ItemEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.io.BufferedReader;
|
||||
@ -36,35 +28,25 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterface {
|
||||
private JPanel mainPanel;
|
||||
private final JTree libraryView = new JTree();
|
||||
private final JTable playList = new JTable();
|
||||
public class PlayerGUI extends JPanel implements PlayerCallbackInterface {
|
||||
private final IPlaylist playlist;
|
||||
private JSlider seekBar;
|
||||
private final JComboBox<String> libraryDisplayType = new JComboBox<>();
|
||||
private final IPlayer player;
|
||||
private final IDatabase database;
|
||||
private final PlaylistTableModel playlistTableModel = new PlaylistTableModel(new ArrayList<>());
|
||||
private static final DefaultMutableTreeNode updatingNode = new DefaultMutableTreeNode();
|
||||
private boolean libraryUpdating = false;
|
||||
private final ILibrary library;
|
||||
private final AtomicBoolean seeking = new AtomicBoolean(false);
|
||||
|
||||
private final Map<String, Runnable> libraryDisplayVariants = createDisplayVariantMap();
|
||||
|
||||
@Inject
|
||||
public PlayerGUI(IPlayer player, IDatabase database) {
|
||||
public PlayerGUI(IPlayer player, IPlaylist playlist, ILibrary library) {
|
||||
this.player = player;
|
||||
this.database = database;
|
||||
this.playlist = playlist;
|
||||
this.library = library;
|
||||
createUI();
|
||||
resetTree();
|
||||
Thread seekBarUpdater = new Thread(() -> {
|
||||
boolean running = true;
|
||||
while (running) {
|
||||
@ -99,15 +81,15 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
||||
System.out.println(ex.getMessage());
|
||||
System.out.println(Arrays.toString(ex.getStackTrace()));
|
||||
}
|
||||
refreshLibrary();
|
||||
library.refreshLibrary();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Injector injector = Guice.createInjector();
|
||||
Injector injector = Guice.createInjector(new SwingUIModule());
|
||||
PlayerGUI playerGUI = injector.getInstance(PlayerGUI.class);
|
||||
JFrame frame = new JFrame();
|
||||
frame.setMinimumSize(new Dimension(600, 400));
|
||||
frame.setContentPane(playerGUI.mainPanel);
|
||||
frame.setContentPane(playerGUI);
|
||||
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||
frame.pack();
|
||||
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.
|
||||
*
|
||||
@ -187,17 +114,14 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
||||
*/
|
||||
@Override
|
||||
public void setSongHighlighting(Song playingSong) {
|
||||
int index = playlistTableModel.getSongIndex(playingSong);
|
||||
if(index >= 0)
|
||||
SwingUtilities.invokeLater(() -> playList.setRowSelectionInterval(index, index));
|
||||
playlistTableModel.setPlayingRow(index);
|
||||
playlist.setPlayingSong(playingSong);
|
||||
}
|
||||
|
||||
private void playSong(Optional<Song> song){
|
||||
try {
|
||||
player.playSong(song);
|
||||
} 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.");
|
||||
}
|
||||
}
|
||||
@ -207,7 +131,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
||||
*/
|
||||
public void playNextSong() {
|
||||
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() {
|
||||
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
|
||||
public void removeInvalidSong(Song invalidSong) {
|
||||
playlistTableModel.removeSong(invalidSong);
|
||||
playlist.delete(invalidSong);
|
||||
}
|
||||
|
||||
public void playerStopped(){
|
||||
setSongHighlighting(player.getCurrentSong());
|
||||
playlistTableModel.setPlayingRow(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
playlist.setPlayingSong(null);
|
||||
}
|
||||
|
||||
/*
|
||||
mainPanel
|
||||
this
|
||||
+--------------------+
|
||||
|menuBar |
|
||||
+---------+----------+
|
||||
@ -311,79 +175,22 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
||||
+--------------------+
|
||||
*/
|
||||
private void createUI() {
|
||||
mainPanel = new JPanel();
|
||||
mainPanel.setLayout(new BorderLayout(0, 0));
|
||||
this.setLayout(new BorderLayout(0, 0));
|
||||
final JSplitPane libraryAndPlaylistPane = new JSplitPane();
|
||||
libraryAndPlaylistPane.setDividerLocation(240);
|
||||
libraryAndPlaylistPane.setRightComponent(createPlaylistArea());
|
||||
mainPanel.add(libraryAndPlaylistPane, BorderLayout.CENTER);
|
||||
final JPanel libraryAndComboboxPane = new JPanel();
|
||||
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());
|
||||
libraryAndPlaylistPane.setRightComponent((JScrollPane)playlist);
|
||||
this.add(libraryAndPlaylistPane, BorderLayout.CENTER);
|
||||
libraryAndPlaylistPane.setLeftComponent((JPanel)library);
|
||||
final JPanel controlPane = new JPanel();
|
||||
controlPane.setLayout(new BorderLayout(0, 0));
|
||||
mainPanel.add(controlPane, BorderLayout.SOUTH);
|
||||
this.add(controlPane, BorderLayout.SOUTH);
|
||||
seekBar = new JSlider();
|
||||
seekBar.setMinorTickSpacing(1);
|
||||
seekBar.setValue(0);
|
||||
seekBar.addMouseListener(new SeekbarMouseListener());
|
||||
controlPane.add(seekBar, BorderLayout.NORTH);
|
||||
controlPane.add(createControlButtons(), BorderLayout.SOUTH);
|
||||
mainPanel.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;
|
||||
this.add(createMenuBar(), BorderLayout.NORTH);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -394,16 +201,10 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
||||
toolBar.setFloatable(false);
|
||||
JPanel controlBar = new JPanel(new GridLayout(1, 5));
|
||||
JButton playButton = new JButton();
|
||||
playButton.setName("playButton");
|
||||
playButton.setIcon(Icons.playIcon);
|
||||
playButton.setMnemonic('P');
|
||||
playButton.addActionListener(e -> {
|
||||
if (playList.getRowCount() > 0) {
|
||||
if (playList.getSelectedRowCount() > 0)
|
||||
playSong(playlistTableModel.getSong(playList.getSelectedRow()));
|
||||
else
|
||||
playSong(playlistTableModel.getFirst());
|
||||
}
|
||||
});
|
||||
playButton.addActionListener(e -> playSong(playlist.getActive()));
|
||||
controlBar.add(playButton);
|
||||
JButton pauseButton = new JButton();
|
||||
pauseButton.setIcon(Icons.pauseIcon);
|
||||
@ -465,12 +266,12 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
||||
// Tools menu
|
||||
JMenu tools = new JMenu("Tools");
|
||||
JMenuItem menuItem = new JMenuItem("Refresh Library");
|
||||
menuItem.addActionListener(e -> refreshLibrary());
|
||||
menuItem.addActionListener(e -> library.refreshLibrary());
|
||||
tools.add(menuItem);
|
||||
|
||||
menuItem = new JMenuItem("Update Library");
|
||||
menuItem.setToolTipText("Reindex and refresh library.");
|
||||
menuItem.addActionListener(e -> updateLibrary());
|
||||
menuItem.addActionListener(e -> library.updateLibrary());
|
||||
tools.add(menuItem);
|
||||
|
||||
menuItem = new JMenuItem("Library Settings");
|
||||
@ -483,7 +284,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
||||
JMenu playlistTools = new JMenu("Playlist");
|
||||
menuItem = new JMenuItem("Clear All");
|
||||
menuItem.addActionListener((e) -> {
|
||||
playlistTableModel.removeAll();
|
||||
playlist.deleteAll();
|
||||
player.stop();
|
||||
});
|
||||
playlistTools.add(menuItem);
|
||||
@ -491,17 +292,17 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
||||
FileNameExtensionFilter m3uExtensionFilter = new FileNameExtensionFilter("m3u playlist", "m3u");
|
||||
menuItem = new JMenuItem("Save Playlist");
|
||||
menuItem.addActionListener(e -> {
|
||||
if(!playlistTableModel.isEmpty()) {
|
||||
if(!playlist.isEmpty()) {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
|
||||
fileChooser.setSelectedFile(new File("playlist.m3u"));
|
||||
fileChooser.setFileFilter(m3uExtensionFilter);
|
||||
if (fileChooser.showSaveDialog(mainPanel) == JFileChooser.APPROVE_OPTION) {
|
||||
if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||
String filename = fileChooser.getSelectedFile().toString();
|
||||
if (!filename.endsWith(".m3u"))
|
||||
filename += ".m3u";
|
||||
try {
|
||||
PlaylistUtils.writePlaylistToFile(playlistTableModel.getSongList(), new File(filename));
|
||||
PlaylistUtils.writePlaylistToFile(playlist.getSongList(), new File(filename));
|
||||
} catch (IOException e1) {
|
||||
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();
|
||||
fileChooser.setDialogType(JFileChooser.OPEN_DIALOG);
|
||||
fileChooser.setFileFilter(m3uExtensionFilter);
|
||||
if(fileChooser.showOpenDialog(mainPanel) == JFileChooser.APPROVE_OPTION){
|
||||
PlaylistUtils.readPlaylistFromFile(fileChooser.getSelectedFile()).stream().forEach(playlistTableModel::addSong);
|
||||
if(fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION){
|
||||
PlaylistUtils.readPlaylistFromFile(fileChooser.getSelectedFile()).stream().forEach(playlist::addSong);
|
||||
}
|
||||
});
|
||||
playlistTools.add(menuItem);
|
||||
@ -540,7 +341,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
||||
e1.printStackTrace();
|
||||
}
|
||||
});
|
||||
JOptionPane.showMessageDialog(mainPanel, messagePane, "About", JOptionPane.INFORMATION_MESSAGE);
|
||||
JOptionPane.showMessageDialog(this, messagePane, "About", JOptionPane.INFORMATION_MESSAGE);
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
@ -551,25 +352,6 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
|
||||
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.
|
||||
* 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.
|
||||
}
|
||||
else{
|
||||
if (playList.getRowCount() > 0) {
|
||||
if (playList.getSelectedRowCount() > 0)
|
||||
playSong(playlistTableModel.getSong(playList.getSelectedRow()));
|
||||
else
|
||||
playSong(playlistTableModel.getFirst());
|
||||
}
|
||||
playSong(playlist.getActive());
|
||||
}
|
||||
break;
|
||||
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;
|
||||
|
||||
import musicplayer.ConfigManager;
|
||||
import musicplayer.util.ConfigManager;
|
||||
import musicplayer.model.*;
|
||||
import musicplayer.util.AlbumArtExtractor;
|
||||
import org.hibernate.Criteria;
|
||||
@ -18,7 +18,7 @@ import java.util.Properties;
|
||||
/**
|
||||
* Class for managing connection to Hibernate.
|
||||
*/
|
||||
class HibernateDatabase implements IDatabase{
|
||||
public class HibernateDatabase implements IDatabase{
|
||||
|
||||
private static SessionFactory sessionFactory;
|
||||
|
||||
|
@ -1,12 +1,10 @@
|
||||
package musicplayer.db;
|
||||
|
||||
import com.google.inject.ImplementedBy;
|
||||
import musicplayer.model.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@ImplementedBy(HibernateDatabase.class)
|
||||
public interface IDatabase {
|
||||
Optional<Album> getOneAlbum(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;
|
||||
|
||||
public interface HasSongs {
|
||||
public interface HasSongs extends IDBType {
|
||||
|
||||
Set<Song> getSongs();
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import org.gstreamer.elements.PlayBin2;
|
||||
import java.io.File;
|
||||
import java.util.Optional;
|
||||
|
||||
class GStreamerPlayer implements IPlayer{
|
||||
public class GStreamerPlayer implements IPlayer{
|
||||
|
||||
private final PlayBin2 playBin;
|
||||
|
||||
|
@ -1,12 +1,10 @@
|
||||
package musicplayer.player;
|
||||
|
||||
import com.google.inject.ImplementedBy;
|
||||
import musicplayer.StartPlayingException;
|
||||
import musicplayer.model.Song;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@ImplementedBy(GStreamerPlayer.class)
|
||||
public interface IPlayer {
|
||||
void playSong(Optional<Song> inputSong) throws StartPlayingException;
|
||||
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.FileInputStream;
|
Loading…
Reference in New Issue
Block a user